javaclass

Annotated classhook.py

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