PageRenderTime 24ms CodeModel.GetById 1ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/kilim/WeavingClassLoader.java

http://github.com/kilim/kilim
Java | 294 lines | 216 code | 35 blank | 43 comment | 53 complexity | 3cd501be1d0a435ea1fb1365923b9ec7 MD5 | raw file
  1// copyright 2016 nqzero, 2014 sriram srinivasan - offered under the terms of the MIT License
  2
  3package kilim;
  4
  5import java.io.ByteArrayInputStream;
  6import java.io.ByteArrayOutputStream;
  7import java.io.DataInputStream;
  8import java.io.File;
  9import java.io.IOException;
 10import java.io.InputStream;
 11import java.lang.invoke.MethodHandle;
 12import java.lang.invoke.MethodHandles;
 13import java.lang.invoke.MethodType;
 14import java.lang.reflect.Method;
 15import java.net.URL;
 16import java.net.URLClassLoader;
 17import java.security.CodeSigner;
 18import java.security.CodeSource;
 19import java.security.ProtectionDomain;
 20import java.util.ArrayList;
 21import java.util.HashMap;
 22
 23import kilim.analysis.ClassInfo;
 24import kilim.analysis.ClassWeaver;
 25import kilim.analysis.FileLister;
 26import kilim.analysis.KilimContext;
 27import kilim.mirrors.CachedClassMirrors;
 28import kilim.tools.Agent;
 29import kilim.tools.Weaver;
 30
 31// TODO: this and related real-time-weaving classes could be moved to the analysis package
 32//       allowing the ant kilim-runtime.jar (see build.xml) to be smaller
 33
 34/**
 35 * Classloader that loads classes from the classpath spec given by the system property
 36 * "kilim.class.path" and weaves them dynamically.
 37 */
 38public class WeavingClassLoader extends KilimClassLoader {
 39    public static final String KILIM_CLASSPATH = "kilim.class.path";
 40    Weaver weaver;
 41    
 42    URLClassLoader proxy;
 43
 44    public static byte [] readFully(InputStream is) {
 45        try {
 46            ByteArrayOutputStream buffer = new ByteArrayOutputStream();
 47            int num;
 48            byte[] data = new byte[1 << 12];
 49            while ((num = is.read( data, 0, data.length )) != -1)
 50                buffer.write(data, 0, num);
 51            buffer.flush();
 52            return buffer.toByteArray();
 53        }
 54        catch (IOException ex) { return null; }
 55    }
 56
 57    ClassLoader pcl;
 58    static ClassLoader platform;
 59    static {
 60        ClassLoader last, cl = WeavingClassLoader.class.getClassLoader();
 61        for (last = cl; cl != null; cl = cl.getParent())
 62            last = cl;
 63        platform = last;
 64    }
 65
 66    public InputStream getResourceAsStream(String cname) {
 67        if (cname==null || !cname.endsWith(".class") || proxy(cname))
 68            return super.getResourceAsStream(cname);
 69        String name = unmakeResourceName(cname);
 70        InputStream is = getByteStream(pcl,name,cname,false);
 71
 72        if (is==null) return null;
 73
 74        byte [] code = null;
 75        ClassWeaver cw = weaver.weave(is);
 76        for (ClassInfo ci : cw.getClassInfos())
 77            if (ci.className.equals(name)) code = ci.bytes;
 78        if (code==null) code = cw.classFlow.code;
 79        return code==null ? null : new ByteArrayInputStream(code);
 80    }
 81
 82    public static URL [] getURLs(String [] classPaths) {
 83        ArrayList<URL> urls = new ArrayList<URL>();
 84        for (String name : classPaths) {
 85            name = name.trim();
 86            if (name.equals("")) continue;
 87            try { urls.add(new File(name).toURI().toURL()); }
 88            catch (IOException ioe) {
 89                // System.err.println( "'" + name + "' does not exist. See property " +
 90                // KILIM_CLASSPATH);
 91            }
 92        }
 93
 94        URL [] paths = urls.toArray(new URL[0]);
 95        return paths;
 96    }
 97
 98    
 99    public WeavingClassLoader() {
100        this(null,getProxy());
101    }
102    private static URLClassLoader getProxy() {
103        String classPath = System.getProperty(KILIM_CLASSPATH, "");
104        String[] classPaths = classPath.split(":");
105        URL [] paths = getURLs(classPaths);
106        return paths.length > 0 ? new URLClassLoader(paths) : null;
107    }
108    public WeavingClassLoader(ClassLoader loader,URLClassLoader $proxy) {
109        proxy = $proxy;
110        pcl = proxy != null
111                ? proxy
112                : loader != null ? loader:getClass().getClassLoader();
113
114        CachedClassMirrors ccm = new CachedClassMirrors(pcl);
115        KilimContext kc = new KilimContext(ccm);
116        weaver = new Weaver(kc);
117    }
118    /** run static method className.method(args) using reflection and this WeavingClassLoader */
119    public void run(String className,String method,String ... args) throws Exception {
120        Class<?> mainClass = loadClass(className);
121        Method mainMethod = mainClass.getMethod(method, new Class[]{String[].class});
122        mainMethod.invoke(null,new Object[] {args});
123    }
124
125    /** 
126     * replace the exclude filter which determines whether a named class should be defined
127     * definitively by this loader or instead delegated to the parent loader.
128     * this is sometimes necessary to work with classes that may have been previously loaded,
129     * eg because they were used as a java agent
130     * @param exclude the filter to run
131     * @return 
132     */
133    public WeavingClassLoader exclude(Excludable exclude) { checker = exclude; return this; }
134    public static interface Excludable {
135        /**
136         * check whether the named class should be loaded by the parent loader
137         * @param name the name of the class to check
138         * @return true if this class loader should delegate to the parent loader
139         */
140        boolean check(String name);
141    }
142    private Excludable checker;
143    
144    
145    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
146        Class klass = null;
147        try { klass = platform.loadClass(name); } catch (ClassNotFoundException ex) {}
148        if (klass==null)
149            klass = findLoadedClass(name);
150        if (klass==null & (checker != null && checker.check(name)))
151            klass = pcl.loadClass(name);
152        if (klass==null) synchronized (this) {
153            klass = findLoadedClass(name);
154            if (klass==null)
155                klass = findClass( name );
156        }
157        if (resolve) resolveClass( klass );
158        return klass;
159    }
160    private boolean proxy(String cname) { 
161        return proxy != null && proxy.findResource(cname) == null;
162    }
163    
164    private Class defineAll(String name,ClassWeaver cw) {
165        Class ret = null;
166        for (ClassInfo ci : cw.getClassInfos()) {
167            if (findLoadedClass(ci.className)==null) {
168                Class<?> c = define(ci.className, ci.bytes);
169                if      (ci.className.equals(name))          ret = c;
170                else if (ci.className.startsWith("kilim.S")) resolveClass(c);
171            }
172        }
173        return ret;
174    }
175
176    public static InputStream getByteStream(ClassLoader cl,String name,String cname,boolean system) {
177        InputStream is = cl.getResourceAsStream( cname );
178        if (is==null && system) is = ClassLoader.getSystemResourceAsStream( cname );
179        if (is==null & Agent.map != null) {
180            // force the nominal class loader to load the bytecode so that the agent sees it
181            try { cl.loadClass(name); }
182            catch (Exception ex) {}
183            byte [] bytes = Agent.map.get(cname);
184            if (bytes != null) is = new ByteArrayInputStream(bytes);
185        }
186        return is;
187    }
188    
189    /**
190     * load the bytecode for a class of a given name from the classpath and weave it
191     * @param name the fully qualified class name
192     * @return the weaver
193     */    
194    public ClassWeaver weaveClass(String name) {
195        String cname = makeResourceName(name);
196        InputStream is = getByteStream(pcl,name,cname,true);
197        ClassWeaver cw = weaver.weave(is);
198        return cw;
199    }
200    protected Class<?> findClass(String name) throws ClassNotFoundException {
201        String cname = makeResourceName(name);
202        
203        InputStream is = getByteStream(pcl,name,cname,true);
204        ClassWeaver cw;
205        byte [] code;
206
207        if (is==null) {}
208        else if (proxy(cname)) {
209            if ((code=readFully(is)) != null)
210                return define(name,code);
211        }
212        else if ((cw = weaver.weave(is)) != null) {
213            Class<?> ret = defineAll(name,cw);
214            return ret==null ? define(name, cw.classFlow.code) : ret;
215        }
216        throw new ClassNotFoundException();
217    }
218
219    private final HashMap<URL, ProtectionDomain> cache = new HashMap<URL, ProtectionDomain>();
220    private ProtectionDomain get(String name) {
221        URL url = url(name);
222        if (url==null) return null;
223
224        ProtectionDomain pd = null;
225        synchronized (cache) {
226            pd = cache.get(url);
227            if (pd == null) {
228                CodeSource cs = new CodeSource(url,(CodeSigner []) null);
229                pd = new ProtectionDomain(cs, null, this, null);
230                cache.put(url, pd);
231            }
232        }
233        return pd;
234    }
235    
236    public Class<?> define(String name,byte [] code) {
237        CachedClassMirrors.ClassMirror cm = null;
238        return defineClass(name, code, 0, code.length, get(name));
239    }
240
241    /**
242     * convert a fully qualified class name to a resource name. Note: the Class and ClassLoader
243     * javadocs don't explicitly specify the string formats used for the various methods, so
244     * this conversion is potentially fragile
245     * @param name as returned by Class.getName
246     * @return the name in a format suitable for use with the various ClassLoader.getResource methods
247     */
248    // https://docs.oracle.com/javase/8/docs/technotes/guides/lang/resources.html#res_names
249    public static String makeResourceName(String name) { return name.replace('.','/') + ".class"; }
250    public static String unmakeResourceName(String cname) { return cname.replace(".class","").replace('/','.'); }
251    
252    /** 
253     * read bytecode for the named class from a source classloader
254     * @param loader the classloader to get the bytecode from, or null for the current classloader
255     * @param name the internal name for the class as would be passed to loadClass
256     * @return a new instance, or null if the bytecode is not found
257     */
258    public static byte [] findCode(ClassLoader loader,String name) {
259        if (loader==null) loader = WeavingClassLoader.class.getClassLoader();
260        String cname = makeResourceName(name);
261        InputStream is = getByteStream(loader,name,cname,true);
262        if (is != null)
263            return readFully(is);
264        return null;
265    }
266    public URL url(String name) {
267        String cname = makeResourceName(name);
268        URL url = pcl.getResource( cname ), ret = null;
269        if (url==null) url = ClassLoader.getSystemResource( cname );
270        if (url==null) return null;
271        String path = url.getPath();
272
273        boolean matches = path.endsWith(cname);
274        assert matches : "url code source doesn't match expectation: " + name + ", " + path;
275        if (! matches) return null;
276        
277        try {
278            ret = new URL(url,path.replace(cname,""));
279        }
280        catch (Exception ex) {}
281        return ret;
282    }
283
284
285    
286    public static byte[] readFully(FileLister.Entry fe) throws IOException {
287        DataInputStream in = new DataInputStream(fe.getInputStream());
288        byte[] contents = new byte[(int)fe.getSize()];
289        in.readFully(contents);
290        in.close();
291        return contents;
292    }
293
294}