javaclass

classhook.py

84:eb71e53b14ce
2004-12-09 Paul Boddie Added support for "package unions" or "multi-rooted packages" where the root of a package hierarchy may appear in many places.
     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(self, name, path=None):   109    110         """   111         Find the module with the given 'name', using the given 'path' to locate   112         it. Note that ModuleLoader.find_module is almost sufficient, but does   113         not provide enough support for "package unions" where the root of a   114         package hierarchy may appear in several places.   115    116         Return a list of locations (each being the "stuff" data structure used   117         by load_module); this replaces the single "stuff" value or None returned   118         by ModuleLoader.find_module.   119         """   120    121         if path is None:   122             path = [None] + self.default_path()   123    124         found_locations = []   125    126         for dir in path:   127             stuff = self.find_module_in_dir(name, dir)   128             if stuff:   129                 found_locations.append(stuff)   130    131         return found_locations   132    133     def find_module_in_dir(self, name, dir, allow_packages=1):   134    135         """   136         Find the module with the given 'name' in the given directory 'dir'.   137         Since Java packages/modules are directories containing class files,   138         return the required information tuple only when the path constructed   139         from 'dir' and 'name' refers to a directory containing class files.   140         """   141    142         result = ihooks.ModuleLoader.find_module_in_dir(self, name, dir, allow_packages)   143         if result is not None:   144             return result   145    146         # An archive may be opened.   147    148         archive = None   149    150         # Provide a special name for the current directory.   151    152         if name == "__this__":   153             path = "."   154    155         # Where no directory is given, return failure immediately.   156    157         elif dir is None:   158             return None   159    160         # Detect archives.   161    162         else:   163             archive, archive_path, path = self._get_archive_and_path(dir, name)   164    165         #print "Processing name", name, "in", dir, "producing", path, "within archive", archive   166    167         if self._find_module_at_path(path, archive):   168             if archive is not None:   169                 return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE))   170             else:   171                 return (None, path, ("", "", JAVA_PACKAGE))   172         else:   173             return None   174    175     def _get_archive_and_path(self, dir, name):   176         parts = dir.split(":")   177         archive_path = parts[0]   178    179         # Archives may include an internal path, but will in any case have   180         # a primary part ending in .jar.   181    182         if archive_path.endswith(os.extsep + "jar"):   183             archive = zipfile.ZipFile(archive_path, "r")   184             path = self.hooks.path_join(":".join(parts[1:]), name)   185    186         # Otherwise, produce a filesystem-based path.   187    188         else:   189             archive = None   190             path = self.hooks.path_join(dir, name)   191    192         return archive, archive_path, path   193    194     def _get_path_in_archive(self, path):   195         parts = path.split(":")   196         if len(parts) == 1:   197             return parts[0]   198         else:   199             return ":".join(parts[1:])   200    201     def _find_module_at_path(self, path, archive):   202         if self.hooks.path_isdir(path, archive):   203             #print "Looking in", path, "using archive", archive   204    205             # Look for classes in the directory.   206    207             if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:   208                 return 1   209    210             # Otherwise permit importing where directories containing classes exist.   211    212             #print "Filenames are", self.hooks.listdir(path, archive)   213             for filename in self.hooks.listdir(path, archive):   214                 pathname = self.hooks.path_join(path, filename)   215                 result = self._find_module_at_path(pathname, archive)   216                 if result is not None:   217                     return result   218    219         return 0   220    221     def load_module(self, name, stuff):   222    223         """   224         Load the module with the given 'name', with a list of 'stuff' items,   225         each of which describes the location of the module and is a tuple of the   226         form (file, filename, (suffix, mode, data type)).   227    228         Return a module object or raise an ImportError if a problem occurred in   229         the import operation.   230    231         Note that the 'stuff' parameter is a list and not a single item as in   232         ModuleLoader.load_module. This should still work, however, since the   233         find_module method produces such a list.   234         """   235    236         # Set up the module.   237         # A union of all locations is placed in the module's path.   238    239         module = self.hooks.add_module(name)   240         module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]   241    242         # Just go into each package and find the class files.   243    244         for stuff_item in stuff:   245    246             # Extract the details, delegating loading responsibility to the   247             # default loader where appropriate.   248             # NOTE: Should we not be using some saved loader remembered upon   249             # NOTE: installation?   250    251             archive, filename, info = stuff_item   252             suffix, mode, datatype = info   253             if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):   254                 return ihooks.ModuleLoader.load_module(self, name, stuff_item)   255    256             #print "Loading", archive, filename, info   257    258             # Prepare a dictionary of globals.   259    260             global_names = module.__dict__   261             global_names["__builtins__"] = __builtins__   262    263             # Process each class file, producing a genuine Python class.   264    265             class_files = []   266             classes = []   267    268             # Get the real filename.   269    270             filename = self._get_path_in_archive(filename)   271             #print "Real filename", filename   272    273             # Load the class files.   274    275             class_files = {}   276             for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):   277                 #print "Loading class", class_filename   278                 s = self.hooks.read(class_filename, archive)   279                 class_file = classfile.ClassFile(s)   280                 class_files[str(class_file.this_class.get_name())] = class_file   281    282             # Get an index of the class files.   283    284             class_file_index = class_files.keys()   285    286             # NOTE: Unnecessary sorting for test purposes.   287    288             class_file_index.sort()   289    290             # Now go through the classes arranging them in a safe loading order.   291    292             position = 0   293             while position < len(class_file_index):   294                 class_name = class_file_index[position]   295                 super_class_name = str(class_files[class_name].super_class.get_name())   296    297                 # Discover whether the superclass appears later.   298    299                 try:   300                     super_class_position = class_file_index.index(super_class_name)   301                     if super_class_position > position:   302    303                         # If the superclass appears later, swap this class and the   304                         # superclass, then process the superclass.   305    306                         class_file_index[position] = super_class_name   307                         class_file_index[super_class_position] = class_name   308                         continue   309    310                 except ValueError:   311                     pass   312    313                 position += 1   314    315             class_files = [class_files[class_name] for class_name in class_file_index]   316    317             for class_file in class_files:   318                 translator = bytecode.ClassTranslator(class_file)   319                 cls, external_names = translator.process(global_names)   320                 module.__dict__[cls.__name__] = cls   321                 classes.append((cls, class_file))   322    323                 # Import the local names.   324    325                 for external_name in external_names:   326                     external_name_parts = external_name.split(".")   327                     if len(external_name_parts) > 1:   328                         external_module_name = ".".join(external_name_parts[:-1])   329                         print "* Importing", external_module_name   330                         obj = __import__(external_module_name, global_names, {}, [])   331                         global_names[external_name_parts[0]] = obj   332    333             # Finally, call __clinit__ methods for all relevant classes.   334    335             for cls, class_file in classes:   336                 print "**", cls, class_file   337                 if hasattr(cls, "__clinit__"):   338                     eval(cls.__clinit__.func_code, global_names)   339    340         return module   341    342 ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()   343    344 # vim: tabstop=4 expandtab shiftwidth=4