javaclass

classhook.py

119:ae2484e54322
2005-01-13 Paul Boddie Added a test of main methods.
     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             if dir == None:   154                 return (None, ".", ("", "", JAVA_PACKAGE))   155             else:   156                 return None   157    158         # Where no directory is given, return failure immediately.   159    160         elif dir is None:   161             return None   162    163         # Detect archives.   164    165         else:   166             archive, archive_path, path = self._get_archive_and_path(dir, name)   167    168         #print "Processing name", name, "in", dir, "producing", path, "within archive", archive   169    170         if self._find_module_at_path(path, archive):   171             if archive is not None:   172                 return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE))   173             else:   174                 return (None, path, ("", "", JAVA_PACKAGE))   175         else:   176             return None   177    178     def _get_archive_and_path(self, dir, name):   179         parts = dir.split(":")   180         archive_path = parts[0]   181    182         # Archives may include an internal path, but will in any case have   183         # a primary part ending in .jar.   184    185         if archive_path.endswith(os.extsep + "jar"):   186             archive = zipfile.ZipFile(archive_path, "r")   187             path = self.hooks.path_join(":".join(parts[1:]), name)   188    189         # Otherwise, produce a filesystem-based path.   190    191         else:   192             archive = None   193             path = self.hooks.path_join(dir, name)   194    195         return archive, archive_path, path   196    197     def _get_path_in_archive(self, path):   198         parts = path.split(":")   199         if len(parts) == 1:   200             return parts[0]   201         else:   202             return ":".join(parts[1:])   203    204     def _find_module_at_path(self, path, archive):   205         if self.hooks.path_isdir(path, archive):   206             #print "Looking in", path, "using archive", archive   207    208             # Look for classes in the directory.   209    210             if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:   211                 return 1   212    213             # Otherwise permit importing where directories containing classes exist.   214    215             #print "Filenames are", self.hooks.listdir(path, archive)   216             for filename in self.hooks.listdir(path, archive):   217                 pathname = self.hooks.path_join(path, filename)   218                 result = self._find_module_at_path(pathname, archive)   219                 if result is not None:   220                     return result   221    222         return 0   223    224     def load_module(self, name, stuff):   225    226         """   227         Load the module with the given 'name', with a list of 'stuff' items,   228         each of which describes the location of the module and is a tuple of the   229         form (file, filename, (suffix, mode, data type)).   230    231         Return a module object or raise an ImportError if a problem occurred in   232         the import operation.   233    234         Note that the 'stuff' parameter is a list and not a single item as in   235         ModuleLoader.load_module. This should still work, however, since the   236         find_module method produces such a list.   237         """   238    239         # Set up the module.   240         # A union of all locations is placed in the module's path.   241    242         module = self.hooks.add_module(name)   243         module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]   244    245         # Just go into each package and find the class files.   246    247         for stuff_item in stuff:   248    249             # Extract the details, delegating loading responsibility to the   250             # default loader where appropriate.   251             # NOTE: Should we not be using some saved loader remembered upon   252             # NOTE: installation?   253    254             archive, filename, info = stuff_item   255             suffix, mode, datatype = info   256             if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):   257                 return ihooks.ModuleLoader.load_module(self, name, stuff_item)   258    259             #print "Loading", archive, filename, info   260    261             # Prepare a dictionary of globals.   262    263             global_names = module.__dict__   264             global_names["__builtins__"] = __builtins__   265    266             # Get the real filename.   267    268             filename = self._get_path_in_archive(filename)   269             #print "Real filename", filename   270    271             # Load the class files.   272    273             class_files = {}   274             for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):   275                 #print "Loading class", class_filename   276                 s = self.hooks.read(class_filename, archive)   277                 class_file = classfile.ClassFile(s)   278                 class_files[str(class_file.this_class.get_name())] = class_file   279    280             # Get an index of the class files.   281    282             class_file_index = class_files.keys()   283    284             # NOTE: Unnecessary sorting for test purposes.   285    286             class_file_index.sort()   287    288             # Now go through the classes arranging them in a safe loading order.   289    290             position = 0   291             while position < len(class_file_index):   292                 class_name = class_file_index[position]   293                 super_class_name = str(class_files[class_name].super_class.get_name())   294    295                 # Discover whether the superclass appears later.   296    297                 try:   298                     super_class_position = class_file_index.index(super_class_name)   299                     if super_class_position > position:   300    301                         # If the superclass appears later, swap this class and the   302                         # superclass, then process the superclass.   303    304                         class_file_index[position] = super_class_name   305                         class_file_index[super_class_position] = class_name   306                         continue   307    308                 except ValueError:   309                     pass   310    311                 position += 1   312    313             # Process each class file, producing a genuine Python class.   314             # Create the classes, but establish a proper initialisation order.   315    316             class_file_init_index = []   317             class_file_init = {}   318    319             for class_name in class_file_index:   320                 #print "* Class", class_name   321                 class_file = class_files[class_name]   322                 translator = bytecode.ClassTranslator(class_file)   323                 cls, external_names = translator.process(global_names)   324                 module.__dict__[cls.__name__] = cls   325    326                 # Process external names.   327    328                 this_class_name_parts = class_file.this_class.get_python_name().split(".")   329                 this_class_module, this_class_name = this_class_name_parts[:-1], this_class_name_parts[-1]   330    331                 for external_name in external_names:   332                     #print "* Name", external_name   333                     external_name_parts = external_name.split(".")   334                     external_class_module, external_class_name = external_name_parts[:-1], external_name_parts[-1]   335    336                     # Names not local to this package need importing.   337    338                     if len(external_name_parts) > 1 and this_class_module != external_class_module:   339    340                         external_module_name = ".".join(external_class_module)   341                         #print "* Importing", external_module_name   342                         obj = __import__(external_module_name, global_names, {}, [])   343                         global_names[external_name_parts[0]] = obj   344    345                     # Names local to this package may affect initialisation order.   346    347                     elif external_class_name not in class_file_init_index:   348                         try:   349                             this_class_name_index = class_file_init_index.index(this_class_name)   350    351                             # Either insert this name before the current class's   352                             # name.   353    354                             #print "* Inserting", external_class_name   355                             class_file_init_index.insert(this_class_name_index, external_class_name)   356    357                         except ValueError:   358    359                             # Or add this name in anticipation of the current   360                             # class's name appearing.   361    362                             #print "* Including", external_class_name   363                             class_file_init_index.append(external_class_name)   364    365                 # Add this class name to the initialisation index.   366    367                 if class_name not in class_file_init_index:   368                     class_file_init_index.append(this_class_name)   369                 class_file_init[this_class_name] = (cls, class_file)   370    371             # Finally, call __clinit__ methods for all relevant classes.   372    373             #print "** Initialisation order", class_file_init_index   374             for class_name in class_file_init_index:   375                 cls, class_file = class_file_init[class_name]   376                 #print "**", cls, class_file   377                 if hasattr(cls, "__clinit__"):   378                     eval(cls.__clinit__.func_code, global_names)   379    380         return module   381    382 ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()   383    384 # vim: tabstop=4 expandtab shiftwidth=4