javaclass

Annotated javaclass/classhook.py

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