Java tutorial
/** * Copyright (c) 2015 TerraFrame, Inc. All rights reserved. * * This file is part of Runway SDK(tm). * * Runway SDK(tm) 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. * * Runway SDK(tm) 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 Runway SDK(tm). If not, see <http://www.gnu.org/licenses/>. */ package com.runwaysdk.generation.loader; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Array; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.HashMap; import java.util.Stack; import java.util.TreeSet; import java.util.concurrent.locks.ReentrantLock; import java.util.regex.Pattern; import org.apache.commons.lang.ArrayUtils; import org.objectweb.asm.ClassReader; import com.runwaysdk.constants.LocalProperties; import com.runwaysdk.util.FileIO; public class RunwayClassLoader extends URLClassLoader { private static final boolean DEBUG = Boolean.getBoolean("runway.loader.debug"); /** * Once loaded, classes are cached for quick retrieval. */ protected HashMap<String, Class<?>> classes; /** * Directories for this loader to check for .class files in. */ static TreeSet<File> binDirs; /** * Lock is used for loading and reloading to prevent race conditions */ // static final ReentrantLock reentrant = new ReentrantLock(); /** * Regex patterns to match fully qualified Array class names */ public static final Pattern arrayPattern = Pattern.compile("(\\[)*L(.)*;"); public static final Pattern arrayPrefix = Pattern.compile("\\[L(.)*;"); /** * Private constructor prevents instances from being used for multiple loads. * Delegates to the specified parent for classes that are not Reloadable. * * @param array * The locations of the generatedClassPaths * @param parent * The parent loader to delegate to for non Reloadable types */ RunwayClassLoader(URL[] array, ClassLoader parent) { this(array, parent, LocalProperties.isRunwayEnvironment(), new File(LocalProperties.getClientGenBin()), new File(LocalProperties.getCommonGenBin()), new File(LocalProperties.getServerGenBin())); } RunwayClassLoader(URL[] array, ClassLoader parent, boolean isRunway, File... bins) { super(array, parent); classes = new HashMap<String, Class<?>>(); binDirs = new TreeSet<File>(); // for (URL url : array) // { // // url. // } for (File bin : bins) { if (!bin.exists() && !isRunway) { if (bin.getParentFile() != null && bin.getParentFile().exists()) { bin.mkdir(); } else { String errMsg = "The specified client bin directory [" + bin + "] does not exist. This is usually indicative of a problem with a property file."; // throw new ConfigurationException(errMsg); throw new RuntimeException(errMsg); } } binDirs.add(bin); } } /** * actualLoad breaks the standard delegation model for ClassLoaders. It reads * the bytes of the requested class, and if the class implements * {@link Reloadable}, then it loads the class <b>without</b> delegating up * the loader hierarchy. If the target class does not implement * {@link Reloadable}, then this well delegate to the parent classloader. * * loadClass is synchronized with a {@link ReentrantLock}. * * @param name * The fully qualified name of the class * @param resolve * If <tt>true</tt> then resolve the class * @return The resulting <tt>Class</tt> object */ public Class<?> actualLoad(String name, boolean resolve) throws ClassNotFoundException { LockHolder.lock(this); debug(RunwayClassLoader.class, "Loading " + name); // Encompass everything in a try block so we can use finally to ensure that // the lock is released try { // First grab a cached copy, if it exists Class<?> c = classes.get(name); debug(c, "Found " + name + " in the cache"); // Next, check if the class has already been loaded by the system if (c == null) c = findLoadedClass(name); debug(c, "Found " + name + " through ClassLoader.findLoadedClass()"); // special case for arrays if (arrayPattern.matcher(name).matches()) { c = loadArray(name); debug(c, "Found " + name + " as an array"); } if (c == null) { // Invoke findClass in order to find the class. c = findClass(name); debug(c, "Found " + name + " with custom findClass"); } // If still not found, delegate to the default loader if (c == null && !(getParent() instanceof LoaderManager)) { c = getParent().loadClass(name); debug(c, "Found " + name + " by delegating to parent " + c.getClassLoader()); } // At this point, if c is null, we have failed to find the class if (c == null) { throw new ClassNotFoundException(name); } if (resolve) resolveClass(c); return c; } finally { LockHolder.unlock(); } } @Override public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { LockHolder.lock(this); try { if (getParent() instanceof LoaderManager) { return getParent().loadClass(name); } else { return actualLoad(name, resolve); } } finally { LockHolder.unlock(); } } @Override public Class<?> loadClass(String name) throws ClassNotFoundException { return this.loadClass(name, false); } /** * */ protected Class<?> findClass(final String name) throws ClassNotFoundException { String mod = name.replace('.', '/') + ".class"; URL res = this.findResource(mod); if (res != null) { try { Class<?> clazz = null; Byte[] objectArray = FileIO.getBytesFromStream(res.openStream()); byte[] classBytes = ArrayUtils.toPrimitive(objectArray); if (implementsReloadable(classBytes)) { clazz = defineClass(name, classBytes, 0, classBytes.length); classes.put(name, clazz); return clazz; } } catch (IOException e1) { throw new ClassNotFoundException("Error reading class " + name); } // throw new ClassNotFoundException("Unable to find class " + name); } return null; } /** * @param classBytes * @return */ private boolean implementsReloadable(byte[] classBytes) { String reloadable = Reloadable.class.getName().replace('.', '/'); try { ClassReader reader = new ClassReader(classBytes); for (String s : reader.getInterfaces()) { if (s.equals(reloadable)) { return true; } } return false; } catch (ArrayIndexOutOfBoundsException e) { return false; } } /** * Loads an array from the base component up. If an array is loaded without * its componentType already loaded then an error occurs. Thus it loads from * inside out. * * @param arrayType */ public static Class<?> loadArray(String arrayType) { // Keep track of what types of array we have (an array of Integers and an // array of // Business objects, for example) Stack<String> baseTypes = new Stack<String>(); String baseType = arrayType; Class<?> arrayClass = null; // This loop strips the base type out of any n-dimensional array while (arrayPattern.matcher(baseType).matches()) { if (arrayPrefix.matcher(baseType).matches()) { baseType = baseType.replaceFirst("\\[L", "").replace(";", "").trim(); } else { baseType = baseType.replaceFirst("\\[", ""); } // Add the base type to the stack baseTypes.push(baseType); } // We must load all base types before we can try to load arrays of those // types while (!baseTypes.isEmpty()) { String type = baseTypes.pop(); Class<?> componentType; componentType = LoaderDecorator.load(type); arrayClass = Array.newInstance(componentType, 0).getClass(); } return arrayClass; } private void debug(Class<?> c, String message) { if (DEBUG && c != null) System.out.println("***" + message); } /** * * @see java.lang.ClassLoader#clearAssertionStatus() */ public void clearAssertionStatus() { getParent().clearAssertionStatus(); } /** * @param name * @return * @see java.lang.ClassLoader#getResource(java.lang.String) */ public URL getResource(String name) { return getParent().getResource(name); } /** * @param name * @return * @see java.lang.ClassLoader#getResourceAsStream(java.lang.String) */ public InputStream getResourceAsStream(String name) { return getParent().getResourceAsStream(name); } /** * @param name * @return * @throws IOException * @see java.lang.ClassLoader#getResources(java.lang.String) */ public Enumeration<URL> getResources(String name) throws IOException { return getParent().getResources(name); } /** * @return * @see java.lang.Object#hashCode() */ public int hashCode() { return getParent().hashCode(); } /** * @param className * @param enabled * @see java.lang.ClassLoader#setClassAssertionStatus(java.lang.String, * boolean) */ public void setClassAssertionStatus(String className, boolean enabled) { getParent().setClassAssertionStatus(className, enabled); } /** * @param enabled * @see java.lang.ClassLoader#setDefaultAssertionStatus(boolean) */ public void setDefaultAssertionStatus(boolean enabled) { getParent().setDefaultAssertionStatus(enabled); } /** * @param packageName * @param enabled * @see java.lang.ClassLoader#setPackageAssertionStatus(java.lang.String, * boolean) */ public void setPackageAssertionStatus(String packageName, boolean enabled) { getParent().setPackageAssertionStatus(packageName, enabled); } }