Java tutorial
/** * Squidy Interaction Library is free software: you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * Squidy Interaction Library is distributed in the hope that it will be * useful, but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Squidy Interaction Library. If not, see * <http://www.gnu.org/licenses/>. * * 2009 Human-Computer Interaction Group, University of Konstanz. * <http://hci.uni-konstanz.de> * * Please contact info@squidy-lib.de or visit our website * <http://www.squidy-lib.de> for further information. */ package org.squidy.common.dynamiccode; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.squidy.SquidyException; /** * <code>DynamicCode</code>. * * <pre> * Date: Mar 30, 2009 * Time: 7:19:03 PM * </pre> * * @author Roman Rdle <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle@uni-konstanz.de</a> * Human-Computer Interaction Group University of Konstanz * @version $Id: DynamicCodeClassLoader.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.0.0 */ public class DynamicCodeClassLoader extends ClassLoader { // Logger to log info, error, debug,... messages. private static final Log LOG = LogFactory.getLog(DynamicCodeClassLoader.class); private String compileClasspath; private File compilerOutput = DYNAMIC_CODE_REPOSITORY; private ClassLoader parentClassLoader; private ArrayList<SourceDir> sourceDirs = new ArrayList<SourceDir>(); // class name => LoadedClass private HashMap<String, LoadedClass> loadedClasses = new HashMap<String, LoadedClass>(); // public static final String DYNAMIC_CODE_REPOSITORY_NAME = // "DynamicCodeRepository"; public static final File DYNAMIC_CODE_REPOSITORY; static { DYNAMIC_CODE_REPOSITORY = new File("target/classes"); } public static final DynamicCodeClassLoader DYNAMIC_CODE; static { if (LOG.isInfoEnabled()) { LOG.info("Using context class loader: " + Thread.currentThread().getContextClassLoader()); } DYNAMIC_CODE = new DynamicCodeClassLoader(Thread.currentThread().getContextClassLoader()); /* * // TODO: JFileChooser crashes with exception: ClassCircularityError JFileChooser sourceDirChooser = new * JFileChooser(); sourceDirChooser.setDialogTitle("Choose Source Directory"); * sourceDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); int option = * sourceDirChooser.showOpenDialog(null); if (option == JFileChooser.APPROVE_OPTION) { File sourceDirectory = * sourceDirChooser.getSelectedFile(); if (sourceDirectory.isDirectory()) { * DYNAMIC_CODE.addSourceDir(sourceDirectory); } else { DYNAMIC_CODE.addSourceDir(new File("src/main/java")); } * } else { DYNAMIC_CODE.addSourceDir(new File("src/main/java")); } */ Thread.currentThread().setContextClassLoader(DYNAMIC_CODE); // DYNAMIC_CODE.addSourceDir(new File("extension")); // DYNAMIC_CODE.addSourceDir(new // File("../squidy-extension-basic-1.1.0-SNAPSHOT/src/main/java")); } public DynamicCodeClassLoader() { this(Thread.currentThread().getContextClassLoader()); } public DynamicCodeClassLoader(ClassLoader parentClassLoader) { this(extractClasspath(parentClassLoader), parentClassLoader); } /** * @param compileClasspath used to compile dynamic classes * @param parentClassLoader the parent of the class loader that loads all the dynamic classes */ public DynamicCodeClassLoader(String compileClasspath, ClassLoader parentClassLoader) { // super(new URL[] { DYNAMIC_CODE_REPOSITORY_URL }); if (LOG.isDebugEnabled()) { LOG.debug("Classpath: " + compileClasspath); } this.compileClasspath = compileClasspath; this.parentClassLoader = parentClassLoader; } /** * Add a directory that contains the source of dynamic java code. * * @param srcDir * @return true if the add is successful */ public boolean addSourceDir(File srcDir) { try { srcDir = srcDir.getCanonicalFile(); } catch (IOException e) { // ignore } synchronized (sourceDirs) { // check existence for (int i = 0; i < sourceDirs.size(); i++) { SourceDir src = (SourceDir) sourceDirs.get(i); if (src.srcDir.equals(srcDir)) { return false; } } // add new SourceDir src = new SourceDir(srcDir); sourceDirs.add(src); if (LOG.isInfoEnabled()) { LOG.info("Added source directory: " + srcDir); } } return true; } /** * Returns the up-to-date dynamic class by name. * * @param className * @return * @throws ClassNotFoundException if source file not found or compilation error */ public Class<?> loadClass(String className) throws ClassNotFoundException { LoadedClass loadedClass = null; synchronized (loadedClasses) { loadedClass = (LoadedClass) loadedClasses.get(className); } // first access of a class if (loadedClass == null) { String resource = className.replace('.', '/') + ".java"; SourceDir src = locateResource(resource); if (src == null) { try { return parentClassLoader.loadClass(className); } catch (Exception e) { throw new ClassNotFoundException("DynamicCode class not found " + className); } } synchronized (this) { // compile and load class loadedClass = new LoadedClass(className, src); synchronized (loadedClasses) { loadedClasses.put(className, loadedClass); } } return loadedClass.loadedClass; } // subsequent access if (loadedClass.isChanged()) { // unload and load again unload(loadedClass.srcDir); return loadClass(className); } return loadedClass.loadedClass; } private SourceDir locateResource(String resource) { for (int i = 0; i < sourceDirs.size(); i++) { SourceDir src = (SourceDir) sourceDirs.get(i); if (new File(src.srcDir, resource).exists()) { return src; } } return null; } private void unload(SourceDir src) { // clear loaded classes synchronized (loadedClasses) { for (Iterator iter = loadedClasses.values().iterator(); iter.hasNext();) { LoadedClass loadedClass = (LoadedClass) iter.next(); if (loadedClass.srcDir == src) { iter.remove(); } } } // create new class loader src.recreateClassLoader(); } /** * Get a resource from added source directories. * * @param resource * @return the resource URL, or null if resource not found */ public URL getResource(String resource) { try { SourceDir src = locateResource(resource); return src == null ? null : new File(src.srcDir, resource).toURL(); } catch (MalformedURLException e) { // should not happen return null; } } /** * Get a resource stream from added source directories. * * @param resource * @return the resource stream, or null if resource not found */ public InputStream getResourceAsStream(String resource) { try { SourceDir src = locateResource(resource); return src == null ? null : new FileInputStream(new File(src.srcDir, resource)); } catch (FileNotFoundException e) { // should not happen return null; } } /** * <code>SourceDir</code>. * * <pre> * Date: Aug 20, 2009 * Time: 9:36:53 PM * </pre> * * @author Roman Rdle <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle * @uni-konstanz.de</a> Human-Computer Interaction Group University of Konstanz * @version $Id: DynamicCodeClassLoader.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.0.0 */ private class SourceDir { File srcDir; File binDir; Javac javac; URLClassLoader classLoader; SourceDir(File srcDir) { this.srcDir = srcDir; // Make directories for hot deployment compiler output if (!compilerOutput.mkdirs()) { throw new SquidyException("Could not make directories for hot deployment compiler output"); } this.binDir = compilerOutput;// new // Make directories for hot deployment binaries if (!binDir.mkdirs()) { throw new SquidyException("Could not make directories for hot deployment binaries"); } // prepare compiler this.javac = new Javac(compileClasspath, binDir.getAbsolutePath()); // class loader recreateClassLoader(); } /** * Recreate class loader. */ void recreateClassLoader() { classLoader = (URLClassLoader) AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() { public ClassLoader run() { try { return new URLClassLoader(new URL[] { binDir.toURL() }, parentClassLoader); } catch (MalformedURLException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } return null; } }); } } /** * <code>LoadedClass</code>. * * <pre> * Date: Aug 20, 2009 * Time: 9:37:28 PM * </pre> * * @author Roman Rdle <a href="mailto:Roman.Raedle@uni-konstanz.de">Roman.Raedle * @uni-konstanz.de</a> Human-Computer Interaction Group University of Konstanz * @version $Id: DynamicCodeClassLoader.java 772 2011-09-16 15:39:44Z raedle $ * @since 1.0.0 */ private static class LoadedClass { String className; SourceDir srcDir; File srcFile; File binFile; Class loadedClass; long lastModified; LoadedClass(String className, SourceDir src) { this.className = className; this.srcDir = src; String path = className.replace('.', '/'); this.srcFile = new File(src.srcDir, path + ".java"); this.binFile = new File(src.binDir, path + ".class"); compileAndLoadClass(); } boolean isChanged() { return srcFile.lastModified() != lastModified; } void compileAndLoadClass() { if (loadedClass != null) { return; // class already loaded } // System.out.println("SRC: " + srcFile); // compile, if required String error = null; if (binFile.lastModified() < srcFile.lastModified()) { error = srcDir.javac.compile(new File[] { srcFile }); } if (error != null) { throw new RuntimeException("Failed to compile " + srcFile.getAbsolutePath() + ". Error: " + error); } try { // load class loadedClass = srcDir.classLoader.loadClass(className); // load class success, remember timestamp lastModified = srcFile.lastModified(); } catch (ClassNotFoundException e) { throw new RuntimeException("Failed to load class " + srcFile.getAbsolutePath()); } if (LOG.isInfoEnabled()) { LOG.info("Initialized " + loadedClass); } } } /** * Extracts a classpath string from a given class loader. Recognizes only URLClassLoader. */ private static String extractClasspath(ClassLoader cl) { StringBuffer buf = new StringBuffer(); while (cl != null) { if (LOG.isDebugEnabled()) { LOG.debug("Trying to extract classpath of class loader: " + cl.getClass().getName()); } if (cl instanceof URLClassLoader) { URL urls[] = ((URLClassLoader) cl).getURLs(); for (int i = 0; i < urls.length; i++) { if (buf.length() > 0) { buf.append(File.pathSeparatorChar); } String path = urls[i].getFile(); if (path.startsWith("/C:/") || path.startsWith("/c:/")) { path = path.substring(1, path.length()); path = path.replace('/', '\\'); path = path.replace("%20", " "); } buf.append(path); } } cl = cl.getParent(); } return buf.toString(); } }