1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
1.2 +++ b/javaclass/classhook.py Wed Aug 16 01:08:03 2006 +0200
1.3 @@ -0,0 +1,389 @@
1.4 +#!/usr/bin/env python
1.5 +
1.6 +import ihooks # for the import machinery
1.7 +import os, glob # for getting suitably-named files
1.8 +from imp import PY_SOURCE, PKG_DIRECTORY, C_BUILTIN # import machinery magic
1.9 +import classfile, bytecode # Java class support
1.10 +import zipfile # for Java archive inspection
1.11 +import sys
1.12 +
1.13 +# NOTE: Arbitrary constants pulled from thin air.
1.14 +
1.15 +JAVA_PACKAGE = 20041113
1.16 +JAVA_CLASS = 20041114
1.17 +JAVA_ARCHIVE = 20041115
1.18 +
1.19 +class ClassHooks(ihooks.Hooks):
1.20 +
1.21 + "A filesystem hooks class providing information about supported files."
1.22 +
1.23 + def get_suffixes(self):
1.24 +
1.25 + "Return the recognised suffixes."
1.26 +
1.27 + return [("", "", JAVA_PACKAGE), (os.extsep + "jar", "r", JAVA_ARCHIVE)] + ihooks.Hooks.get_suffixes(self)
1.28 +
1.29 + def path_isdir(self, x, archive=None):
1.30 +
1.31 + "Return whether 'x' is a directory in the given 'archive'."
1.32 +
1.33 + if archive is None:
1.34 + return ihooks.Hooks.path_isdir(self, x)
1.35 +
1.36 + return self._get_dirname(x) in archive.namelist()
1.37 +
1.38 + def _get_dirname(self, x):
1.39 +
1.40 + """
1.41 + Return the directory name for 'x'.
1.42 + In zip files, the presence of "/" seems to indicate a directory.
1.43 + """
1.44 +
1.45 + if x.endswith("/"):
1.46 + return x
1.47 + else:
1.48 + return x + "/"
1.49 +
1.50 + def listdir(self, x, archive=None):
1.51 +
1.52 + "Return the contents of the directory 'x' in the given 'archive'."
1.53 +
1.54 + if archive is None:
1.55 + return ihooks.Hooks.listdir(self, x)
1.56 +
1.57 + x = self._get_dirname(x)
1.58 + l = []
1.59 + for path in archive.namelist():
1.60 +
1.61 + # Find out if the path is within the given directory.
1.62 +
1.63 + if path != x and path.startswith(x):
1.64 +
1.65 + # Get the path below the given directory.
1.66 +
1.67 + subpath = path[len(x):]
1.68 +
1.69 + # Find out whether the path is an object in the current directory.
1.70 +
1.71 + if subpath.count("/") == 0 or subpath.count("/") == 1 and subpath.endswith("/"):
1.72 + l.append(subpath)
1.73 +
1.74 + return l
1.75 +
1.76 + def matching(self, dir, extension, archive=None):
1.77 +
1.78 + """
1.79 + Return the matching files in the given directory 'dir' having the given
1.80 + 'extension' within the given 'archive'. Produce a list containing full
1.81 + paths as opposed to simple filenames.
1.82 + """
1.83 +
1.84 + if archive is None:
1.85 + return glob.glob(self.path_join(dir, "*" + extension))
1.86 +
1.87 + dir = self._get_dirname(dir)
1.88 + l = []
1.89 + for path in self.listdir(dir, archive):
1.90 + if path.endswith(extension):
1.91 + l.append(self.path_join(dir, path))
1.92 + return l
1.93 +
1.94 + def read(self, filename, archive=None):
1.95 +
1.96 + """
1.97 + Return the contents of the file with the given 'filename' in the given
1.98 + 'archive'.
1.99 + """
1.100 +
1.101 + if archive is None:
1.102 + f = open(filename, "rb")
1.103 + s = f.read()
1.104 + f.close()
1.105 + return s
1.106 + return archive.read(filename)
1.107 +
1.108 +class ClassLoader(ihooks.ModuleLoader):
1.109 +
1.110 + "A class providing support for searching directories for supported files."
1.111 +
1.112 + def find_module(self, name, path=None):
1.113 +
1.114 + """
1.115 + Find the module with the given 'name', using the given 'path' to locate
1.116 + it. Note that ModuleLoader.find_module is almost sufficient, but does
1.117 + not provide enough support for "package unions" where the root of a
1.118 + package hierarchy may appear in several places.
1.119 +
1.120 + Return a list of locations (each being the "stuff" data structure used
1.121 + by load_module); this replaces the single "stuff" value or None returned
1.122 + by ModuleLoader.find_module.
1.123 + """
1.124 +
1.125 + if path is None:
1.126 + path = [None] + self.default_path()
1.127 +
1.128 + found_locations = []
1.129 +
1.130 + for dir in path:
1.131 + stuff = self.find_module_in_dir(name, dir)
1.132 + if stuff:
1.133 + found_locations.append(stuff)
1.134 +
1.135 + return found_locations
1.136 +
1.137 + def find_module_in_dir(self, name, dir, allow_packages=1):
1.138 +
1.139 + """
1.140 + Find the module with the given 'name' in the given directory 'dir'.
1.141 + Since Java packages/modules are directories containing class files,
1.142 + return the required information tuple only when the path constructed
1.143 + from 'dir' and 'name' refers to a directory containing class files.
1.144 + """
1.145 +
1.146 + result = ihooks.ModuleLoader.find_module_in_dir(self, name, dir, allow_packages)
1.147 + if result is not None:
1.148 + return result
1.149 +
1.150 + # An archive may be opened.
1.151 +
1.152 + archive = None
1.153 +
1.154 + # Provide a special name for the current directory.
1.155 +
1.156 + if name == "__this__":
1.157 + if dir == None:
1.158 + return (None, ".", ("", "", JAVA_PACKAGE))
1.159 + else:
1.160 + return None
1.161 +
1.162 + # Where no directory is given, return failure immediately.
1.163 +
1.164 + elif dir is None:
1.165 + return None
1.166 +
1.167 + # Detect archives.
1.168 +
1.169 + else:
1.170 + archive, archive_path, path = self._get_archive_and_path(dir, name)
1.171 +
1.172 +
1.173 + if self._find_module_at_path(path, archive):
1.174 + if archive is not None:
1.175 + return (archive, archive_path + ":" + path, (os.extsep + "jar", "r", JAVA_ARCHIVE))
1.176 + else:
1.177 + return (None, path, ("", "", JAVA_PACKAGE))
1.178 + else:
1.179 + return None
1.180 +
1.181 + def _get_archive_and_path(self, dir, name):
1.182 + parts = dir.split(":")
1.183 + archive_path = parts[0]
1.184 +
1.185 + # Archives may include an internal path, but will in any case have
1.186 + # a primary part ending in .jar.
1.187 +
1.188 + if archive_path.endswith(os.extsep + "jar"):
1.189 + archive = zipfile.ZipFile(archive_path, "r")
1.190 + path = self.hooks.path_join(":".join(parts[1:]), name)
1.191 +
1.192 + # Otherwise, produce a filesystem-based path.
1.193 +
1.194 + else:
1.195 + archive = None
1.196 + path = self.hooks.path_join(dir, name)
1.197 +
1.198 + return archive, archive_path, path
1.199 +
1.200 + def _get_path_in_archive(self, path):
1.201 + parts = path.split(":")
1.202 + if len(parts) == 1:
1.203 + return parts[0]
1.204 + else:
1.205 + return ":".join(parts[1:])
1.206 +
1.207 + def _find_module_at_path(self, path, archive):
1.208 + if self.hooks.path_isdir(path, archive):
1.209 +
1.210 + # Look for classes in the directory.
1.211 +
1.212 + if len(self.hooks.matching(path, os.extsep + "class", archive)) != 0:
1.213 + return 1
1.214 +
1.215 + # Otherwise permit importing where directories containing classes exist.
1.216 +
1.217 + for filename in self.hooks.listdir(path, archive):
1.218 + pathname = self.hooks.path_join(path, filename)
1.219 + result = self._find_module_at_path(pathname, archive)
1.220 + if result is not None:
1.221 + return result
1.222 +
1.223 + return 0
1.224 +
1.225 + def load_module(self, name, stuff):
1.226 +
1.227 + """
1.228 + Load the module with the given 'name', with a list of 'stuff' items,
1.229 + each of which describes the location of the module and is a tuple of the
1.230 + form (file, filename, (suffix, mode, data type)).
1.231 +
1.232 + Return a module object or raise an ImportError if a problem occurred in
1.233 + the import operation.
1.234 +
1.235 + Note that the 'stuff' parameter is a list and not a single item as in
1.236 + ModuleLoader.load_module. This should still work, however, since the
1.237 + find_module method produces such a list.
1.238 + """
1.239 +
1.240 + #print "load_module", name
1.241 + module = self._not_java_module(name, stuff)
1.242 + if module is not None:
1.243 + return module
1.244 +
1.245 + if not hasattr(self, "loaded_classes"):
1.246 + self.loaded_classes = {}
1.247 + top_level = 1
1.248 + else:
1.249 + top_level = 0
1.250 +
1.251 + main_module = self._load_module(name, stuff)
1.252 +
1.253 + # Initialise the loaded classes.
1.254 +
1.255 + if top_level:
1.256 + self._init_classes()
1.257 + delattr(self, "loaded_classes")
1.258 +
1.259 + return main_module
1.260 +
1.261 + def _not_java_module(self, name, stuff):
1.262 +
1.263 + "Detect non-Java modules."
1.264 +
1.265 + for stuff_item in stuff:
1.266 + archive, filename, info = stuff_item
1.267 + suffix, mode, datatype = info
1.268 + if datatype not in (JAVA_PACKAGE, JAVA_ARCHIVE):
1.269 + return ihooks.ModuleLoader.load_module(self, name, stuff_item)
1.270 +
1.271 + return None
1.272 +
1.273 + def _load_module(self, name, stuff):
1.274 +
1.275 + # Set up the module.
1.276 + # A union of all locations is placed in the module's path.
1.277 +
1.278 + external_names = []
1.279 + module = self.hooks.add_module(name)
1.280 + module.__path__ = [item_filename for (item_archive, item_filename, item_info) in stuff]
1.281 +
1.282 + # Prepare a dictionary of globals.
1.283 +
1.284 + global_names = module.__dict__
1.285 + global_names["__builtins__"] = __builtins__
1.286 +
1.287 + # Just go into each package and find the class files.
1.288 +
1.289 + classes = {}
1.290 + for stuff_item in stuff:
1.291 +
1.292 + # Extract the details, delegating loading responsibility to the
1.293 + # default loader where appropriate.
1.294 + # NOTE: Should we not be using some saved loader remembered upon
1.295 + # NOTE: installation?
1.296 +
1.297 + archive, filename, info = stuff_item
1.298 + suffix, mode, datatype = info
1.299 +
1.300 + # Get the real filename.
1.301 +
1.302 + filename = self._get_path_in_archive(filename)
1.303 +
1.304 + # Load the class files.
1.305 +
1.306 + for class_filename in self.hooks.matching(filename, os.extsep + "class", archive):
1.307 + s = self.hooks.read(class_filename, archive)
1.308 + class_file = classfile.ClassFile(s)
1.309 + #print "Translating", str(class_file.this_class.get_name())
1.310 + translator = bytecode.ClassTranslator(class_file)
1.311 + external_names += translator.process(global_names)
1.312 +
1.313 + # Record the classes found under the current module.
1.314 +
1.315 + self.loaded_classes[str(class_file.this_class.get_name())] = module, translator
1.316 +
1.317 + # Return modules used by external names.
1.318 +
1.319 + external_module_names = self._get_external_module_names(external_names)
1.320 +
1.321 + # Repeatedly load classes from referenced modules.
1.322 +
1.323 + for module_name in external_module_names:
1.324 + new_module = __import__(module_name, global_names)
1.325 + global_names[module_name.split(".")[0]] = new_module
1.326 +
1.327 + return module
1.328 +
1.329 + def _get_external_module_names(self, names):
1.330 + groups = self._get_names_grouped_by_module(names)
1.331 + if groups.has_key(""):
1.332 + del groups[""]
1.333 + return groups.keys()
1.334 +
1.335 + def _get_names_grouped_by_module(self, names):
1.336 + groups = {}
1.337 + for name in names:
1.338 + module_name, class_name = self._get_module_and_class_names(name)
1.339 + if not groups.has_key(module_name):
1.340 + groups[module_name] = []
1.341 + groups[module_name].append(class_name)
1.342 + return groups
1.343 +
1.344 + def _get_module_and_class_names(self, full_name):
1.345 + full_name_parts = full_name.split(".")
1.346 + class_name = full_name_parts[-1]
1.347 + module_name = ".".join(full_name_parts[:-1])
1.348 + return module_name, class_name
1.349 +
1.350 + def _init_classes(self):
1.351 +
1.352 + # Order the classes according to inheritance.
1.353 +
1.354 + init_order = []
1.355 + for class_name, (module, translator) in self.loaded_classes.items():
1.356 +
1.357 + # Insert the base classes before any mention of the current class.
1.358 +
1.359 + for base_class in translator.get_base_class_references():
1.360 + base_class_name = str(base_class.get_name())
1.361 + if base_class_name not in init_order:
1.362 + if class_name not in init_order:
1.363 + init_order.append(base_class_name)
1.364 + else:
1.365 + index = init_order.index(class_name)
1.366 + init_order.insert(index, base_class_name)
1.367 +
1.368 + if class_name not in init_order:
1.369 + init_order.append(class_name)
1.370 +
1.371 + # Create the classes.
1.372 +
1.373 + real_classes = []
1.374 + for class_name in init_order:
1.375 + try:
1.376 + module, translator = self.loaded_classes[class_name]
1.377 + global_names = module.__dict__
1.378 + real_classes.append((module, translator.get_class(global_names)))
1.379 + except KeyError:
1.380 + # NOTE: Should be a non-Java class.
1.381 + pass
1.382 +
1.383 + # Finally, call __clinit__ methods for all relevant classes.
1.384 +
1.385 + for module, cls in real_classes:
1.386 + if hasattr(cls, "__clinit__"):
1.387 + global_names = module.__dict__
1.388 + eval(cls.__clinit__.func_code, global_names)
1.389 +
1.390 +ihooks.ModuleImporter(loader=ClassLoader(hooks=ClassHooks())).install()
1.391 +
1.392 +# vim: tabstop=4 expandtab shiftwidth=4