package org.nanovm.converter; import javassist.ClassPool; import javassist.CtClass; import javassist.CtConstructor; import javassist.CtMethod; import org.apache.log4j.Logger; import java.io.*; import java.util.*; import java.util.jar.JarFile; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import nanovm.Native; public class ClassLoader { private static final Logger Tracer = Logger.getLogger(ClassLoader.class); private List<File> classPath = new ArrayList<File>(1); private Vector<ClassInfo> classes = new Vector<ClassInfo>(); private Vector<NativeClass> nativeClasses = new Vector<NativeClass>(); private Vector<NativeMethod> nativeMethods = new Vector<NativeMethod>(); private Vector<NativeField> nativeFields = new Vector<NativeField>(); public int lowestNativeId = 9999; // lowest native class id // java doesn't have native classes, but we do class NativeClass { String className; int id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; NativeClass that = (NativeClass) o; if (id != that.id) return false; if (className != null ? !className.equals(that.className) : that.className != null) return false; return true; } @Override public int hashCode() { int result = className != null ? className.hashCode() : 0; result = 31 * result + id; return result; } @Override public String toString() { return "NativeClass{" + "className='" + className + '\'' + ", id=" + id + '}'; } } class NativeMethod { String className; String name; String type; int id; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; NativeMethod that = (NativeMethod) o; if (id != that.id) return false; if (className != null ? !className.equals(that.className) : that.className != null) return false; if (name != null ? !name.equals(that.name) : that.name != null) return false; if (type != null ? !type.equals(that.type) : that.type != null) return false; return true; } @Override public int hashCode() { int result = className != null ? className.hashCode() : 0; result = 31 * result + (name != null ? name.hashCode() : 0); result = 31 * result + (type != null ? type.hashCode() : 0); result = 31 * result + id; return result; } @Override public String toString() { return "NativeMethod{" + "className='" + className + '\'' + ", name='" + name + '\'' + ", type='" + type + '\'' + ", id=" + id + '}'; } } class NativeField { String className; String name; String type; int id; } public ClassLoader(String classPath) throws ClassNotFoundException { Tracer.debug("Classpath: "+classPath); StringTokenizer pathTokenizer = new StringTokenizer(classPath, File.pathSeparator); while (pathTokenizer.hasMoreElements()) { this.classPath.add(new File(pathTokenizer.nextToken())); } loadNativeClass("java.lang.Object"); } public ClassInfo getClassInfo(String className) throws ClassNotFoundException { for(ClassInfo ci : classes){ if(ci.getName().equals(className)){ return ci; } } return loadClass(className); } public ClassInfo getClassInfo(int index){ return classes.elementAt(index); } public int getClassIndex(String className){ // search through all classes for (int i = 0; i < classes.size(); i++) { if (getClassInfo(i).getName().equals(className)) return i; } return -1; } // in an uvm file all methods of all classes are stored after each other // this method returns the class index from the method index in this list public ClassInfo getClassInfoFromMethodIndex(int index) { int classIndex = 0; // search through all classes while (index >= getClassInfo(classIndex).methods()) index -= getClassInfo(classIndex++).methods(); return getClassInfo(classIndex); } // get class index of method with index public int getClassIndex(int index) { int i = 0; // search through all classes while (index >= getClassInfo(i).methods()) index -= getClassInfo(i++).methods(); return i; } public int totalClasses() { return classes.size(); } // return index of main method (must be in class 0, since // it is the one the user gave as an argument) public int getMainIndex() { return getClassInfo(0).getMethodIndex("main", "([Ljava/lang/String;)V"); } public String getSuperClassName(String className) { // search through all classes for (int i = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); if (classInfo.getName().equals(className)) return classInfo.getSuperClassName(); } return null; } public boolean fieldExistsExact(String className, String name, String type) { // search through all classes for (int i = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); if (classInfo.getName().equals(className) && classInfo.providesField(name, type)){ return true; } } return false; } public boolean fieldExists(String className, String name, String type) { while (className != null) { if (fieldExistsExact(className, name, type)) return true; className = getSuperClassName(className); } return false; } public FieldInfo getFieldInfoExact(String className, String name, String type) { // search through all classes for (int i = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); if (classInfo.getName().equals(className)){ return classInfo.getFieldInfo(name, type); } } return null; } public FieldInfo getFieldInfo(String className, String name, String type) { while (className != null) { FieldInfo result = getFieldInfoExact(className, name, type); if (result != null){ return result; } className = getSuperClassName(className); } return null; } // search for a static field and sum up all static fields before, // this gives us the offset to this static field in the table // of all static fields of our set of class files public int getStaticFieldIndex(String className, String name, String type) { // search through all classes for (int i = 0, cnt = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); if (classInfo.getName().equals(className)){ return cnt + classInfo.getFieldIndex(AccessFlags.STATIC, name, type); } else { cnt += classInfo.staticFields(); } } return -1; } // search through all classes and return index of matching method public int getMethodIndex(String className, String name, String type) { for (int i = 0, index = 0; i < classes.size(); i++) { if (getClassInfo(i).getName().equals(className)) { index += getClassInfo(i).getMethodIndex(name, type); return index; } else index += getClassInfo(i).methods(); } return -1; } public boolean methodExists(String className, String name, String type) { Tracer.debug("methodExists CN:"+className+" Name:"+name+" Type:"+type); // search through all classes for (int i = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); if (classInfo.getName().equals(className) && classInfo.providesMethod(name, type)) return true; } return false; } public MethodInfo getMethod(int index) { int i = 0; // search through all classes while (index >= getClassInfo(i).methods()) index -= getClassInfo(i++).methods(); return getClassInfo(i).getMethod(index); } // get total methods of all classes public int totalMethods() { int num = 0; for (int i = 0; i < classes.size(); i++) num += getClassInfo(i).methods(); return num; } // TODO: public int totalConstantEntries() { int sum = 0; for (int i = 0; i < classes.size(); i++) sum += getClassInfo(i).getConstPool().totalConstantEntries(); return sum; } public int getConstantEntry(int index) { int i = 0; // search through all classes while (index >= getClassInfo(i).getConstPool().totalConstantEntries()) index -= getClassInfo(i++).getConstPool().totalConstantEntries(); return getClassInfo(i).getConstPool().getConstantEntry(index); } // get memory required to store all strings public int totalStringSize() { int sum = 0; for (int i = 0; i < classes.size(); i++) sum += getClassInfo(i).getConstPool().totalStringSize(); return sum; } // get memory required to store all strings public int totalStrings() { int sum = 0; for (int i = 0; i < classes.size(); i++) sum += getClassInfo(i).getConstPool().totalStrings(); return sum; } public String getString(int index) { int i = 0; // search through all classes while (index >= getClassInfo(i).getConstPool().totalStrings()) index -= getClassInfo(i++).getConstPool().totalStrings(); return getClassInfo(i).getConstPool().getString(index); } // total number of static fields public int totalStaticFields() { int sum = 0; for (int i = 0; i < classes.size(); i++) { ClassInfo classInfo = getClassInfo(i); sum += classInfo.staticFields(); } return sum; } /** * loads resource from cp * @param name * @return */ byte[] loadResource(List<File> classPath, String name){ Tracer.debug("loadResource "+name); byte[] resourceData = null; for(File dir : classPath){ if(dir.isDirectory()){ Tracer.debug("Check directory "+dir.getAbsolutePath()); File cf = new File(dir, name); if(cf.isFile()){ // read single file Tracer.debug("Found file "+cf.getAbsolutePath()); FileInputStream fis = null; try { resourceData = readStream(fis = new FileInputStream(cf)); } catch (Throwable t){ Tracer.error("Failed reading file", t); } finally { if(cf != null){ try { fis.close(); } catch (Throwable t){} } } } } else if(dir.isFile()){ Tracer.debug("Check archive "+dir.getAbsolutePath()); JarFile jf = null; try { jf = new JarFile(dir, false, ZipFile.OPEN_READ); ZipEntry entry = jf.getEntry(name); if(entry != null){ Tracer.debug("Found entry "+name); resourceData = readStream(jf.getInputStream(entry)); } } catch (Throwable t){ Tracer.error("Failed accessing archive", t); } finally { if(jf != null){ try { jf.close(); } catch (Throwable t){} } } } else { Tracer.debug("Unknown path "+dir.getAbsolutePath()); } if(resourceData != null){ break; } } return resourceData; } private ClassInfo loadClass(String name) throws ClassNotFoundException { // not loaded already... String filePath = name.replace('.', '/')+".class"; Tracer.debug("loadClass "+filePath); byte[] classData = loadResource(classPath, filePath); if(classData == null){ throw new ClassNotFoundException(name); } ClassFileReader reader = new ClassFileReader(); ClassInfo info = new ClassInfo(this); try { reader.read(new ByteArrayInputStream(classData), info); } catch (IOException ex){ throw new ClassNotFoundException(name, ex); } classes.add(info); // go through all constants and check for method references info.getConstPool().resolveMethodRefs(); return info; } private byte[] readStream(InputStream is) throws IOException { if(is == null){ return null; } try { ByteArrayOutputStream bos = new ByteArrayOutputStream(is.available()); byte[] buffer = new byte[1024]; int read; while((read = is.read(buffer)) != -1){ bos.write(buffer, 0, read); } return bos.size() > 0 ? bos.toByteArray() : null; } finally { try { is.close(); } catch(Throwable t){} } } public NativeClass getNativeClass(String className) throws ClassNotFoundException { Tracer.debug("getNativeClass "+className); for(NativeClass nc : nativeClasses){ if(nc.className.equals(className)){ return nc; } } Tracer.debug("Try loading native..."); return loadNativeClass(className); } public boolean methodIsNative(String className, String name, String type) { Tracer.debug("methodIsNative "+className+" "+name+" "+type); try { getNativeClass(className); } catch (ClassNotFoundException ex){ Tracer.debug("No native found, return false."); return false; } // search through all native methods for (int i = 0; i < nativeMethods.size(); i++) { NativeMethod nativeMethod = nativeMethods.elementAt(i); Tracer.debug("Check native method: "+nativeMethod); // check if we have a match if ((className.equals(nativeMethod.className)) && (name.equals(nativeMethod.name)) && (type.equals(nativeMethod.type))) return true; } return false; } public int getNativeMethodId(String className, String name, String type) { try { getNativeClass(className); } catch (ClassNotFoundException ex){ return -1; } // search through all native methods for (int i = 0; i < nativeMethods.size(); i++) { NativeMethod nativeMethod = nativeMethods.elementAt(i); // check if we have a match if ((className.equals(nativeMethod.className)) && (name.equals(nativeMethod.name)) && (type.equals(nativeMethod.type))) return nativeMethod.id; } return -1; } public int getNativeFieldId(String className, String name, String type) { try { getNativeClass(className); } catch (ClassNotFoundException ex){ return -1; } // search through all native fields for (int i = 0; i < nativeFields.size(); i++) { NativeField nativeField = nativeFields.elementAt(i); // check if we have a match if ((className.equals(nativeField.className)) && (name.equals(nativeField.name)) && (type.equals(nativeField.type))) return nativeField.id; } return -1; } public int getNativeClassId(String className) { // search through all native classes for (int i = 0; i < nativeClasses.size(); i++) { NativeClass nativeClass = nativeClasses.elementAt(i); if (nativeClass.className.equals(className)) return nativeClass.id; } return -1; } private NativeClass loadAnnotatedNativeClass(String className) { try { String filePath = className.replace('.', '/')+".class"; byte[] nativeData = loadResource(classPath, filePath); ClassPool cp = new ClassPool(); CtClass ctClass = cp.makeClass(new ByteArrayInputStream(nativeData)); Tracer.debug("Found class "+ctClass.getName()); Native nativeAnnotation = (Native)ctClass.getAnnotation(Native.class); if(nativeAnnotation == null){ return null; } Tracer.debug("Class has native ID "+nativeAnnotation.id()); NativeClass res = new NativeClass(); res.className = ctClass.getName().replace('.', '/'); res.id = nativeAnnotation.id(); Tracer.debug("Found "+res); nativeClasses.add(res); // c'tors for(CtConstructor ctC : ctClass.getConstructors()){ nativeAnnotation = (Native)ctC.getAnnotation(Native.class); if(nativeAnnotation == null){ continue; } NativeMethod nativeMethod = new NativeMethod(); nativeMethod.className = res.className; nativeMethod.name = "<init>"; nativeMethod.id = nativeAnnotation.id() + (res.id << 8); nativeMethod.type = ctC.getParameterTypes().length == 0 ? "()V" : "FIXME"; Tracer.debug("Found "+nativeMethod); nativeMethods.add(nativeMethod); } // apply methods for(CtMethod ctMethod : ctClass.getMethods()){ nativeAnnotation = (Native)ctMethod.getAnnotation(Native.class); if(nativeAnnotation == null){ continue; } NativeMethod nativeMethod = new NativeMethod(); nativeMethod.className = res.className; nativeMethod.name = ctMethod.getName(); nativeMethod.id = nativeAnnotation.id() + (res.id << 8); nativeMethod.type = ctMethod.getReturnType().getName(); Tracer.debug("Found "+nativeMethod); nativeMethods.add(nativeMethod); } if (res.id < lowestNativeId) lowestNativeId = res.id; return res; } catch(Exception ex){ ex.printStackTrace(); } return null; } private NativeClass loadNativeClass(String className) throws ClassNotFoundException { System.out.println("read native " + className); Tracer.debug("read native "+ className); NativeClass res = null; // try annotations res = loadAnnotatedNativeClass(className); if(res != null){ return res; } String filePath = className.replace('.', '/')+".native"; byte[] nativeData = loadResource(classPath, filePath); if(nativeData == null){ return null; } try { String line; String fullClassName = null; int fullClassId = 0; BufferedReader reader = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(nativeData))); // read through all lines in file while ((line = reader.readLine()) != null) { StringTokenizer st = new StringTokenizer(line); boolean skipRest = false; int token_cnt = 0; String name = null, value = null, id = null; while (st.hasMoreTokens() && !skipRest) { String token = st.nextToken(); if (token.charAt(0) == '#') skipRest = true; else { if (token_cnt == 0) name = token; else if (token_cnt == 1) value = token; else if (token_cnt == 2) id = token; else { System.out.println("Ignoring superfluous data: " + token); } token_cnt++; } } if (token_cnt > 0) { // class entry if (name.equalsIgnoreCase("class") && (token_cnt == 3)) { res = new NativeClass(); res.className = value; res.id = Integer.parseInt(id); Tracer.debug("Found "+res); nativeClasses.addElement(res); if (Integer.parseInt(id) < lowestNativeId) lowestNativeId = Integer.parseInt(id); // save locally for further method processing fullClassName = value; fullClassId = Integer.parseInt(id); } // method entry else if (name.equalsIgnoreCase("method") && (token_cnt == 3)) { if (value.indexOf(':') == -1) { System.out.println("Invalid method reference"); System.exit(-1); } if (fullClassName == null) { System.out.println("Method reference before class"); System.exit(-1); } // seperate class, method and type NativeMethod nativeMethod = new NativeMethod(); nativeMethod.className = fullClassName; nativeMethod.name = value.substring(0, value.indexOf(':')); nativeMethod.type = value.substring( value.indexOf(':') + 1, value.length()); nativeMethod.id = Integer.parseInt(id) + (fullClassId << 8); Tracer.debug("Found "+nativeMethod); nativeMethods.addElement(nativeMethod); } // field entry else if (name.equalsIgnoreCase("field") && (token_cnt == 3)) { if (value.indexOf(':') == -1) { System.out.println("Invalid field reference"); System.exit(-1); } if (fullClassName == null) { System.out.println("Method reference before class"); System.exit(-1); } // seperate class, method and type NativeField nativeField = new NativeField(); nativeField.className = fullClassName; nativeField.name = value.substring(0, value.indexOf(':')); nativeField.type = value.substring( value.indexOf(':') + 1, value.length()); nativeField.id = Integer.parseInt(id) + (fullClassId << 8); nativeFields.addElement(nativeField); } else { System.out.println("Unknown entry in native file: " + name + " + " + token_cnt + " parms"); System.exit(-1); } } } } catch (IOException e) { System.out.println(e.toString()); throw new ClassNotFoundException("Error at native reading "+className, e); } if(res == null){ throw new ClassNotFoundException("Native class not found "+className); } return res; } }