javaclass

classhook.py

78:f08b72e9f060
2004-12-08 Paul Boddie Added .jar archive support to the import hook. Fixed accidental attempts to import classes as modules.
     1 #!/usr/bin/env python     2      3 import ihooks # for the import machinery     4 import os, glob # for getting suitably-named files     5 from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic     6 import classfile, bytecode # Java class support     7 import zipfile # for Java archive inspection     8      9 # NOTE: Arbitrary constants pulled from thin air.    10     11 JAVA_PACKAGE = 20041113    12 JAVA_CLASS = 20041114    13 JAVA_ARCHIVE = 20041115    14     15 class ClassHooks(ihooks.Hooks):    16     17     "A filesystem hooks class providing information about supported files."    18     19     def get_suffixes(self):    20     21         "Return the recognised suffixes."    22     23         return [("", "", JAVA_PACKAGE), (os.extsep + "jar", "r", JAVA_ARCHIVE)] + ihooks.Hooks.get_suffixes(self)    24     25     def path_isdir(self, x, archive=None):    26     27         "Return whether 'x' is a directory in the given 'archive'."    28     29         if archive is None:    30             return ihooks.Hooks.path_isdir(self, x)    31     32         return self._get_dirname(x) in archive.namelist()    33     34     def _get_dirname(self, x):    35     36         """    37         Return the directory name for 'x'.    38         In zip files, the presence of "/" seems to indicate a directory.    39         """    40     41         if x.endswith("/"):    42             return x    43         else:    44             return x + "/"    45     46     def listdir(self, x, archive=None):    47     48         "Return the contents of the directory 'x' in the given 'archive'."    49     50         if archive is None:    51             return ihooks.Hooks.listdir(self, x)    52     53         x = self._get_dirname(x)    54         l = []    55         for path in archive.namelist():    56     57             # Find out if the path is within the given directory.    58     59             if path != x and path.startswith(x):    60     61                 # Get the path below the given directory.    62     63                 subpath = path[len(x):]    64     65                 # Find out whether the path is an object in the current directory.    66     67                 if subpath.count("/") == 0 or subpath.count("/") == 1 and subpath.endswith("/"):    68                     l.append(subpath)    69     70         return l    71     72     def matching(self, dir, extension, archive=None):    73     74         """    75         Return the matching files in the given directory 'dir' having the given    76         'extension' within the given 'archive'. Produce a list containing full    77         paths as opposed to simple filenames.    78         """    79     80         if archive is None:    81             return glob.glob(self.path_join(dir, "*" + extension))    82     83         dir = self._get_dirname(dir)    84         l = []    85         for path in self.listdir(dir, archive):    86             if path.endswith(extension):    87                 l.append(self.path_join(dir, path))    88         return l    89     90     def read(self, filename, archive=None):    91     92         """    93         Return the contents of the file with the given 'filename' in the given    94         'archive'.    95         """    96     97         if archive is None:    98             f = open(filename, "rb")    99             s = f.read()   100             f.close()   101             return s   102         return archive.read(filename)   103    104 class ClassLoader(ihooks.ModuleLoader):   105    106     "A class providing support for searching directories for supported files."   107    108     def find_module_in_dir(self, name, dir, allow_packages=1):   109    110         """   111         Find the module with the given 'name' in the given directory 'dir'.   112         Since Java packages/modules are directories containing class files,   113         return the required information tuple only when the path constructed   114         from 'dir' and 'name' refers to a directory containing class files.   115         """   116    117         result = ihooks.ModuleLoader.find_module_in_dir(self, name, dir, allow_packages)   118         if result is not None:   119             return result   120    121         # An archive may be opened.   122    123         archive = None   124    125         # Provide a special name for the current directory.   126    127         if name == "__this__":   128             path = "."   129    130         # Where no directory is given, return failure immediately.   131    132         elif dir is None:   133             return None   134    135         # Detect archives.   136    137         else:   138             archive, archive_path, path = self._get_archive_and_path(dir, name)   139    140         print "Processing name", name, "in", dir, "producing", path, "within archive", archive   141    142         if self._find_module_at_path(path, archive):   143             if archive is not None:   144                 return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE))   145             else:   146                 return (None, path, ("", "", JAVA_PACKAGE))   147         else:   148             return None   149    150     def _get_archive_and_path(self, dir, name):   151         parts = dir.split(":")   152         archive_path = parts[0]   153    154         # Archives may include an internal path, but will in any case have   155         # a primary part ending in .jar.   156    157         if archive_path.endswith(os.extsep + "jar"):   158             archive = zipfile.ZipFile(archive_path, "r")   159             path = self.hooks.path_join(":".join(parts[1:]), name)   160    161         # Otherwise, produce a filesystem-based path.   162    163         else:   164             archive = None   165             path = self.hooks.path_join(dir, name)   166    167         return archive, archive_path, path   168    169     def _get_path_in_archive(self, path):   170         parts = path.split(":")   171         if len(parts) == 1:   172             return parts[0]   173         else:   174             return ":".join(parts[1:])   175    176     def _find_module_at_path(self, path, archive):   177         if self.hooks.path_isdir(path, archive):   178             print "Looking in", path, "using archive", archive   179    180             # Look for classes in the directory.   181    182             if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:   183                 return 1   184    185             # Otherwise permit importing where directories containing classes exist.   186    187             print "Filenames are", self.hooks.listdir(path, archive)   188             for filename in self.hooks.listdir(path, archive):   189                 pathname = self.hooks.path_join(path, filename)   190                 result = self._find_module_at_path(pathname, archive)   191                 if result is not None:   192                     return result   193    194         return 0   195    196     def load_module(self, name, stuff):   197    198         """   199         Load the module with the given 'name', whose 'stuff' which describes the   200         location of the module is a tuple of the form (file, filename, (suffix,   201         mode, data type)). Return a module object or raise an ImportError if a   202         problem occurred in the import operation.   203         """   204    205         # Just go into the directory and find the class files.   206    207         archive, filename, info = stuff   208         suffix, mode, datatype = info   209         if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):   210             return ihooks.ModuleLoader.load_module(self, name, stuff)   211    212         print "Loading", archive, filename, info   213    214         # Set up the module.   215    216         module = self.hooks.add_module(name)   217         module.__path__ = [filename]   218    219         # Prepare a dictionary of globals.   220    221         global_names = module.__dict__   222         global_names["__builtins__"] = __builtins__   223    224         # Process each class file, producing a genuine Python class.   225    226         class_files = []   227         classes = []   228    229         # Get the real filename.   230    231         filename = self._get_path_in_archive(filename)   232         print "Real filename", filename   233    234         # Load the class files.   235    236         class_files = {}   237         for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):   238             print "Loading class", class_filename   239             s = self.hooks.read(class_filename, archive)   240             class_file = classfile.ClassFile(s)   241             class_files[str(class_file.this_class.get_name())] = class_file   242    243         # Get an index of the class files.   244    245         class_file_index = class_files.keys()   246    247         # NOTE: Unnecessary sorting for test purposes.   248    249         class_file_index.sort()   250    251         # Now go through the classes arranging them in a safe loading order.   252    253         position = 0   254         while position < len(class_file_index):   255             class_name = class_file_index[position]   256             super_class_name = str(class_files[class_name].super_class.get_name())   257    258             # Discover whether the superclass appears later.   259    260             try:   261                 super_class_position = class_file_index.index(super_class_name)   262                 if super_class_position > position:   263    264                     # If the superclass appears later, swap this class and the   265                     # superclass, then process the superclass.   266    267                     class_file_index[position] = super_class_name   268                     class_file_index[super_class_position] = class_name   269                     continue   270    271             except ValueError:   272                 pass   273    274             position += 1   275    276         class_files = [class_files[class_name] for class_name in class_file_index]   277    278         for class_file in class_files:   279             translator = bytecode.ClassTranslator(class_file)   280             cls, external_names = translator.process(global_names)   281             module.__dict__[cls.__name__] = cls   282             classes.append((cls, class_file))   283    284             # Import the local names.   285    286             for external_name in external_names:   287                 external_name_parts = external_name.split(".")   288                 if len(external_name_parts) > 1:   289                     external_module_name = ".".join(external_name_parts[:-1])   290                     print "* Importing", external_module_name   291                     obj = __import__(external_module_name, global_names, {}, [])   292                     global_names[external_name_parts[0]] = obj   293    294         # Finally, call __clinit__ methods for all relevant classes.   295    296         for cls, class_file in classes:   297             print "**", cls, class_file   298             if hasattr(cls, "__clinit__"):   299                 eval(cls.__clinit__.func_code, global_names)   300    301         return module   302    303 ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()   304    305 # vim: tabstop=4 expandtab shiftwidth=4