# HG changeset patch # User Paul Boddie # Date 1108254944 -3600 # Node ID fed5a5ceb0e66f95ab6c119bf75e1f7597807766 # Parent 36fc18633fec34cd16728e3c9d7c5bb09ebb34ea Introduced initial measures for handling circular imports/references. diff -r 36fc18633fec -r fed5a5ceb0e6 README.txt --- a/README.txt Sun Feb 13 01:33:26 2005 +0100 +++ b/README.txt Sun Feb 13 01:35:44 2005 +0100 @@ -140,6 +140,14 @@ The test program crashes, fairly quickly under Python 2.4, too. There seems to be some kind of memory allocation problem. +Implement better importing mechanisms so that circular module dependencies +can be avoided. For example, when dealing with java.lang classes which +depend on java.security classes which then inherit from java.lang.Object, +the classes should all be imported but only initialised after no more +importing is necessary. Since usage of external names is recorded in the +bytecode translation process, it should be possible to reach such a +condition definitively. + Investigate better exception raising. Currently, exceptions have to be derived from object so that object.__new__ can be used upon them. However, this seems to prevent them from being raised, and they need to be wrapped diff -r 36fc18633fec -r fed5a5ceb0e6 javaclass/bytecode.py --- a/javaclass/bytecode.py Sun Feb 13 01:33:26 2005 +0100 +++ b/javaclass/bytecode.py Sun Feb 13 01:35:44 2005 +0100 @@ -2041,13 +2041,13 @@ translator.process(method, writer) return translator, writer - def make_method(self, real_method_name, methods, global_names, namespace): + def make_method(self, real_method_name, methods, global_names): """ Make a dispatcher method with the given 'real_method_name', providing dispatch to the supplied type-sensitive 'methods', accessing the given 'global_names' where necessary, and storing the new method in the - 'namespace' provided. + class's namespace. """ if real_method_name == "": @@ -2059,7 +2059,7 @@ if len(methods) == 1: method, fn = methods[0] - namespace[method_name] = fn + self.namespace[method_name] = fn return # Write a simple bytecode dispatching mechanism. @@ -2224,28 +2224,27 @@ if method_is_static: fn = staticmethod(fn) - namespace[method_name] = fn + self.namespace[method_name] = fn def process(self, global_names): """ Process the class, storing it in the 'global_names' dictionary provided. - Return a tuple containing the class and a list of external names - referenced by the class's methods. + Return a list of external names referenced by the class's methods. """ - namespace = {} + self.namespace = {} # Make the fields. for field in self.class_file.fields: if classfile.has_flags(field.access_flags, [classfile.STATIC]): field_name = str(field.get_python_name()) - namespace[field_name] = None + self.namespace[field_name] = None # Make the methods. - real_methods = {} + self.real_methods = {} external_names = [] for method in self.class_file.methods: @@ -2287,13 +2286,27 @@ # Remember the real method name and the corresponding methods produced. - if not real_methods.has_key(real_method_name): - real_methods[real_method_name] = [] - real_methods[real_method_name].append((method, fn)) + if not self.real_methods.has_key(real_method_name): + self.real_methods[real_method_name] = [] + self.real_methods[real_method_name].append((method, fn)) # Add the method to the class's namespace. - namespace[method_name] = fn + self.namespace[method_name] = fn + + # Add the super class as an external name, if appropriate. + + if self.class_file.super_class is not None: + external_names.append(self.class_file.super_class.get_python_name()) + + return external_names + + def get_class(self, global_names): + + """ + Get the Python class representing the underlying Java class, using the + given 'global_names' to define dispatcher methods. + """ # Define superclasses. @@ -2301,18 +2314,18 @@ # Define method dispatchers. - for real_method_name, methods in real_methods.items(): + for real_method_name, methods in self.real_methods.items(): if real_method_name != "": - self.make_method(real_method_name, methods, global_names, namespace) + self.make_method(real_method_name, methods, global_names) # Use only the last part of the fully qualified name. full_class_name = str(self.class_file.this_class.get_python_name()) class_name = full_class_name.split(".")[-1] - cls = new.classobj(class_name, bases, namespace) + cls = new.classobj(class_name, bases, self.namespace) global_names[cls.__name__] = cls - return cls, external_names + return cls def get_base_classes(self, global_names): @@ -2323,27 +2336,20 @@ tuple). """ - original_name = str(self.class_file.super_class.get_name()) - full_this_class_name = str(self.class_file.this_class.get_python_name()) - this_class_name_parts = full_this_class_name.split(".") - this_class_module_name = ".".join(this_class_name_parts[:-1]) - full_super_class_name = str(self.class_file.super_class.get_python_name()) - super_class_name_parts = full_super_class_name.split(".") - super_class_name = super_class_name_parts[-1] - super_class_module_name = ".".join(super_class_name_parts[:-1]) - if super_class_module_name == "": - obj = global_names[super_class_name] - elif super_class_module_name == this_class_module_name: - obj = global_names[super_class_name] - else: - #print "Importing", super_class_module_name, super_class_name - obj = __import__(super_class_module_name, global_names, {}, []) - for super_class_name_part in super_class_name_parts[1:] or [super_class_name]: - #print "*", obj, super_class_name_part - try: - obj = getattr(obj, super_class_name_part) - except AttributeError: - raise AttributeError, "Cannot find class '%s' in Java package '%s'" % (super_class_name_part, super_class_module_name) + super_class = self.class_file.super_class + if super_class is None: + return () + + super_class_name = super_class.get_python_name() + super_class_name_parts = super_class_name.split(".") + obj = global_names + for super_class_name_part in super_class_name_parts[:-1]: + try: + obj = obj[super_class_name_part].__dict__ + except KeyError: + raise AttributeError, "Cannot find '%s' when referencing Java class '%s'" % ( + super_class_name_part, super_class_name) + obj = obj[super_class_name_parts[-1]] return (obj,) def make_varnames(self, nlocals, method_is_static=0): @@ -2378,6 +2384,7 @@ if __name__ == "__main__": import sys import dis + import java.lang global_names = globals() #global_names["isinstance"] = _isinstance #global_names["map"] = _map @@ -2385,6 +2392,7 @@ f = open(filename, "rb") c = classfile.ClassFile(f.read()) translator = ClassTranslator(c) - cls, external_names = translator.process(global_names) + external_names = translator.process(global_names) + cls = translator.get_class(global_names) # vim: tabstop=4 expandtab shiftwidth=4 diff -r 36fc18633fec -r fed5a5ceb0e6 javaclass/classhook.py --- a/javaclass/classhook.py Sun Feb 13 01:33:26 2005 +0100 +++ b/javaclass/classhook.py Sun Feb 13 01:35:44 2005 +0100 @@ -5,6 +5,7 @@ from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic import classfile, bytecode # Java class support import zipfile # for Java archive inspection +import sys # NOTE: Arbitrary constants pulled from thin air. @@ -236,14 +237,52 @@ find_module method produces such a list. """ + loaded_module_names = [] + loaded_classes = {} + main_module = self._load_module(name, stuff, loaded_module_names, loaded_classes) + + # Initialise the loaded classes. + + for module, classes in loaded_classes.items(): + self._init_classes(module, classes) + + return main_module + + def _filter_names(self, module_names, loaded_module_names): + for module_name in loaded_module_names: + try: + i = module_names.index(module_name) + del module_names[i] + except ValueError: + pass + + def _load_module(self, name, stuff, loaded_module_names, loaded_classes): + #print "_load_module", name, loaded_module_names + loaded_module_names.append(name) + + # Detect non-Java modules. + + for stuff_item in stuff: + archive, filename, info = stuff_item + suffix, mode, datatype = info + if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE): + return ihooks.ModuleLoader.load_module(self, name, stuff_item) + # Set up the module. # A union of all locations is placed in the module's path. + external_names = [] module = self.hooks.add_module(name) module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff] + # Prepare a dictionary of globals. + + global_names = module.__dict__ + global_names["__builtins__"] = __builtins__ + # Just go into each package and find the class files. + classes = {} for stuff_item in stuff: # Extract the details, delegating loading responsibility to the @@ -253,16 +292,8 @@ archive, filename, info = stuff_item suffix, mode, datatype = info - if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE): - return ihooks.ModuleLoader.load_module(self, name, stuff_item) - #print "Loading", archive, filename, info - # Prepare a dictionary of globals. - - global_names = module.__dict__ - global_names["__builtins__"] = __builtins__ - # Get the real filename. filename = self._get_path_in_archive(filename) @@ -270,115 +301,111 @@ # Load the class files. - class_files = {} for class_filename in self.hooks.matching(filename, os.extsep + "class", archive): #print "Loading class", class_filename s = self.hooks.read(class_filename, archive) class_file = classfile.ClassFile(s) - class_files[str(class_file.this_class.get_name())] = class_file - - # Get an index of the class files. - - class_file_index = class_files.keys() - - # NOTE: Unnecessary sorting for test purposes. - - class_file_index.sort() - - # Now go through the classes arranging them in a safe loading order. - - position = 0 - while position < len(class_file_index): - class_name = class_file_index[position] - super_class_name = str(class_files[class_name].super_class.get_name()) - - # Discover whether the superclass appears later. - - try: - super_class_position = class_file_index.index(super_class_name) - if super_class_position > position: + translator = bytecode.ClassTranslator(class_file) + classes[str(class_file.this_class.get_name())] = translator + external_names += translator.process(global_names) - # If the superclass appears later, swap this class and the - # superclass, then process the superclass. - - class_file_index[position] = super_class_name - class_file_index[super_class_position] = class_name - continue - - except ValueError: - pass - - position += 1 + # Record the classes found under the current module. - # Process each class file, producing a genuine Python class. - # Create the classes, but establish a proper initialisation order. - - class_file_init_index = [] - class_file_init = {} + loaded_classes[module] = classes - for class_name in class_file_index: - #print "* Class", class_name - class_file = class_files[class_name] - translator = bytecode.ClassTranslator(class_file) - cls, external_names = translator.process(global_names) - module.__dict__[cls.__name__] = cls - - # Process external names. + # Return modules used by external names. - this_class_name_parts = class_file.this_class.get_python_name().split(".") - this_class_module, this_class_name = this_class_name_parts[:-1], this_class_name_parts[-1] - - for external_name in external_names: - #print "* Name", external_name - external_name_parts = external_name.split(".") - external_class_module, external_class_name = external_name_parts[:-1], external_name_parts[-1] - - # Names not local to this package need importing. - - if len(external_name_parts) > 1 and this_class_module != external_class_module: + external_module_names = self._get_external_module_names(external_names) - external_module_name = ".".join(external_class_module) - #print "* Importing", external_module_name - obj = __import__(external_module_name, global_names, {}, []) - global_names[external_name_parts[0]] = obj - - # Names local to this package may affect initialisation order. - - elif external_class_name not in class_file_init_index: - try: - this_class_name_index = class_file_init_index.index(this_class_name) - - # Either insert this name before the current class's - # name. + # Repeatedly load classes from referenced modules. - #print "* Inserting", external_class_name - class_file_init_index.insert(this_class_name_index, external_class_name) - - except ValueError: - - # Or add this name in anticipation of the current - # class's name appearing. - - #print "* Including", external_class_name - class_file_init_index.append(external_class_name) - - # Add this class name to the initialisation index. + self._filter_names(external_module_names, loaded_module_names) + for module_name in external_module_names: + if module_name not in loaded_module_names: - if class_name not in class_file_init_index: - class_file_init_index.append(this_class_name) - class_file_init[this_class_name] = (cls, class_file) - - # Finally, call __clinit__ methods for all relevant classes. + # Emulate the __import__ function, loading the requested module + # but returning the top-level module. - #print "** Initialisation order", class_file_init_index - for class_name in class_file_init_index: - cls, class_file = class_file_init[class_name] - #print "**", cls, class_file - if hasattr(cls, "__clinit__"): - eval(cls.__clinit__.func_code, global_names) + self._import(module_name, global_names, loaded_module_names, loaded_classes) return module + def _import(self, module_name, parent, loaded_module_names, loaded_classes): + + # Where no Java-based submodules can be found, look for + # Python modules instead. + + new_stuff = self.find_module(module_name) + #print "_", new_stuff + if not new_stuff: + new_module = __import__(module_name, parent) + #print "P", new_module + parent[module_name.split(".")[0]] = new_module + return new_module + + module_name_parts = module_name.split(".") + path = [] + for module_name_part in module_name_parts: + path.append(module_name_part) + path_str = ".".join(path) + if self.modules_dict().has_key(path_str): + + # Add submodules to existing modules. + + new_module = self.modules_dict()[path_str] + parent = new_module.__dict__ + #print "-", path_str + + else: + + # Find submodules. + + new_stuff = self.find_module(path_str) + new_module = self._load_module(path_str, new_stuff, loaded_module_names, loaded_classes) + #print "J", new_module + #print "+", path_str, new_module + parent[module_name_part] = new_module + parent = new_module.__dict__ + + #print "->", new_module.__dict__.keys() + return new_module + + def _get_external_module_names(self, names): + groups = self._get_names_grouped_by_module(names) + if groups.has_key(""): + del groups[""] + return groups.keys() + + def _get_names_grouped_by_module(self, names): + groups = {} + for name in names: + module_name, class_name = self._get_module_and_class_names(name) + if not groups.has_key(module_name): + groups[module_name] = [] + groups[module_name].append(class_name) + return groups + + def _get_module_and_class_names(self, full_name): + full_name_parts = full_name.split(".") + class_name = full_name_parts[-1] + module_name = ".".join(full_name_parts[:-1]) + return module_name, class_name + + def _init_classes(self, module, classes): + global_names = module.__dict__ + + # First, create the classes. + + real_classes = [] + for name, translator in classes.items(): + real_classes.append(translator.get_class(global_names)) + + # Finally, call __clinit__ methods for all relevant classes. + + for cls in real_classes: + if hasattr(cls, "__clinit__"): + eval(cls.__clinit__.func_code, global_names) + ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install() # vim: tabstop=4 expandtab shiftwidth=4