javaclass

Annotated javaclass/classhook.py

137:182cba61327c
2005-01-21 Paul Boddie Moved the modules into the javaclass package.
paul@137 1
#!/usr/bin/env python
paul@137 2
paul@137 3
import ihooks # for the import machinery
paul@137 4
import os, glob # for getting suitably-named files
paul@137 5
from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic
paul@137 6
import classfile, bytecode # Java class support
paul@137 7
import zipfile # for Java archive inspection
paul@137 8
paul@137 9
# NOTE: Arbitrary constants pulled from thin air.
paul@137 10
paul@137 11
JAVA_PACKAGE = 20041113
paul@137 12
JAVA_CLASS = 20041114
paul@137 13
JAVA_ARCHIVE = 20041115
paul@137 14
paul@137 15
class ClassHooks(ihooks.Hooks):
paul@137 16
paul@137 17
    "A filesystem hooks class providing information about supported files."
paul@137 18
paul@137 19
    def get_suffixes(self):
paul@137 20
paul@137 21
        "Return the recognised suffixes."
paul@137 22
paul@137 23
        return [("", "", JAVA_PACKAGE), (os.extsep + "jar", "r", JAVA_ARCHIVE)] + ihooks.Hooks.get_suffixes(self)
paul@137 24
paul@137 25
    def path_isdir(self, x, archive=None):
paul@137 26
paul@137 27
        "Return whether 'x' is a directory in the given 'archive'."
paul@137 28
paul@137 29
        if archive is None:
paul@137 30
            return ihooks.Hooks.path_isdir(self, x)
paul@137 31
paul@137 32
        return self._get_dirname(x) in archive.namelist()
paul@137 33
paul@137 34
    def _get_dirname(self, x):
paul@137 35
paul@137 36
        """
paul@137 37
        Return the directory name for 'x'.
paul@137 38
        In zip files, the presence of "/" seems to indicate a directory.
paul@137 39
        """
paul@137 40
paul@137 41
        if x.endswith("/"):
paul@137 42
            return x
paul@137 43
        else:
paul@137 44
            return x + "/"
paul@137 45
paul@137 46
    def listdir(self, x, archive=None):
paul@137 47
paul@137 48
        "Return the contents of the directory 'x' in the given 'archive'."
paul@137 49
paul@137 50
        if archive is None:
paul@137 51
            return ihooks.Hooks.listdir(self, x)
paul@137 52
paul@137 53
        x = self._get_dirname(x)
paul@137 54
        l = []
paul@137 55
        for path in archive.namelist():
paul@137 56
paul@137 57
            # Find out if the path is within the given directory.
paul@137 58
paul@137 59
            if path != x and path.startswith(x):
paul@137 60
paul@137 61
                # Get the path below the given directory.
paul@137 62
paul@137 63
                subpath = path[len(x):]
paul@137 64
paul@137 65
                # Find out whether the path is an object in the current directory.
paul@137 66
paul@137 67
                if subpath.count("/") == 0 or subpath.count("/") == 1 and subpath.endswith("/"):
paul@137 68
                    l.append(subpath)
paul@137 69
paul@137 70
        return l
paul@137 71
paul@137 72
    def matching(self, dir, extension, archive=None):
paul@137 73
paul@137 74
        """
paul@137 75
        Return the matching files in the given directory 'dir' having the given
paul@137 76
        'extension' within the given 'archive'. Produce a list containing full
paul@137 77
        paths as opposed to simple filenames.
paul@137 78
        """
paul@137 79
paul@137 80
        if archive is None:
paul@137 81
            return glob.glob(self.path_join(dir, "*" + extension))
paul@137 82
paul@137 83
        dir = self._get_dirname(dir)
paul@137 84
        l = []
paul@137 85
        for path in self.listdir(dir, archive):
paul@137 86
            if path.endswith(extension):
paul@137 87
                l.append(self.path_join(dir, path))
paul@137 88
        return l
paul@137 89
paul@137 90
    def read(self, filename, archive=None):
paul@137 91
paul@137 92
        """
paul@137 93
        Return the contents of the file with the given 'filename' in the given
paul@137 94
        'archive'.
paul@137 95
        """
paul@137 96
paul@137 97
        if archive is None:
paul@137 98
            f = open(filename, "rb")
paul@137 99
            s = f.read()
paul@137 100
            f.close()
paul@137 101
            return s
paul@137 102
        return archive.read(filename)
paul@137 103
paul@137 104
class ClassLoader(ihooks.ModuleLoader):
paul@137 105
paul@137 106
    "A class providing support for searching directories for supported files."
paul@137 107
paul@137 108
    def find_module(self, name, path=None):
paul@137 109
paul@137 110
        """
paul@137 111
        Find the module with the given 'name', using the given 'path' to locate
paul@137 112
        it. Note that ModuleLoader.find_module is almost sufficient, but does
paul@137 113
        not provide enough support for "package unions" where the root of a
paul@137 114
        package hierarchy may appear in several places.
paul@137 115
paul@137 116
        Return a list of locations (each being the "stuff" data structure used
paul@137 117
        by load_module); this replaces the single "stuff" value or None returned
paul@137 118
        by ModuleLoader.find_module.
paul@137 119
        """
paul@137 120
paul@137 121
        if path is None:
paul@137 122
            path = [None] + self.default_path()
paul@137 123
paul@137 124
        found_locations = []
paul@137 125
paul@137 126
        for dir in path:
paul@137 127
            stuff = self.find_module_in_dir(name, dir)
paul@137 128
            if stuff:
paul@137 129
                found_locations.append(stuff)
paul@137 130
paul@137 131
        return found_locations
paul@137 132
paul@137 133
    def find_module_in_dir(self, name, dir, allow_packages=1):
paul@137 134
paul@137 135
        """
paul@137 136
        Find the module with the given 'name' in the given directory 'dir'.
paul@137 137
        Since Java packages/modules are directories containing class files,
paul@137 138
        return the required information tuple only when the path constructed
paul@137 139
        from 'dir' and 'name' refers to a directory containing class files.
paul@137 140
        """
paul@137 141
paul@137 142
        result = ihooks.ModuleLoader.find_module_in_dir(self, name, dir, allow_packages)
paul@137 143
        if result is not None:
paul@137 144
            return result
paul@137 145
paul@137 146
        # An archive may be opened.
paul@137 147
paul@137 148
        archive = None
paul@137 149
paul@137 150
        # Provide a special name for the current directory.
paul@137 151
paul@137 152
        if name == "__this__":
paul@137 153
            if dir == None:
paul@137 154
                return (None, ".", ("", "", JAVA_PACKAGE))
paul@137 155
            else:
paul@137 156
                return None
paul@137 157
paul@137 158
        # Where no directory is given, return failure immediately.
paul@137 159
paul@137 160
        elif dir is None:
paul@137 161
            return None
paul@137 162
paul@137 163
        # Detect archives.
paul@137 164
paul@137 165
        else:
paul@137 166
            archive, archive_path, path = self._get_archive_and_path(dir, name)
paul@137 167
paul@137 168
        #print "Processing name", name, "in", dir, "producing", path, "within archive", archive
paul@137 169
paul@137 170
        if self._find_module_at_path(path, archive):
paul@137 171
            if archive is not None:
paul@137 172
                return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE))
paul@137 173
            else:
paul@137 174
                return (None, path, ("", "", JAVA_PACKAGE))
paul@137 175
        else:
paul@137 176
            return None
paul@137 177
paul@137 178
    def _get_archive_and_path(self, dir, name):
paul@137 179
        parts = dir.split(":")
paul@137 180
        archive_path = parts[0]
paul@137 181
paul@137 182
        # Archives may include an internal path, but will in any case have
paul@137 183
        # a primary part ending in .jar.
paul@137 184
paul@137 185
        if archive_path.endswith(os.extsep + "jar"):
paul@137 186
            archive = zipfile.ZipFile(archive_path, "r")
paul@137 187
            path = self.hooks.path_join(":".join(parts[1:]), name)
paul@137 188
paul@137 189
        # Otherwise, produce a filesystem-based path.
paul@137 190
paul@137 191
        else:
paul@137 192
            archive = None
paul@137 193
            path = self.hooks.path_join(dir, name)
paul@137 194
paul@137 195
        return archive, archive_path, path
paul@137 196
paul@137 197
    def _get_path_in_archive(self, path):
paul@137 198
        parts = path.split(":")
paul@137 199
        if len(parts) == 1:
paul@137 200
            return parts[0]
paul@137 201
        else:
paul@137 202
            return ":".join(parts[1:])
paul@137 203
paul@137 204
    def _find_module_at_path(self, path, archive):
paul@137 205
        if self.hooks.path_isdir(path, archive):
paul@137 206
            #print "Looking in", path, "using archive", archive
paul@137 207
paul@137 208
            # Look for classes in the directory.
paul@137 209
paul@137 210
            if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:
paul@137 211
                return 1
paul@137 212
paul@137 213
            # Otherwise permit importing where directories containing classes exist.
paul@137 214
paul@137 215
            #print "Filenames are", self.hooks.listdir(path, archive)
paul@137 216
            for filename in self.hooks.listdir(path, archive):
paul@137 217
                pathname = self.hooks.path_join(path, filename)
paul@137 218
                result = self._find_module_at_path(pathname, archive)
paul@137 219
                if result is not None:
paul@137 220
                    return result
paul@137 221
paul@137 222
        return 0
paul@137 223
paul@137 224
    def load_module(self, name, stuff):
paul@137 225
paul@137 226
        """
paul@137 227
        Load the module with the given 'name', with a list of 'stuff' items,
paul@137 228
        each of which describes the location of the module and is a tuple of the
paul@137 229
        form (file, filename, (suffix, mode, data type)).
paul@137 230
paul@137 231
        Return a module object or raise an ImportError if a problem occurred in
paul@137 232
        the import operation.
paul@137 233
paul@137 234
        Note that the 'stuff' parameter is a list and not a single item as in
paul@137 235
        ModuleLoader.load_module. This should still work, however, since the
paul@137 236
        find_module method produces such a list.
paul@137 237
        """
paul@137 238
paul@137 239
        # Set up the module.
paul@137 240
        # A union of all locations is placed in the module's path.
paul@137 241
paul@137 242
        module = self.hooks.add_module(name)
paul@137 243
        module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]
paul@137 244
paul@137 245
        # Just go into each package and find the class files.
paul@137 246
paul@137 247
        for stuff_item in stuff:
paul@137 248
paul@137 249
            # Extract the details, delegating loading responsibility to the
paul@137 250
            # default loader where appropriate.
paul@137 251
            # NOTE: Should we not be using some saved loader remembered upon
paul@137 252
            # NOTE: installation?
paul@137 253
paul@137 254
            archive, filename, info = stuff_item
paul@137 255
            suffix, mode, datatype = info
paul@137 256
            if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):
paul@137 257
                return ihooks.ModuleLoader.load_module(self, name, stuff_item)
paul@137 258
paul@137 259
            #print "Loading", archive, filename, info
paul@137 260
paul@137 261
            # Prepare a dictionary of globals.
paul@137 262
paul@137 263
            global_names = module.__dict__
paul@137 264
            global_names["__builtins__"] = __builtins__
paul@137 265
paul@137 266
            # Get the real filename.
paul@137 267
paul@137 268
            filename = self._get_path_in_archive(filename)
paul@137 269
            #print "Real filename", filename
paul@137 270
paul@137 271
            # Load the class files.
paul@137 272
paul@137 273
            class_files = {}
paul@137 274
            for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):
paul@137 275
                #print "Loading class", class_filename
paul@137 276
                s = self.hooks.read(class_filename, archive)
paul@137 277
                class_file = classfile.ClassFile(s)
paul@137 278
                class_files[str(class_file.this_class.get_name())] = class_file
paul@137 279
paul@137 280
            # Get an index of the class files.
paul@137 281
paul@137 282
            class_file_index = class_files.keys()
paul@137 283
paul@137 284
            # NOTE: Unnecessary sorting for test purposes.
paul@137 285
paul@137 286
            class_file_index.sort()
paul@137 287
paul@137 288
            # Now go through the classes arranging them in a safe loading order.
paul@137 289
paul@137 290
            position = 0
paul@137 291
            while position < len(class_file_index):
paul@137 292
                class_name = class_file_index[position]
paul@137 293
                super_class_name = str(class_files[class_name].super_class.get_name())
paul@137 294
paul@137 295
                # Discover whether the superclass appears later.
paul@137 296
paul@137 297
                try:
paul@137 298
                    super_class_position = class_file_index.index(super_class_name)
paul@137 299
                    if super_class_position > position:
paul@137 300
paul@137 301
                        # If the superclass appears later, swap this class and the
paul@137 302
                        # superclass, then process the superclass.
paul@137 303
paul@137 304
                        class_file_index[position] = super_class_name
paul@137 305
                        class_file_index[super_class_position] = class_name
paul@137 306
                        continue
paul@137 307
paul@137 308
                except ValueError:
paul@137 309
                    pass
paul@137 310
paul@137 311
                position += 1
paul@137 312
paul@137 313
            # Process each class file, producing a genuine Python class.
paul@137 314
            # Create the classes, but establish a proper initialisation order.
paul@137 315
paul@137 316
            class_file_init_index = []
paul@137 317
            class_file_init = {}
paul@137 318
paul@137 319
            for class_name in class_file_index:
paul@137 320
                #print "* Class", class_name
paul@137 321
                class_file = class_files[class_name]
paul@137 322
                translator = bytecode.ClassTranslator(class_file)
paul@137 323
                cls, external_names = translator.process(global_names)
paul@137 324
                module.__dict__[cls.__name__] = cls
paul@137 325
paul@137 326
                # Process external names.
paul@137 327
paul@137 328
                this_class_name_parts = class_file.this_class.get_python_name().split(".")
paul@137 329
                this_class_module, this_class_name = this_class_name_parts[:-1], this_class_name_parts[-1]
paul@137 330
paul@137 331
                for external_name in external_names:
paul@137 332
                    #print "* Name", external_name
paul@137 333
                    external_name_parts = external_name.split(".")
paul@137 334
                    external_class_module, external_class_name = external_name_parts[:-1], external_name_parts[-1]
paul@137 335
paul@137 336
                    # Names not local to this package need importing.
paul@137 337
paul@137 338
                    if len(external_name_parts) > 1 and this_class_module != external_class_module:
paul@137 339
paul@137 340
                        external_module_name = ".".join(external_class_module)
paul@137 341
                        #print "* Importing", external_module_name
paul@137 342
                        obj = __import__(external_module_name, global_names, {}, [])
paul@137 343
                        global_names[external_name_parts[0]] = obj
paul@137 344
paul@137 345
                    # Names local to this package may affect initialisation order.
paul@137 346
paul@137 347
                    elif external_class_name not in class_file_init_index:
paul@137 348
                        try:
paul@137 349
                            this_class_name_index = class_file_init_index.index(this_class_name)
paul@137 350
paul@137 351
                            # Either insert this name before the current class's
paul@137 352
                            # name.
paul@137 353
paul@137 354
                            #print "* Inserting", external_class_name
paul@137 355
                            class_file_init_index.insert(this_class_name_index, external_class_name)
paul@137 356
paul@137 357
                        except ValueError:
paul@137 358
paul@137 359
                            # Or add this name in anticipation of the current
paul@137 360
                            # class's name appearing.
paul@137 361
paul@137 362
                            #print "* Including", external_class_name
paul@137 363
                            class_file_init_index.append(external_class_name)
paul@137 364
paul@137 365
                # Add this class name to the initialisation index.
paul@137 366
paul@137 367
                if class_name not in class_file_init_index:
paul@137 368
                    class_file_init_index.append(this_class_name)
paul@137 369
                class_file_init[this_class_name] = (cls, class_file)
paul@137 370
paul@137 371
            # Finally, call __clinit__ methods for all relevant classes.
paul@137 372
paul@137 373
            #print "** Initialisation order", class_file_init_index
paul@137 374
            for class_name in class_file_init_index:
paul@137 375
                cls, class_file = class_file_init[class_name]
paul@137 376
                #print "**", cls, class_file
paul@137 377
                if hasattr(cls, "__clinit__"):
paul@137 378
                    eval(cls.__clinit__.func_code, global_names)
paul@137 379
paul@137 380
        return module
paul@137 381
paul@137 382
ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()
paul@137 383
paul@137 384
# vim: tabstop=4 expandtab shiftwidth=4