package cn.bran.japid.rendererloader; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import cn.bran.japid.template.JapidRenderer; import cn.bran.japid.template.JapidTemplateBaseWithoutPlay; import cn.bran.japid.util.JapidFlags; /** * The template class loader that detects changes and recompile on the fly. * * 1. whenever changes detected, clear the global class cache. 2. Only redefine * the class to load, which will lead to define all the dependencies. All the * dependencies must be defined by the same class classloader or * InvalidAccessException. 3. The main program will call the loadClass once for * each of the classes defined in one classloader. * * * @author Bing Ran<bing_ran@hotmail.com> * */ public class TemplateClassLoader extends ClassLoader { // the per classloader class cache private Map<String, Class<?>> localClasses = new ConcurrentHashMap<String, Class<?>>(); private ClassLoader parentClassLoader; public TemplateClassLoader(ClassLoader parentClassLoader) { super(TemplateClassLoader.class.getClassLoader()); // super(parentClassLoader); this.parentClassLoader = parentClassLoader; } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { if (!name.startsWith(JapidRenderer.JAPIDVIEWS)) { Class<?> cl = parentClassLoader.loadClass(name); if (cl != null) { return cl; } Class<?> superClass = super.loadClass(name); return superClass; } else { String oid = "[TemplateClassLoader@" + Integer.toHexString(hashCode()) + "]"; Class<?> cla = localClasses.get(name); if (cla != null) { return cla; } RendererClass rc = JapidRenderer.japidClasses.get(name); if (rc == null) throw new ClassNotFoundException("Japid could not resolve class: " + name); if (!rc.getClassName().contains("$")) { // added just in time compiling JapidRenderer.recompile(rc); } byte[] bytecode = rc.bytecode; if (bytecode == null) { throw new RuntimeException(oid + " could not find the bytecode for: " + name); } // the defineClass method will load the classes of the dependency // classes. @SuppressWarnings("unchecked") Class<? extends JapidTemplateBaseWithoutPlay> cl = (Class<? extends JapidTemplateBaseWithoutPlay>) defineClass( name, bytecode, 0, bytecode.length); rc.setClz(cl); localClasses.put(name, cl); rc.setLastDefined(System.currentTimeMillis()); return cl; } } /** * Search for the byte code of the given class. */ protected byte[] getClassDefinition(String name) { name = name.replace(".", "/") + ".class"; InputStream is = getResourceAsStream(name); if (is == null) { // is = parentClassLoader.getResourceAsStream(name); // if (is == null) return null; } // System.out.println("got class def for: " + name); try { ByteArrayOutputStream os = new ByteArrayOutputStream(8192); byte[] buffer = new byte[8192]; int count; while ((count = is.read(buffer, 0, buffer.length)) > 0) { os.write(buffer, 0, count); } return os.toByteArray(); } catch (Exception e) { throw new RuntimeException(e); } finally { try { is.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }