// :tabSize=4:indentSize=4:noTabs=false:folding=explicit:collapseFolds=1: // {{{ imports import java.io.*; import java.io.File; import java.net.URL; import java.net.URLClassLoader; import java.util.HashMap; import java.util.Vector; import java.util.Enumeration; import java.util.Iterator; import java.util.StringTokenizer; import java.util.zip.*; // }}} /** * Class loader used internally by rJava * * The class manages the class paths and the native libraries (jri, ...) */ public class RJavaClassLoader extends URLClassLoader { // {{{ fields /** * path of RJava */ String rJavaPath ; /** * lib sub directory of rJava */ String rJavaLibPath; /** * map of libraries */ HashMap/**/ libMap; /** * The class path vector */ Vector/**/ classPath; /** * singleton */ public static RJavaClassLoader primaryLoader = null; /** * Print debug messages if is set to true */ public static boolean verbose = false; /** * Should the system class loader be used to resolve classes * as well as this class loader */ public boolean useSystem = true; // }}} // {{{ UnixFile class /** * Light extension of File that handles file separators and updates */ class UnixFile extends File { /** * cached "last time modified" stamp */ long lastModStamp; /** * Constructor. Modifies the path so that * the proper path separator is used (most useful on windows) */ public UnixFile(String fn) { super( u2w(fn) ) ; lastModStamp=0; } /** * @return whether the file modified since last time the update method was called */ public boolean hasChanged() { long curMod = lastModified(); return (curMod != lastModStamp); } /** * Cache the result of the lastModified stamp */ public void update() { lastModStamp = lastModified(); } } // }}} // {{{ UnixJarFile /** * Specialization of UnixFile that deals with jar files */ class UnixJarFile extends UnixFile { /** * The cached jar file */ private ZipFile zfile ; /** * common prefix for all URLs within this jar file */ private String urlPrefix ; public UnixJarFile( String filename ){ super( filename ); } /* @Override */ public void update(){ try { if (zfile != null){ zfile.close(); } zfile = new ZipFile( this ) ; } catch (Exception tryCloseX) {} /* time stamp */ super.update( ) ; } /** * Get an input stream for a resource contained in the jar file * * @param name file name of the resource within the jar file * @return an input stream representing the resouce if it exists or null */ public InputStream getResourceAsStream( String name ){ if (zfile==null || hasChanged()) { update(); } try { if (zfile == null) return null; ZipEntry e = zfile.getEntry(name); if (e != null) return zfile.getInputStream(e); } catch(Exception e) { if (verbose) System.err.println("RJavaClassLoader$UnixJarFile: exception: "+e.getMessage()); } return null; } public URL getResource(String name ){ if( zfile == null || zfile.getEntry( name ) == null ){ return null ; } URL u = null ; if( urlPrefix == null ){ try{ urlPrefix = "jar:" + toURL().toString() + "!" ; } catch( java.net.MalformedURLException ex){ } } try{ u = new URL( urlPrefix + name ) ; } catch( java.net.MalformedURLException ex ){ /* not to worry */ } return u ; } } // }}} // {{{ UnixDirectory class /** * Specialization of UnixFile representing a directory */ /* it is not really a specialization but makes possible to dispatch on instanceof*/ class UnixDirectory extends UnixFile { public UnixDirectory( String dirname ){ super( dirname ) ; } } // }}} // {{{ getPrimaryLoader /** * Returns the singleton instance of RJavaClassLoader */ public static RJavaClassLoader getPrimaryLoader() { return primaryLoader; } // }}} // {{{ constructor /** * Constructor. The first time an RJavaClassLoader is created, it is * cached as the primary loader. * * @param path path of the rJava package * @param libpath lib sub directory of the rJava package */ public RJavaClassLoader(String path, String libpath) { this(path, libpath, null); } /** * Constructor. The first time an RJavaClassLoader is created, it is * cached as the primary loader. * * @param path path of the rJava package * @param libpath lib sub directory of the rJava package * @param parent parent loader if we should fall back upstream or NULL */ public RJavaClassLoader(String path, String libpath, RJavaClassLoader parent) { super(new URL[] {}, (parent == null) ? ClassLoader.getSystemClassLoader() : parent); // respect rJava.debug level String rjd = System.getProperty("rJava.debug"); if (rjd != null && rjd.length() > 0 && !rjd.equals("0")) verbose = true; if (verbose) System.out.println("RJavaClassLoader(\""+path+"\", \""+libpath+"\", "+ ((parent == null) ? "no parent" : parent) + ")"); if (primaryLoader==null) { primaryLoader = this; if (verbose) System.out.println(" - primary loader"); } else { if (verbose) System.out.println(" - NOT primrary (this="+this+", primary="+primaryLoader+")"); } libMap = new HashMap/**/(); classPath = new Vector/**/(); classPath.add(new UnixDirectory(path+"/java")); rJavaPath = path; rJavaLibPath = libpath; /* load the rJava library */ UnixFile so = new UnixFile(rJavaLibPath+"/rJava.so"); if (!so.exists()) so = new UnixFile(rJavaLibPath+"/rJava.dll"); if (so.exists()) libMap.put("rJava", so); /* load the jri library */ UnixFile jri = new UnixFile(path+"/jri/libjri.so"); String rarch = System.getProperty("r.arch"); if (rarch != null && rarch.length()>0) { UnixFile af = new UnixFile(path+"/jri"+rarch+"/libjri.so"); if (af.exists()) jri = af; else { af = new UnixFile(path+"/jri"+rarch+"/jri.dll"); if (af.exists()) jri = af; } } if (!jri.exists()) jri = new UnixFile(path+"/jri/libjri.jnilib"); if (!jri.exists()) jri = new UnixFile(path+"/jri/jri.dll"); if (jri.exists()) { libMap.put("jri", jri); if (verbose) System.out.println(" - registered JRI: "+jri); } /* if we are the primary loader, make us the context loader so projects that rely on the context loader pick us */ if (primaryLoader == this) Thread.currentThread().setContextClassLoader(this); if (verbose) { System.out.println("RJavaClassLoader initialized.\n\nRegistered libraries:"); for(Iterator entries = libMap.keySet().iterator(); entries.hasNext(); ) { Object key = entries.next(); System.out.println(" " + key + ": '" + libMap.get(key) + "'"); } System.out.println("\nRegistered class paths:"); for (Enumeration e = classPath.elements() ; e.hasMoreElements() ;) System.out.println(" '"+e.nextElement()+"'"); System.out.println("\n-- end of class loader report --"); } } // }}} // {{{ classNameToFile /** * convert . to / */ String classNameToFile(String cls) { return cls.replace('.','/'); } // }}} // {{{ findClass protected Class findClass(String name) throws ClassNotFoundException { Class cl = null; if (verbose) System.out.println(""+this+".findClass("+name+")"); if ("RJavaClassLoader".equals(name)) return getClass(); // {{{ use the usual method of URLClassLoader if (useSystem) { try { cl = super.findClass(name); if (cl != null) { if (verbose) System.out.println("RJavaClassLoader: found class "+name+" using URL loader"); return cl; } } catch (Exception fnf) { if (verbose) System.out.println(" - URL loader did not find it: " + fnf); } } if (verbose) System.out.println("RJavaClassLoader.findClass(\""+name+"\")"); // }}} // {{{ iterate through the elements of the class path InputStream ins = null; Exception defineException = null; Enumeration/**/ e = classPath.elements() ; while( e.hasMoreElements() ){ UnixFile cp = (UnixFile) e.nextElement(); if (verbose) System.out.println(" - trying class path \""+cp+"\""); try { ins = null; /* a file - assume it is a jar file */ if (cp instanceof UnixJarFile){ ins = ((UnixJarFile)cp).getResourceAsStream( classNameToFile(name) + ".class" ) ; if (verbose) System.out.println(" JAR file, can get '" + classNameToFile(name) + "'? " + ((ins == null) ? "NO" : "YES")); } else if ( cp instanceof UnixDirectory ){ UnixFile class_f = new UnixFile(cp.getPath()+"/"+classNameToFile(name)+".class"); if (class_f.isFile() ) { ins = new FileInputStream(class_f); } if (verbose) System.out.println(" Directory, can get '" + class_f + "'? " + ((ins == null) ? "NO" : "YES")); } /* some comments on the following : we could call ZipEntry.getSize in case of a jar file to find out the size of the byte[] directly also ByteBuffer seems more efficient, but the ByteBuffer class is java >= 1.4 and the defineClass method that uses the class is java >= 1.5 */ if (ins != null) { int al = 128*1024; byte fc[] = new byte[al]; int n = ins.read(fc); int rp = n; if( verbose ) System.out.println(" loading class file, initial n = "+n); while (n > 0) { if (rp == al) { int nexa = al*2; if (nexa<512*1024) nexa=512*1024; byte la[] = new byte[nexa]; System.arraycopy(fc, 0, la, 0, al); fc = la; al = nexa; } n = ins.read(fc, rp, fc.length-rp); if( verbose ) System.out.println(" next n = "+n+" (rp="+rp+", al="+al+")"); if (n>0) rp += n; } ins.close(); n = rp; if (verbose) System.out.println("RJavaClassLoader: loaded class "+name+", "+n+" bytes"); try { cl = defineClass(name, fc, 0, n); } catch (Exception dce) { // we want to save this one so we can pass it on defineException = dce; break; } if (verbose) System.out.println(" defineClass('" + name +"') returned " + cl); // System.out.println(" - class = "+cl); return cl; } } catch (Exception ex) { // System.out.println(" * won't work: "+ex.getMessage()); } } // }}} if (defineException != null) // we bailed out on class interpretation, re-throw it throw (new ClassNotFoundException("Class not found - candidate class binary found but could not be loaded", defineException)); // giving up if (cl == null) { if( verbose ) System.out.println(" >> ClassNotFoundException "); throw (new ClassNotFoundException()); } return cl; } // }}} // {{{ findResource public URL findResource(String name) { if (verbose) System.out.println("RJavaClassLoader: findResource('"+name+"')"); // {{{ use the standard way if (useSystem) { try { URL u = super.findResource(name); if (u != null) { if (verbose) System.out.println("RJavaClassLoader: found resource in "+u+" using URL loader."); return u; } } catch (Exception fre) { } } // }}} // {{{ iterate through the classpath if (verbose) System.out.println(" - resource not found with URL loader, trying alternative"); Enumeration/**/ e = classPath.elements() ; while( e.hasMoreElements()) { UnixFile cp = (UnixFile) e.nextElement(); try { /* is a file - assume it is a jar file */ if (cp instanceof UnixJarFile ) { URL u = ( (UnixJarFile)cp ).getResource( name ) ; if (u != null) { if (verbose) System.out.println(" - found in a JAR file, URL "+u); return u; } } else if(cp instanceof UnixDirectory ) { UnixFile res_f = new UnixFile(cp.getPath()+"/"+name); if (res_f.isFile()) { if (verbose) System.out.println(" - find as a file: "+res_f); return res_f.toURL(); } } } catch (Exception iox) { } } // }}} return null; } // }}} // {{{ addRLibrary /** add a library to path mapping for a native library */ public void addRLibrary(String name, String path) { libMap.put(name, new UnixFile(path)); } // }}} // {{{ addClassPath /** * adds an entry to the class path */ public void addClassPath(String cp) { UnixFile f = new UnixFile(cp); // use the URLClassLoader if (useSystem) { try { addURL(f.toURL()); if (verbose) System.out.println("RJavaClassLoader: added '" + cp + "' to the URL class path loader"); //return; // we need to add it anyway so it appears in .jclassPath() } catch (Exception ufe) { } } UnixFile g = null ; if( f.isFile() && (f.getName().endsWith(".jar") || f.getName().endsWith(".JAR"))) { g = new UnixJarFile(cp) ; if (verbose) System.out.println("RJavaClassLoader: adding Java archive file '"+cp+"' to the internal class path"); } else if( f.isDirectory() ){ g = new UnixDirectory(cp) ; if (verbose) System.out.println("RJavaClassLoader: adding class directory '"+cp+"' to the internal class path"); } else if (verbose) System.err.println(f.exists() ? ("WARNING: the path '"+cp+"' is neither a directory nor a .jar file, it will NOT be added to the internal class path!") : ("WARNING: the path '"+cp+"' does NOT exist, it will NOT be added to the internal class path!")); if (g != null && !classPath.contains(g)) { // this is the real meat - add it to our internal list classPath.add(g); // this is just cosmetics - it doesn't really have any meaning System.setProperty("java.class.path", System.getProperty("java.class.path")+File.pathSeparator+g.getPath()); } } /** * adds several entries to the class path */ public void addClassPath(String[] cp) { int i = 0; while (i < cp.length) addClassPath(cp[i++]); } // }}} // {{{ getClassPath /** * @return the array of class paths used by this class loader */ public String[] getClassPath() { int j = classPath.size(); String[] s = new String[j]; int i = 0; while (i < j) { s[i] = ((UnixFile) classPath.elementAt(i)).getPath(); i++; } return s; } // }}} // {{{ findLibrary protected String findLibrary(String name) { if (verbose) System.out.println("RJavaClassLoader.findLibrary(\""+name+"\")"); //if (name.equals("rJava")) // return rJavaLibPath+"/"+name+".so"; UnixFile u = (UnixFile) libMap.get(name); String s = null; if (u!=null && u.exists()) s=u.getPath(); if (verbose) System.out.println(" - mapping to "+((s==null)?"":s)); return s; } // }}} // {{{ bootClass /** * Boots the specified method of the specified class * * @param cName class to boot * @param mName method to boot (typically main). The method must take a String[] as parameter * @param args arguments to pass to the method */ public void bootClass(String cName, String mName, String[] args) throws java.lang.IllegalAccessException, java.lang.reflect.InvocationTargetException, java.lang.NoSuchMethodException, java.lang.ClassNotFoundException { Class c = findClass(cName); resolveClass(c); java.lang.reflect.Method m = c.getMethod(mName, new Class[] { String[].class }); m.invoke(null, new Object[] { args }); } // }}} // {{{ setDebug /** * Set the debug level. At the moment, there is only verbose (level > 0) * or quiet * * @param level debug level. verbose (>0), quiet otherwise */ public static void setDebug(int level) { verbose=(level>0); } // }}} // {{{ u2w /** * Utility to convert paths for windows. Converts / to the path separator in use * * @param fn file name */ public static String u2w(String fn) { return (File.separatorChar != '/') ? fn.replace('/', File.separatorChar) : fn ; } // }}} // {{{ main /** * main method * *

This uses the system properties:

*
    *
  • rjava.path : path of the rJava package
  • *
  • rjava.lib : lib sub directory of the rJava package
  • *
  • main.class : main class to "boot", assumes Main if not specified
  • *
  • rjava.class.path : set of paths to populate the initiate the class path
  • *
* *

and boots the "main" method of the specified main.class, * passing the args down to the booted class

* *

This makes sure R and rJava are known by the class loader

*/ public static void main(String[] args) { String rJavaPath = System.getProperty("rjava.path"); if (rJavaPath == null) { System.err.println("ERROR: rjava.path is not set"); System.exit(2); } String rJavaLib = System.getProperty("rjava.lib"); if (rJavaLib == null) { // it is not really used so far, just for rJava.so, so we can guess rJavaLib = rJavaPath + File.separator + "libs"; } RJavaClassLoader cl = new RJavaClassLoader(u2w(rJavaPath), u2w(rJavaLib)); String mainClass = System.getProperty("main.class"); if (mainClass == null || mainClass.length()<1) { System.err.println("WARNING: main.class not specified, assuming 'Main'"); mainClass = "Main"; } String classPath = System.getProperty("rjava.class.path"); if (classPath != null) { StringTokenizer st = new StringTokenizer(classPath, File.pathSeparator); while (st.hasMoreTokens()) { String dirname = u2w(st.nextToken()); cl.addClassPath(dirname); } } try { cl.bootClass(mainClass, "main", args); } catch (Exception ex) { System.err.println("ERROR: while running main method: "+ex); ex.printStackTrace(); } } // }}} //----- tools ----- // {{{ RJavaObjectInputStream class class RJavaObjectInputStream extends ObjectInputStream { public RJavaObjectInputStream(InputStream in) throws IOException { super(in); } protected Class resolveClass(ObjectStreamClass desc) throws ClassNotFoundException { return Class.forName(desc.getName(), false, RJavaClassLoader.getPrimaryLoader()); } } // }}} // {{{ toByte /** * Serialize an object to a byte array. (code by CB) * * @param object object to serialize * @return byte array that represents the object * @throws Exception */ public static byte[] toByte(Object object) throws Exception { ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream((OutputStream) os); oos.writeObject(object); oos.close(); return os.toByteArray(); } // }}} // {{{ toObject /** * Deserialize an object from a byte array. (code by CB) * * @param byteArray * @return the object that is represented by the byte array * @throws Exception */ public Object toObject(byte[] byteArray) throws Exception { InputStream is = new ByteArrayInputStream(byteArray); RJavaObjectInputStream ois = new RJavaObjectInputStream(is); Object o = (Object) ois.readObject(); ois.close(); return o; } // }}} // {{{ toObjectPL /** * converts the byte array into an Object using the primary RJavaClassLoader */ public static Object toObjectPL(byte[] byteArray) throws Exception{ return RJavaClassLoader.getPrimaryLoader().toObject(byteArray); } // }}} }