java/ch/wlkl/wsh/JavaCmpLoad.java

package ch.wlkl.wsh;

import static ch.wlkl.wsh.Top.sFail;

import java.io.File;
import java.io.FileInputStream;
import java.io.PrintStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import javax.tools.Tool;

public class JavaCmpLoad<T> extends ClassLoader {

    public final JavaCmpLoad<?> parent;
    public final File root;
    public final boolean temporary;
    public final Object pckg;
    public final Class<T> superCla;

    private int uniqueNum = 0;
    private Compile compile = makeCompile();
    
    public JavaCmpLoad(JavaCmpLoad<?> pa, Class<T> sup) {
        this(pa, null, null, null, sup);
    }

    @SuppressWarnings("unchecked")
    public JavaCmpLoad(JavaCmpLoad<?> par, String rt, Boolean tmp, Object pac, Class<T> sup) {
        root = Ut.canonical(new File(rt != null ? rt : par != null ? par.root.getAbsolutePath() : "."));
        temporary = tmp != null ? tmp : par != null ? par.temporary : false;
        pckg = pac != null ? pac : par != null ? par.pckg : "temporary.shell";
        superCla = sup != null ? sup : par != null ? (Class<T>) par.superCla : null;
        while (par != null && par.parent != null)
            par = par.parent;
        parent = par;
        if (! (root.exists() && root.isDirectory()))
            root.mkdirs();
    }

    public String toString() {
        return this.getClass().getName() + "(" + (parent == null ? "uniqueNum=" + uniqueNum : "parent.uniqueNum=" + parent.uniqueNum)+ ", root=" + root + ", temporary=" + temporary + ")";
    }

    public static String className(Object o) {
        try {
            return (String) o;
        } catch (ClassCastException e) {
            return ((Class<?>) o).getSimpleName();
        }
    }
    /** the imports for each class, an element i of the array is translated to the following source:
     * <ul>
     * <li> import ((String) i) ;
     * <li> import (((Class) i).getName()) ;
     * <li> import (((Package) i).getName()).* ;
     * </ul> */
    
    public static CharSequence[] classSource(Object pk, Object[] imports, String cla, Object sup, Object[] implem, CharSequence... body) {
        ArrayList<CharSequence> l = new ArrayList<CharSequence>();
        String pr = null;
        l.add("package " + pk + ';');
        for (Object i: imports) {
            try {
                pr = (String) i;
            } catch (ClassCastException e1) {
                try {
                    pr = ((Class<?>) i).getName();
                } catch (ClassCastException e2) {
                    try {
                        pr = ((Package) i).getName() + ".*";
                    } catch (ClassCastException e3) {
                        sFail("bad javaImport[?] " + i);
                    }                        
                }                    
            }
            l.add("\nimport " + pr + ';');
        }
        l.add("\npublic class " + cla);
        if (sup != null && ! sup.equals(""))
            l.add(" extends " + className(sup));
        pr = " implements ";
        for (Object e : implem) {
            if (e != null && ! e.equals("")) {
                l.add(pr + className(e));
                pr = ", ";
            }                
        }
        l.add(" {");
        for (CharSequence s: body) {
            if (s != null) {
                if (s.length() > 0 && s.charAt(0) != '\n')
                    l.add("\n");
                l.add(s);
            }
        }
        l.add("\n}");
        return l.toArray(new CharSequence[l.size()]);
    }

    public CharSequence[] classSource(Object[] imports, String cla, Object[] implem, CharSequence... body) {
        return classSource(pckg, imports, cla, superCla, implem, body);
    }

    @SuppressWarnings("unchecked")
    public <C> Class<? extends C> makeClass(File rt, Boolean tmp, Object pk, String cla, Class<C> sup, CharSequence... source) {
        int dx = cla.lastIndexOf('.');
        rt = rt == null ? root : rt;
        sup = sup == null ? (Class<C>) superCla : sup;
        if (dx >= 0) {
            pk = cla.substring(0, dx);
            cla = cla.substring(dx+1);
        }
        tmp = tmp != null ? tmp : pk != null ? false : temporary;
        pk = pk == null ? pckg : pk;
        File dir = new File(rt, pk.toString().replace('.', '/'));
        File srcFile = new File(dir, cla + ".java");
        File claFile = new File(dir, cla + ".class");
        if (! dir.isDirectory())
            if (! dir.mkdirs())
                sFail("could not create directory " + dir);
        try {
            srcFile.delete();
            claFile.delete();
            PrintStream ps = new PrintStream(srcFile);
            for(CharSequence s : source)
                ps.print(s);
            ps.println("");
            ps.close();
//            String args = "-source 5.0 -warn:-unchecked,unused " + srcFile.getAbsolutePath();
            Compile compile = makeCompile();

            boolean res = compile.compile(srcFile, "");
//            boolean res = org.eclipse.jdt.internal.compiler.batch.Main.compile(args);
//            int res = com.sun.tools.javac.Main.compile(args, new java.io.PrintWriter(System.out));
            if (! res )
                sFail("compile rc " + res + " errors for class " + pk + '.' + cla);
            FileInputStream f = new FileInputStream(claFile);
            int len = f.available();
            byte[] byteCode = new byte[len];
            f.read(byteCode);
            f.close();
            Class<? extends C> cl = defineClass(pk.toString() + '.' + cla, byteCode, 0, len).asSubclass(sup);
            if (tmp) {
                srcFile.delete();
                claFile.delete(); 
            }
            return cl;
        } catch (Exception e) {
            e.printStackTrace();
            sFail("define class " + pk + '.' + cla + " in dir " + dir);
            return null;
        } 
    }

    public boolean compile(File src, String opt) {
        try {
            return compile.compile(src, opt);
        } catch (Exception e) {
            System.out.println("error: compile caught exception: " + e);
            return false;
        } 
    }

    public Class<? extends T> makeClass(String cla, CharSequence... source) {
        return makeClass(null, null, null, cla, null, source);
    }

    public int uniqueNum() {
        return parent == null ? ++uniqueNum : parent.uniqueNum();
    }

    public static String uniqueName(String pref, int i) {
        return pref + i;
    }

    public String uniqueName(String pref) {
        return uniqueName(pref, uniqueNum());
    }

    public Compile makeCompile () {
        String s = "";
        try {
            return new CompileEclipse();
        } catch (Throwable e) {
            s += "\nEclipse: " + e;
        }
        try {
            return new CompileSun();
        } catch (Throwable e) {
            s += "\nSun: " + e;
        }
        try {
                    return new CompileTool();
        } catch (Throwable e) {
            s += "\nTool: " + e;
        }
        sFail("no compiler found " + s);
        return null;
    }

    abstract class Compile extends Top {
        abstract public boolean compile(File src, String opt) throws Exception;
    }

    class CompileTool extends Compile {
        public boolean compile(File src, String opt) throws Exception {
        String[] args = ("-source 8 -Xlint:-unchecked  -d " + root + ' ' + opt + ' ' +src.getAbsolutePath()).split(" +");
                Tool comp = javax.tools.ToolProvider.getSystemJavaCompiler();
                int res =  comp.run(null, null, null, args);
                return res == 0;
                }
        }
        
        class CompileEclipse extends Compile {
        final Method met;
        public CompileEclipse() throws Throwable {
            // compiler im März12 in /usr/share/eclipse362/plugins/org.eclipse.jdt.core_3.6.2.v_A76_R36x.jar
            // compiler im Jan13 in /usr/lib/eclipse/plugins/org.eclipse.jdt.core_3.7.3.dist.jar
            met = Class.forName("org.eclipse.jdt.internal.compiler.batch.Main").getDeclaredMethod("compile", new Class[] {String.class});
            if (met.getReturnType() != Boolean.TYPE)
                fail("bad returnType " + met.getReturnType() + " for method " + met);
        }

        public boolean compile(File src, String opt) throws Exception {
            String args = "-source 8.0 -warn:-unchecked,unused " + opt + ' ' + src.getAbsolutePath();
            return (Boolean) met.invoke(null, (Object) args);
        }
    }

    class CompileSun extends Compile {
        final Method met;
        public CompileSun()throws Throwable {
            met = Class.forName("com.sun.tools.javac.Main").getDeclaredMethod("compile", String[].class, java.io.PrintWriter.class ); //, new Class[] {String.class});
            if (met.getReturnType() != Integer.TYPE)
                fail("bad returnType " + met.getReturnType() + " for method " + met);
        }

        public boolean compile(File src, String opt) throws Exception {
            return 0 == (Integer) met.invoke(null, new String[] {src.getAbsolutePath()}, new java.io.PrintWriter(System.out));
        }
    }
}