module J2ME { declare var ZipFile; declare var snarf; export class ClassRegistry { /** * List of directories to look for source files in. */ sourceDirectories: string []; /** * All source code, only ever used for debugging. */ sourceFiles: Map; /** * List of classes whose sources files were not found. We keep track * of them so we don't have to search for them over and over. */ missingSourceFiles: Map; jarFiles: Map; classFiles: Map; classes: Map; preInitializedClasses: ClassInfo []; java_lang_Object: ClassInfo; java_lang_Class: ClassInfo; java_lang_String: ClassInfo; java_lang_Thread: ClassInfo; constructor() { this.sourceDirectories = []; this.sourceFiles = Object.create(null); this.missingSourceFiles = Object.create(null); this.jarFiles = Object.create(null); this.classFiles = Object.create(null); this.classes = Object.create(null); this.preInitializedClasses = []; } initializeBuiltinClasses() { // These classes are guaranteed to not have a static initializer. enterTimeline("initializeBuiltinClasses"); this.java_lang_Object = this.loadAndLinkClass("java/lang/Object"); this.java_lang_Class = this.loadAndLinkClass("java/lang/Class"); this.java_lang_String = this.loadAndLinkClass("java/lang/String"); this.java_lang_Thread = this.loadAndLinkClass("java/lang/Thread"); this.preInitializedClasses.push(this.java_lang_Object); this.preInitializedClasses.push(this.java_lang_Class); this.preInitializedClasses.push(this.java_lang_String); this.preInitializedClasses.push(this.java_lang_Thread); /** * Force these frequently used classes to be initialized eagerly. We can * skip the class initialization check for them. This is only possible * because they don't have any static state. */ var classNames = [ "java/lang/Integer", "java/lang/Character", "java/lang/Math", "java/util/HashtableEntry", "java/lang/StringBuffer", "java/util/Vector", "java/io/IOException", "java/lang/IllegalArgumentException" ]; for (var i = 0; i < classNames.length; i++) { this.preInitializedClasses.push(this.loadAndLinkClass(classNames[i])); } // Link primitive values and primitive arrays. for (var i = 0; i < "ZCFDBSIJ".length; i++) { var typeName = "ZCFDBSIJ"[i]; linkKlass(PrimitiveClassInfo[typeName]); this.getClass("[" + typeName); } leaveTimeline("initializeBuiltinClasses"); } isPreInitializedClass(classInfo: ClassInfo) { return this.preInitializedClasses.indexOf(classInfo) >= 0; } addPath(name: string, buffer: ArrayBuffer) { if (name.substr(-4) === ".jar") { this.jarFiles[name] = new ZipFile(buffer); } else { this.classFiles[name] = buffer; } } addSourceDirectory(name: string) { this.sourceDirectories.push(name); } getSourceLine(sourceLocation: SourceLocation): string { if (typeof snarf === "undefined") { // Sorry, no snarf in the browser. Do async loading instead. return null; } var source = this.sourceFiles[sourceLocation.className]; if (!source && !this.missingSourceFiles[sourceLocation.className]) { for (var i = 0; i < this.sourceDirectories.length; i++) { try { var path = this.sourceDirectories[i] + "/" + sourceLocation.className + ".java"; var file = snarf(path); if (file) { source = this.sourceFiles[sourceLocation.className] = file.split("\n"); } } catch (x) { // Keep looking. //stderrWriter.writeLn("" + x); } } } if (source) { return source[sourceLocation.lineNumber - 1]; } this.missingSourceFiles[sourceLocation.className] = true; return null; } loadFileFromJar(jarName: string, fileName: string): ArrayBuffer { var zip = this.jarFiles[jarName]; if (!zip) return null; if (!(fileName in zip.directory)) return null; var bytes = zip.read(fileName); return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); } loadFile(fileName: string): ArrayBuffer { var classFiles = this.classFiles; var data = classFiles[fileName]; if (data) { return data; } var jarFiles = this.jarFiles; for (var k in jarFiles) { var zip = jarFiles[k]; if (fileName in zip.directory) { enterTimeline("ZIP", {file: fileName}); var bytes = zip.read(fileName); data = bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength); leaveTimeline("ZIP"); break; } } if (data) { classFiles[fileName] = data; } return data; } loadClassBytes(bytes: ArrayBuffer): ClassInfo { enterTimeline("loadClassBytes"); var classInfo = new ClassInfo(bytes); leaveTimeline("loadClassBytes", {className: classInfo.className}); this.classes[classInfo.className] = classInfo; return classInfo; } loadClassFile(fileName: string): ClassInfo { loadWriter && loadWriter.enter("> Loading Class File: " + fileName); var bytes = this.loadFile(fileName); if (!bytes) { loadWriter && loadWriter.leave("< ClassNotFoundException"); throw new (ClassNotFoundException)(fileName); } var self = this; var classInfo = this.loadClassBytes(bytes); if (classInfo.superClassName) { classInfo.superClass = this.loadClass(classInfo.superClassName); var superClass = classInfo.superClass; superClass.subClasses.push(classInfo); while (superClass) { superClass.allSubClasses.push(classInfo); superClass = superClass.superClass; } } var classes = classInfo.classes; classes.forEach(function (c, n) { classes[n] = self.loadClass(c); }); classInfo.complete(); loadWriter && loadWriter.leave("<"); return classInfo; } /** * Used to test loading of all class files. */ loadAllClassFiles() { var jarFiles = this.jarFiles; Object.keys(jarFiles).every(function (path) { if (path.substr(-4) !== ".jar") { return true; } var zipFile = jarFiles[path]; Object.keys(zipFile.directory).every(function (fileName) { if (fileName.substr(-6) !== '.class') { return true; } var className = fileName.substring(0, fileName.length - 6); CLASSES.getClass(className); return true; }); }); } loadClass(className: string): ClassInfo { var classInfo = this.classes[className]; if (classInfo) { return classInfo; } return this.loadClassFile(className + ".class"); } loadAndLinkClass(className: string): ClassInfo { var classInfo = this.loadClass(className); linkKlass(classInfo); return classInfo; } getEntryPoint(classInfo: ClassInfo): MethodInfo { var methods = classInfo.methods; for (var i=0; i