javaclass

Changeset

166:fed5a5ceb0e6
2005-02-13 Paul Boddie raw files shortlog changelog graph Introduced initial measures for handling circular imports/references.
README.txt (file) javaclass/bytecode.py (file) javaclass/classhook.py (file)
     1.1 --- a/README.txt	Sun Feb 13 01:33:26 2005 +0100
     1.2 +++ b/README.txt	Sun Feb 13 01:35:44 2005 +0100
     1.3 @@ -140,6 +140,14 @@
     1.4  The test program crashes, fairly quickly under Python 2.4, too. There seems
     1.5  to be some kind of memory allocation problem.
     1.6  
     1.7 +Implement better importing mechanisms so that circular module dependencies
     1.8 +can be avoided. For example, when dealing with java.lang classes which
     1.9 +depend on java.security classes which then inherit from java.lang.Object,
    1.10 +the classes should all be imported but only initialised after no more
    1.11 +importing is necessary. Since usage of external names is recorded in the
    1.12 +bytecode translation process, it should be possible to reach such a
    1.13 +condition definitively.
    1.14 +
    1.15  Investigate better exception raising. Currently, exceptions have to be
    1.16  derived from object so that object.__new__ can be used upon them. However,
    1.17  this seems to prevent them from being raised, and they need to be wrapped
     2.1 --- a/javaclass/bytecode.py	Sun Feb 13 01:33:26 2005 +0100
     2.2 +++ b/javaclass/bytecode.py	Sun Feb 13 01:35:44 2005 +0100
     2.3 @@ -2041,13 +2041,13 @@
     2.4          translator.process(method, writer)
     2.5          return translator, writer
     2.6  
     2.7 -    def make_method(self, real_method_name, methods, global_names, namespace):
     2.8 +    def make_method(self, real_method_name, methods, global_names):
     2.9  
    2.10          """
    2.11          Make a dispatcher method with the given 'real_method_name', providing
    2.12          dispatch to the supplied type-sensitive 'methods', accessing the given
    2.13          'global_names' where necessary, and storing the new method in the
    2.14 -        'namespace' provided.
    2.15 +        class's namespace.
    2.16          """
    2.17  
    2.18          if real_method_name == "<init>":
    2.19 @@ -2059,7 +2059,7 @@
    2.20  
    2.21          if len(methods) == 1:
    2.22              method, fn = methods[0]
    2.23 -            namespace[method_name] = fn
    2.24 +            self.namespace[method_name] = fn
    2.25              return
    2.26  
    2.27          # Write a simple bytecode dispatching mechanism.
    2.28 @@ -2224,28 +2224,27 @@
    2.29          if method_is_static:
    2.30              fn = staticmethod(fn)
    2.31  
    2.32 -        namespace[method_name] = fn
    2.33 +        self.namespace[method_name] = fn
    2.34  
    2.35      def process(self, global_names):
    2.36  
    2.37          """
    2.38          Process the class, storing it in the 'global_names' dictionary provided.
    2.39 -        Return a tuple containing the class and a list of external names
    2.40 -        referenced by the class's methods.
    2.41 +        Return a list of external names referenced by the class's methods.
    2.42          """
    2.43  
    2.44 -        namespace = {}
    2.45 +        self.namespace = {}
    2.46  
    2.47          # Make the fields.
    2.48  
    2.49          for field in self.class_file.fields:
    2.50              if classfile.has_flags(field.access_flags, [classfile.STATIC]):
    2.51                  field_name = str(field.get_python_name())
    2.52 -                namespace[field_name] = None
    2.53 +                self.namespace[field_name] = None
    2.54  
    2.55          # Make the methods.
    2.56  
    2.57 -        real_methods = {}
    2.58 +        self.real_methods = {}
    2.59          external_names = []
    2.60  
    2.61          for method in self.class_file.methods:
    2.62 @@ -2287,13 +2286,27 @@
    2.63  
    2.64              # Remember the real method name and the corresponding methods produced.
    2.65  
    2.66 -            if not real_methods.has_key(real_method_name):
    2.67 -                real_methods[real_method_name] = []
    2.68 -            real_methods[real_method_name].append((method, fn))
    2.69 +            if not self.real_methods.has_key(real_method_name):
    2.70 +                self.real_methods[real_method_name] = []
    2.71 +            self.real_methods[real_method_name].append((method, fn))
    2.72  
    2.73              # Add the method to the class's namespace.
    2.74  
    2.75 -            namespace[method_name] = fn
    2.76 +            self.namespace[method_name] = fn
    2.77 +
    2.78 +        # Add the super class as an external name, if appropriate.
    2.79 +
    2.80 +        if self.class_file.super_class is not None:
    2.81 +            external_names.append(self.class_file.super_class.get_python_name())
    2.82 +
    2.83 +        return external_names
    2.84 +
    2.85 +    def get_class(self, global_names):
    2.86 +
    2.87 +        """
    2.88 +        Get the Python class representing the underlying Java class, using the
    2.89 +        given 'global_names' to define dispatcher methods.
    2.90 +        """
    2.91  
    2.92          # Define superclasses.
    2.93  
    2.94 @@ -2301,18 +2314,18 @@
    2.95  
    2.96          # Define method dispatchers.
    2.97  
    2.98 -        for real_method_name, methods in real_methods.items():
    2.99 +        for real_method_name, methods in self.real_methods.items():
   2.100              if real_method_name != "<clinit>":
   2.101 -                self.make_method(real_method_name, methods, global_names, namespace)
   2.102 +                self.make_method(real_method_name, methods, global_names)
   2.103  
   2.104          # Use only the last part of the fully qualified name.
   2.105  
   2.106          full_class_name = str(self.class_file.this_class.get_python_name())
   2.107          class_name = full_class_name.split(".")[-1]
   2.108 -        cls = new.classobj(class_name, bases, namespace)
   2.109 +        cls = new.classobj(class_name, bases, self.namespace)
   2.110          global_names[cls.__name__] = cls
   2.111  
   2.112 -        return cls, external_names
   2.113 +        return cls
   2.114  
   2.115      def get_base_classes(self, global_names):
   2.116  
   2.117 @@ -2323,27 +2336,20 @@
   2.118          tuple).
   2.119          """
   2.120  
   2.121 -        original_name = str(self.class_file.super_class.get_name())
   2.122 -        full_this_class_name = str(self.class_file.this_class.get_python_name())
   2.123 -        this_class_name_parts = full_this_class_name.split(".")
   2.124 -        this_class_module_name = ".".join(this_class_name_parts[:-1])
   2.125 -        full_super_class_name = str(self.class_file.super_class.get_python_name())
   2.126 -        super_class_name_parts = full_super_class_name.split(".")
   2.127 -        super_class_name = super_class_name_parts[-1]
   2.128 -        super_class_module_name = ".".join(super_class_name_parts[:-1])
   2.129 -        if super_class_module_name == "":
   2.130 -            obj = global_names[super_class_name]
   2.131 -        elif super_class_module_name == this_class_module_name:
   2.132 -            obj = global_names[super_class_name]
   2.133 -        else:
   2.134 -            #print "Importing", super_class_module_name, super_class_name
   2.135 -            obj = __import__(super_class_module_name, global_names, {}, [])
   2.136 -            for super_class_name_part in super_class_name_parts[1:] or [super_class_name]:
   2.137 -                #print "*", obj, super_class_name_part
   2.138 -                try:
   2.139 -                    obj = getattr(obj, super_class_name_part)
   2.140 -                except AttributeError:
   2.141 -                    raise AttributeError, "Cannot find class '%s' in Java package '%s'" % (super_class_name_part, super_class_module_name)
   2.142 +        super_class = self.class_file.super_class
   2.143 +        if super_class is None:
   2.144 +            return ()
   2.145 +
   2.146 +        super_class_name = super_class.get_python_name()
   2.147 +        super_class_name_parts = super_class_name.split(".")
   2.148 +        obj = global_names
   2.149 +        for super_class_name_part in super_class_name_parts[:-1]:
   2.150 +            try:
   2.151 +                obj = obj[super_class_name_part].__dict__
   2.152 +            except KeyError:
   2.153 +                raise AttributeError, "Cannot find '%s' when referencing Java class '%s'" % (
   2.154 +                    super_class_name_part, super_class_name)
   2.155 +        obj = obj[super_class_name_parts[-1]]
   2.156          return (obj,)
   2.157  
   2.158      def make_varnames(self, nlocals, method_is_static=0):
   2.159 @@ -2378,6 +2384,7 @@
   2.160  if __name__ == "__main__":
   2.161      import sys
   2.162      import dis
   2.163 +    import java.lang
   2.164      global_names = globals()
   2.165      #global_names["isinstance"] = _isinstance
   2.166      #global_names["map"] = _map
   2.167 @@ -2385,6 +2392,7 @@
   2.168          f = open(filename, "rb")
   2.169          c = classfile.ClassFile(f.read())
   2.170          translator = ClassTranslator(c)
   2.171 -        cls, external_names = translator.process(global_names)
   2.172 +        external_names = translator.process(global_names)
   2.173 +        cls = translator.get_class(global_names)
   2.174  
   2.175  # vim: tabstop=4 expandtab shiftwidth=4
     3.1 --- a/javaclass/classhook.py	Sun Feb 13 01:33:26 2005 +0100
     3.2 +++ b/javaclass/classhook.py	Sun Feb 13 01:35:44 2005 +0100
     3.3 @@ -5,6 +5,7 @@
     3.4  from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic
     3.5  import classfile, bytecode # Java class support
     3.6  import zipfile # for Java archive inspection
     3.7 +import sys
     3.8  
     3.9  # NOTE: Arbitrary constants pulled from thin air.
    3.10  
    3.11 @@ -236,14 +237,52 @@
    3.12          find_module method produces such a list.
    3.13          """
    3.14  
    3.15 +        loaded_module_names = []
    3.16 +        loaded_classes = {}
    3.17 +        main_module = self._load_module(name, stuff, loaded_module_names, loaded_classes)
    3.18 +
    3.19 +        # Initialise the loaded classes.
    3.20 +
    3.21 +        for module, classes in loaded_classes.items():
    3.22 +            self._init_classes(module, classes)
    3.23 +
    3.24 +        return main_module
    3.25 +
    3.26 +    def _filter_names(self, module_names, loaded_module_names):
    3.27 +        for module_name in loaded_module_names:
    3.28 +            try:
    3.29 +                i = module_names.index(module_name)
    3.30 +                del module_names[i]
    3.31 +            except ValueError:
    3.32 +                pass
    3.33 +
    3.34 +    def _load_module(self, name, stuff, loaded_module_names, loaded_classes):
    3.35 +        #print "_load_module", name, loaded_module_names
    3.36 +        loaded_module_names.append(name)
    3.37 +
    3.38 +        # Detect non-Java modules.
    3.39 +
    3.40 +        for stuff_item in stuff:
    3.41 +            archive, filename, info = stuff_item
    3.42 +            suffix, mode, datatype = info
    3.43 +            if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):
    3.44 +                return ihooks.ModuleLoader.load_module(self, name, stuff_item)
    3.45 +
    3.46          # Set up the module.
    3.47          # A union of all locations is placed in the module's path.
    3.48  
    3.49 +        external_names = []
    3.50          module = self.hooks.add_module(name)
    3.51          module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]
    3.52  
    3.53 +        # Prepare a dictionary of globals.
    3.54 +
    3.55 +        global_names = module.__dict__
    3.56 +        global_names["__builtins__"] = __builtins__
    3.57 +
    3.58          # Just go into each package and find the class files.
    3.59  
    3.60 +        classes = {}
    3.61          for stuff_item in stuff:
    3.62  
    3.63              # Extract the details, delegating loading responsibility to the
    3.64 @@ -253,16 +292,8 @@
    3.65  
    3.66              archive, filename, info = stuff_item
    3.67              suffix, mode, datatype = info
    3.68 -            if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):
    3.69 -                return ihooks.ModuleLoader.load_module(self, name, stuff_item)
    3.70 -
    3.71              #print "Loading", archive, filename, info
    3.72  
    3.73 -            # Prepare a dictionary of globals.
    3.74 -
    3.75 -            global_names = module.__dict__
    3.76 -            global_names["__builtins__"] = __builtins__
    3.77 -
    3.78              # Get the real filename.
    3.79  
    3.80              filename = self._get_path_in_archive(filename)
    3.81 @@ -270,115 +301,111 @@
    3.82  
    3.83              # Load the class files.
    3.84  
    3.85 -            class_files = {}
    3.86              for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):
    3.87                  #print "Loading class", class_filename
    3.88                  s = self.hooks.read(class_filename, archive)
    3.89                  class_file = classfile.ClassFile(s)
    3.90 -                class_files[str(class_file.this_class.get_name())] = class_file
    3.91 -
    3.92 -            # Get an index of the class files.
    3.93 -
    3.94 -            class_file_index = class_files.keys()
    3.95 -
    3.96 -            # NOTE: Unnecessary sorting for test purposes.
    3.97 -
    3.98 -            class_file_index.sort()
    3.99 -
   3.100 -            # Now go through the classes arranging them in a safe loading order.
   3.101 -
   3.102 -            position = 0
   3.103 -            while position < len(class_file_index):
   3.104 -                class_name = class_file_index[position]
   3.105 -                super_class_name = str(class_files[class_name].super_class.get_name())
   3.106 -
   3.107 -                # Discover whether the superclass appears later.
   3.108 -
   3.109 -                try:
   3.110 -                    super_class_position = class_file_index.index(super_class_name)
   3.111 -                    if super_class_position > position:
   3.112 +                translator = bytecode.ClassTranslator(class_file)
   3.113 +                classes[str(class_file.this_class.get_name())] = translator
   3.114 +                external_names += translator.process(global_names)
   3.115  
   3.116 -                        # If the superclass appears later, swap this class and the
   3.117 -                        # superclass, then process the superclass.
   3.118 -
   3.119 -                        class_file_index[position] = super_class_name
   3.120 -                        class_file_index[super_class_position] = class_name
   3.121 -                        continue
   3.122 -
   3.123 -                except ValueError:
   3.124 -                    pass
   3.125 -
   3.126 -                position += 1
   3.127 +        # Record the classes found under the current module.
   3.128  
   3.129 -            # Process each class file, producing a genuine Python class.
   3.130 -            # Create the classes, but establish a proper initialisation order.
   3.131 -
   3.132 -            class_file_init_index = []
   3.133 -            class_file_init = {}
   3.134 +        loaded_classes[module] = classes
   3.135  
   3.136 -            for class_name in class_file_index:
   3.137 -                #print "* Class", class_name
   3.138 -                class_file = class_files[class_name]
   3.139 -                translator = bytecode.ClassTranslator(class_file)
   3.140 -                cls, external_names = translator.process(global_names)
   3.141 -                module.__dict__[cls.__name__] = cls
   3.142 -
   3.143 -                # Process external names.
   3.144 +        # Return modules used by external names.
   3.145  
   3.146 -                this_class_name_parts = class_file.this_class.get_python_name().split(".")
   3.147 -                this_class_module, this_class_name = this_class_name_parts[:-1], this_class_name_parts[-1]
   3.148 -
   3.149 -                for external_name in external_names:
   3.150 -                    #print "* Name", external_name
   3.151 -                    external_name_parts = external_name.split(".")
   3.152 -                    external_class_module, external_class_name = external_name_parts[:-1], external_name_parts[-1]
   3.153 -
   3.154 -                    # Names not local to this package need importing.
   3.155 -
   3.156 -                    if len(external_name_parts) > 1 and this_class_module != external_class_module:
   3.157 +        external_module_names = self._get_external_module_names(external_names)
   3.158  
   3.159 -                        external_module_name = ".".join(external_class_module)
   3.160 -                        #print "* Importing", external_module_name
   3.161 -                        obj = __import__(external_module_name, global_names, {}, [])
   3.162 -                        global_names[external_name_parts[0]] = obj
   3.163 -
   3.164 -                    # Names local to this package may affect initialisation order.
   3.165 -
   3.166 -                    elif external_class_name not in class_file_init_index:
   3.167 -                        try:
   3.168 -                            this_class_name_index = class_file_init_index.index(this_class_name)
   3.169 -
   3.170 -                            # Either insert this name before the current class's
   3.171 -                            # name.
   3.172 +        # Repeatedly load classes from referenced modules.
   3.173  
   3.174 -                            #print "* Inserting", external_class_name
   3.175 -                            class_file_init_index.insert(this_class_name_index, external_class_name)
   3.176 -
   3.177 -                        except ValueError:
   3.178 -
   3.179 -                            # Or add this name in anticipation of the current
   3.180 -                            # class's name appearing.
   3.181 -
   3.182 -                            #print "* Including", external_class_name
   3.183 -                            class_file_init_index.append(external_class_name)
   3.184 -
   3.185 -                # Add this class name to the initialisation index.
   3.186 +        self._filter_names(external_module_names, loaded_module_names)
   3.187 +        for module_name in external_module_names:
   3.188 +            if module_name not in loaded_module_names:
   3.189  
   3.190 -                if class_name not in class_file_init_index:
   3.191 -                    class_file_init_index.append(this_class_name)
   3.192 -                class_file_init[this_class_name] = (cls, class_file)
   3.193 -
   3.194 -            # Finally, call __clinit__ methods for all relevant classes.
   3.195 +                # Emulate the __import__ function, loading the requested module
   3.196 +                # but returning the top-level module.
   3.197  
   3.198 -            #print "** Initialisation order", class_file_init_index
   3.199 -            for class_name in class_file_init_index:
   3.200 -                cls, class_file = class_file_init[class_name]
   3.201 -                #print "**", cls, class_file
   3.202 -                if hasattr(cls, "__clinit__"):
   3.203 -                    eval(cls.__clinit__.func_code, global_names)
   3.204 +                self._import(module_name, global_names, loaded_module_names, loaded_classes)
   3.205  
   3.206          return module
   3.207  
   3.208 +    def _import(self, module_name, parent, loaded_module_names, loaded_classes):
   3.209 +
   3.210 +        # Where no Java-based submodules can be found, look for
   3.211 +        # Python modules instead.
   3.212 +
   3.213 +        new_stuff = self.find_module(module_name)
   3.214 +        #print "_", new_stuff
   3.215 +        if not new_stuff:
   3.216 +            new_module = __import__(module_name, parent)
   3.217 +            #print "P", new_module
   3.218 +            parent[module_name.split(".")[0]] = new_module
   3.219 +            return new_module
   3.220 +
   3.221 +        module_name_parts = module_name.split(".")
   3.222 +        path = []
   3.223 +        for module_name_part in module_name_parts:
   3.224 +            path.append(module_name_part)
   3.225 +            path_str = ".".join(path)
   3.226 +            if self.modules_dict().has_key(path_str):
   3.227 +
   3.228 +                # Add submodules to existing modules.
   3.229 +
   3.230 +                new_module = self.modules_dict()[path_str]
   3.231 +                parent = new_module.__dict__
   3.232 +                #print "-", path_str
   3.233 +
   3.234 +            else:
   3.235 +
   3.236 +                # Find submodules.
   3.237 +
   3.238 +                new_stuff = self.find_module(path_str)
   3.239 +                new_module = self._load_module(path_str, new_stuff, loaded_module_names, loaded_classes)
   3.240 +                #print "J", new_module
   3.241 +                #print "+", path_str, new_module
   3.242 +                parent[module_name_part] = new_module
   3.243 +                parent = new_module.__dict__
   3.244 +
   3.245 +        #print "->", new_module.__dict__.keys()
   3.246 +        return new_module
   3.247 +
   3.248 +    def _get_external_module_names(self, names):
   3.249 +        groups = self._get_names_grouped_by_module(names)
   3.250 +        if groups.has_key(""):
   3.251 +            del groups[""]
   3.252 +        return groups.keys()
   3.253 +
   3.254 +    def _get_names_grouped_by_module(self, names):
   3.255 +        groups = {}
   3.256 +        for name in names:
   3.257 +            module_name, class_name = self._get_module_and_class_names(name)
   3.258 +            if not groups.has_key(module_name):
   3.259 +                groups[module_name] = []
   3.260 +            groups[module_name].append(class_name)
   3.261 +        return groups
   3.262 +
   3.263 +    def _get_module_and_class_names(self, full_name):
   3.264 +        full_name_parts = full_name.split(".")
   3.265 +        class_name = full_name_parts[-1]
   3.266 +        module_name = ".".join(full_name_parts[:-1])
   3.267 +        return module_name, class_name
   3.268 +
   3.269 +    def _init_classes(self, module, classes):
   3.270 +        global_names = module.__dict__
   3.271 +
   3.272 +        # First, create the classes.
   3.273 +
   3.274 +        real_classes = []
   3.275 +        for name, translator in classes.items():
   3.276 +            real_classes.append(translator.get_class(global_names))
   3.277 +
   3.278 +        # Finally, call __clinit__ methods for all relevant classes.
   3.279 +
   3.280 +        for cls in real_classes:
   3.281 +            if hasattr(cls, "__clinit__"):
   3.282 +                eval(cls.__clinit__.func_code, global_names)
   3.283 +
   3.284  ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()
   3.285  
   3.286  # vim: tabstop=4 expandtab shiftwidth=4