javaclass

javaclass/classhook.py

178:3b3b9f5e2793
2005-02-21 Paul Boddie Attempted to add more sophisticated circular import handling.
     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 import sys     9     10 # NOTE: Arbitrary constants pulled from thin air.    11     12 JAVA_PACKAGE = 20041113    13 JAVA_CLASS = 20041114    14 JAVA_ARCHIVE = 20041115    15     16 class ClassHooks(ihooks.Hooks):    17     18     "A filesystem hooks class providing information about supported files."    19     20     def get_suffixes(self):    21     22         "Return the recognised suffixes."    23     24         return [("", "", JAVA_PACKAGE), (os.extsep + "jar", "r", JAVA_ARCHIVE)] + ihooks.Hooks.get_suffixes(self)    25     26     def path_isdir(self, x, archive=None):    27     28         "Return whether 'x' is a directory in the given 'archive'."    29     30         if archive is None:    31             return ihooks.Hooks.path_isdir(self, x)    32     33         return self._get_dirname(x) in archive.namelist()    34     35     def _get_dirname(self, x):    36     37         """    38         Return the directory name for 'x'.    39         In zip files, the presence of "/" seems to indicate a directory.    40         """    41     42         if x.endswith("/"):    43             return x    44         else:    45             return x + "/"    46     47     def listdir(self, x, archive=None):    48     49         "Return the contents of the directory 'x' in the given 'archive'."    50     51         if archive is None:    52             return ihooks.Hooks.listdir(self, x)    53     54         x = self._get_dirname(x)    55         l = []    56         for path in archive.namelist():    57     58             # Find out if the path is within the given directory.    59     60             if path != x and path.startswith(x):    61     62                 # Get the path below the given directory.    63     64                 subpath = path[len(x):]    65     66                 # Find out whether the path is an object in the current directory.    67     68                 if subpath.count("/") == 0 or subpath.count("/") == 1 and subpath.endswith("/"):    69                     l.append(subpath)    70     71         return l    72     73     def matching(self, dir, extension, archive=None):    74     75         """    76         Return the matching files in the given directory 'dir' having the given    77         'extension' within the given 'archive'. Produce a list containing full    78         paths as opposed to simple filenames.    79         """    80     81         if archive is None:    82             return glob.glob(self.path_join(dir, "*" + extension))    83     84         dir = self._get_dirname(dir)    85         l = []    86         for path in self.listdir(dir, archive):    87             if path.endswith(extension):    88                 l.append(self.path_join(dir, path))    89         return l    90     91     def read(self, filename, archive=None):    92     93         """    94         Return the contents of the file with the given 'filename' in the given    95         'archive'.    96         """    97     98         if archive is None:    99             f = open(filename, "rb")   100             s = f.read()   101             f.close()   102             return s   103         return archive.read(filename)   104    105 class ClassLoader(ihooks.ModuleLoader):   106    107     "A class providing support for searching directories for supported files."   108    109     def find_module(self, name, path=None):   110    111         """   112         Find the module with the given 'name', using the given 'path' to locate   113         it. Note that ModuleLoader.find_module is almost sufficient, but does   114         not provide enough support for "package unions" where the root of a   115         package hierarchy may appear in several places.   116    117         Return a list of locations (each being the "stuff" data structure used   118         by load_module); this replaces the single "stuff" value or None returned   119         by ModuleLoader.find_module.   120         """   121    122         if path is None:   123             path = [None] + self.default_path()   124    125         found_locations = []   126    127         for dir in path:   128             stuff = self.find_module_in_dir(name, dir)   129             if stuff:   130                 found_locations.append(stuff)   131    132         return found_locations   133    134     def find_module_in_dir(self, name, dir, allow_packages=1):   135    136         """   137         Find the module with the given 'name' in the given directory 'dir'.   138         Since Java packages/modules are directories containing class files,   139         return the required information tuple only when the path constructed   140         from 'dir' and 'name' refers to a directory containing class files.   141         """   142    143         result = ihooks.ModuleLoader.find_module_in_dir(self, name, dir, allow_packages)   144         if result is not None:   145             return result   146    147         # An archive may be opened.   148    149         archive = None   150    151         # Provide a special name for the current directory.   152    153         if name == "__this__":   154             if dir == None:   155                 return (None, ".", ("", "", JAVA_PACKAGE))   156             else:   157                 return None   158    159         # Where no directory is given, return failure immediately.   160    161         elif dir is None:   162             return None   163    164         # Detect archives.   165    166         else:   167             archive, archive_path, path = self._get_archive_and_path(dir, name)   168    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    207             # Look for classes in the directory.   208    209             if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:   210                 return 1   211    212             # Otherwise permit importing where directories containing classes exist.   213    214             for filename in self.hooks.listdir(path, archive):   215                 pathname = self.hooks.path_join(path, filename)   216                 result = self._find_module_at_path(pathname, archive)   217                 if result is not None:   218                     return result   219    220         return 0   221    222     def load_module(self, name, stuff):   223    224         """   225         Load the module with the given 'name', with a list of 'stuff' items,   226         each of which describes the location of the module and is a tuple of the   227         form (file, filename, (suffix, mode, data type)).   228    229         Return a module object or raise an ImportError if a problem occurred in   230         the import operation.   231    232         Note that the 'stuff' parameter is a list and not a single item as in   233         ModuleLoader.load_module. This should still work, however, since the   234         find_module method produces such a list.   235         """   236    237         #print "load_module", name   238         module = self._not_java_module(name, stuff)   239         if module is not None:   240             return module   241    242         if not hasattr(self, "loaded_classes"):   243             self.loaded_classes = {}   244             top_level = 1   245         else:   246             top_level = 0   247    248         main_module = self._load_module(name, stuff)   249    250         # Initialise the loaded classes.   251    252         if top_level:   253             self._init_classes()   254             delattr(self, "loaded_classes")   255    256         return main_module   257    258     def _not_java_module(self, name, stuff):   259    260         "Detect non-Java modules."   261    262         for stuff_item in stuff:   263             archive, filename, info = stuff_item   264             suffix, mode, datatype = info   265             if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):   266                 return ihooks.ModuleLoader.load_module(self, name, stuff_item)   267    268         return None   269    270     def _load_module(self, name, stuff):   271    272         # Set up the module.   273         # A union of all locations is placed in the module's path.   274    275         external_names = []   276         module = self.hooks.add_module(name)   277         module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]   278    279         # Prepare a dictionary of globals.   280    281         global_names = module.__dict__   282         global_names["__builtins__"] = __builtins__   283    284         # Just go into each package and find the class files.   285    286         classes = {}   287         for stuff_item in stuff:   288    289             # Extract the details, delegating loading responsibility to the   290             # default loader where appropriate.   291             # NOTE: Should we not be using some saved loader remembered upon   292             # NOTE: installation?   293    294             archive, filename, info = stuff_item   295             suffix, mode, datatype = info   296    297             # Get the real filename.   298    299             filename = self._get_path_in_archive(filename)   300    301             # Load the class files.   302    303             for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):   304                 s = self.hooks.read(class_filename, archive)   305                 class_file = classfile.ClassFile(s)   306                 #print "Translating", str(class_file.this_class.get_name())   307                 translator = bytecode.ClassTranslator(class_file)   308                 external_names += translator.process(global_names)   309    310                 # Record the classes found under the current module.   311    312                 self.loaded_classes[str(class_file.this_class.get_name())] = module, translator   313    314         # Return modules used by external names.   315    316         external_module_names = self._get_external_module_names(external_names, name)   317    318         # Repeatedly load classes from referenced modules.   319    320         for module_name in external_module_names:   321             new_module = __import__(module_name, global_names)   322             global_names[module_name.split(".")[0]] = new_module   323    324         return module   325    326     def _get_external_module_names(self, names, current_module_name):   327         groups = self._get_names_grouped_by_module(names)   328         if groups.has_key(""):   329             del groups[""]   330    331         # NOTE: Could filter out the current module and all parent modules.   332         # NOTE:   333         # NOTE: current_module_parts = current_module_name.split(".")   334         # NOTE: while len(current_module_parts) > 0:   335         # NOTE:     try:   336         # NOTE:         del groups[".".join(current_module_parts)]   337         # NOTE:     except KeyError:   338         # NOTE:         pass   339         # NOTE:     del current_module_parts[-1]   340    341         try:   342             del groups[".".join(current_module_name)]   343         except KeyError:   344             pass   345    346         return groups.keys()   347    348     def _get_names_grouped_by_module(self, names):   349         groups = {}   350         for name in names:   351             module_name, class_name = self._get_module_and_class_names(name)   352             if not groups.has_key(module_name):   353                 groups[module_name] = []   354             groups[module_name].append(class_name)   355         return groups   356    357     def _get_module_and_class_names(self, full_name):   358         full_name_parts = full_name.split(".")   359         class_name = full_name_parts[-1]   360         module_name = ".".join(full_name_parts[:-1])   361         return module_name, class_name   362    363     def _init_classes(self):   364    365         # Order the classes according to inheritance.   366    367         init_order = []   368         for class_name, (module, translator) in self.loaded_classes.items():   369    370             # Insert the base classes before any mention of the current class.   371    372             for base_class in translator.get_base_class_references():   373                 base_class_name = str(base_class.get_name())   374                 if base_class_name not in init_order:   375                     if class_name not in init_order:   376                         init_order.append(base_class_name)   377                     else:   378                         index = init_order.index(class_name)   379                         init_order.insert(index, base_class_name)   380    381             if class_name not in init_order:   382                 init_order.append(class_name)   383    384         # Create the classes.   385    386         real_classes = {}   387         real_classes_index = []   388         for class_name in init_order:   389             try:   390                 module, translator = self.loaded_classes[class_name]   391                 global_names = module.__dict__   392                 if not real_classes.has_key(module):   393                     real_classes[module] = []   394                 real_class = translator.get_class(global_names, real_classes)   395                 real_classes[class_name].append(real_class)   396                 real_classes_index.append((module, real_class))   397             except KeyError:   398                 # NOTE: Should be a non-Java class.   399                 pass   400    401         # Finally, call __clinit__ methods for all relevant classes.   402    403         for module, cls in real_classes_index:   404             if hasattr(cls, "__clinit__"):   405                 global_names = module.__dict__   406                 eval(cls.__clinit__.func_code, global_names)   407    408 ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()   409    410 # vim: tabstop=4 expandtab shiftwidth=4