package com.sijobe.spc.util; import java.io.File; import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; import java.util.Enumeration; import java.util.List; import java.util.Vector; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * Class that is used to help dynamically configure the system classloader * at runtime. This allows classes and jars (and all other files) to be loaded * during runtime to provide greater flexibility. * * This class is quite a hack in getting things added to the system * classpath and may not work where the environment is secured by a security * manager. * * @author simo_415 * @version 1.1 */ public class DynamicClassLoader { /** * The class loader that this class uses */ private static URLClassLoader CLASSLOADER; /** * Flag designates whether the populateClassLoaderWithClasses method has * been called yet or not. * * @see DynamicClassLoader#populateClassLoaderWithClasses() */ private static boolean POPULATED = false; /* * A vector used to store all SPC classes */ private static Vector<Class<?>> spcClasses = new Vector<Class<?>>(); /** * Gets the class loader used by this class (and generally all base classes) * * @return The URL class loader used */ public static URLClassLoader getClassLoader() { if (CLASSLOADER == null) { CLASSLOADER = (URLClassLoader)DynamicClassLoader.class.getClassLoader(); } return CLASSLOADER; } /** * Attempts to add the specified file to the system classpath. * * @param file - The path to the file to add to the system classpath * @return True is returned when the file was successfully added to the * system classpath, false otherwise. */ public static boolean addFile(String file) { return addFile(new File(file)); } /** * Attempts to add the specified file to the system classpath. * * @param file - The file to add to the system classpath * @return True is returned when the file was successfully added to the * system classpath, false otherwise. */ public static boolean addFile(File file) { try { return addURL(file.toURI().toURL()); } catch (MalformedURLException e) { return false; } } /** * Attempts to add the specified URL to the system classpath. * * @param file - The URL to add to the system classpath * @return True is returned when the URL was successfully added to the * system classpath, false otherwise. */ public static boolean addURL(URL file) { URLClassLoader sysloader = getClassLoader(); Class<URLClassLoader> sysclass = URLClassLoader.class; try { Method method = sysclass.getDeclaredMethod("addURL", new Class[] { URL.class }); method.setAccessible(true); method.invoke(sysloader, new Object[] { file }); } catch (Exception e) { return false; } return true; } /** * Returns a Vector containing all of the loaded classes that are castable * to the specified Class type. This would mean that the class itself and * any classes that extend the specified class that are currently on the * classpath are returned by the method. * * @param <T> - The class type * @param type - The type of class to search for * @return A Vector that contains all the matching classes. If an exception * occurs attempting to retrieve the loaded classes, null will be returned. */ @SuppressWarnings("unchecked") public static synchronized <T> List<Class<T>> getClasses(Class<T> type) { Vector<Class<T>> found = new Vector<Class<T>>(); try { Vector<Class<?>> classes = getSPCClasses(); for(Class<?> c : classes) { if(c == null) { //System.out.println("Skipping null class."); continue; } if(type.isAssignableFrom(c)) { found.add((Class<T>) c); } } } catch (Exception e) { e.printStackTrace(); } return found; } /** * * * @return A Vector that contains all classes in SPC's package */ private static Vector<Class<?>> getSPCClasses() throws Exception { if(spcClasses.size() == 0) { String className = DynamicClassLoader.class.getName().replace(".", "/"); URL location = DynamicClassLoader.class.getResource("/" + className + ".class"); if(location == null) { throw new RuntimeException("SPC: Couldn't find SPC classes!"); } final List<Class<?>> tempSPCClasses; if(location.toString().toLowerCase().startsWith("jar")) { String jarLocation = location.toString().replaceAll("jar:", "").split("!")[0]; File root = new File((new URL(jarLocation)).toURI()); System.out.println("SPC: " + root); tempSPCClasses = loadSPCClassesFromJAR(root); } else { File dynamicClassLoader = new File(location.getFile()); File parentDir = getSPCParent(dynamicClassLoader); if(parentDir == null) { System.out.println("SPC: Not loading."); tempSPCClasses = new Vector<Class<?>>(); tempSPCClasses.add(DynamicClassLoader.class); } else { System.out.println("SPC: " + parentDir); tempSPCClasses = loadSPCClassesFromDirectory(parentDir, "com/sijobe/spc/"); } } spcClasses.addAll(tempSPCClasses); return spcClasses; } else { return spcClasses; } } /** * Get the parent SPC folder * * @parameter file - The base file * @return The parent directory */ private static File getSPCParent(File file) { File parent = file; while((parent = parent.getParentFile()) != null) { if(parent.getPath().endsWith("spc") && parent.isDirectory()) { return parent; } } return null; // Hopefully, this is impossible } /** * Loads all of SPC's classes within the specified directory * * @param directory - The directory to load all of the classes from * @param parent - The path of the parent directory(s). This parent is used * as the package name of the classes that are loaded. * @return A Vector containing all of the loaded classes is returned */ public static List<Class<?>> loadSPCClassesFromDirectory(File directory, String parent) { if(directory.toString().indexOf("sijobe") == -1) { System.out.println("Nope: " + directory); return new Vector<Class<?>>(); } Vector<Class<?>> classes = new Vector<Class<?>>(); try { File files[] = directory.listFiles(); for (File file : files) { try { if (file.isFile()) { classes.add(loadClass(file.getName(),parent)); } else { classes.addAll(loadSPCClassesFromDirectory(file,parent + file.getName() + "/")); } } catch (Exception e) { e.printStackTrace(); } } } catch (Exception e) { e.printStackTrace(); } return classes; } /** * Loads all of SPC's classes that are contained within the specified JAR file. * * @param jar - The location of the JAR file * @return A Vector containing all of the classes that are part of this JAR * that were loaded */ public static List<Class<?>> loadSPCClassesFromJAR(File jar) { Vector<Class<?>> classes = new Vector<Class<?>>(); try { JarFile jf = new JarFile(jar); Enumeration<JarEntry> em = jf.entries(); while (em.hasMoreElements()) { JarEntry je = em.nextElement(); try { String entry = je.getName(); if(!entry.startsWith("com/sijobe/spc/")) { continue; } classes.add(loadClass(entry, null)); } catch (Throwable t) { } } } catch (Exception e) { e.printStackTrace(); return null; } return classes; } /** * Loads a single class onto the system classpath. As this method is * intended for use by classes dynamically loading content from the file * system the clazz parameter should be the filename on the system - ie: * CLASSNAME.class - the .class is the important part. The pack parameter is * the package that the class belongs to. * * @param clazz - The name of the class with a .class extension * @param pack - The name of the package * @return The Class object that was loaded based on the parameters * @throws Exception When the specified class is not a .class */ public static Class<?> loadClass(String clazz, String pack) throws Exception { if (!clazz.endsWith(".class")) { throw new Exception("'" + clazz + "' is not a class."); } clazz = clazz.split("\\.")[0]; if (pack != null) { pack = pack.endsWith("/") ? pack.substring(0, pack.length() - 1) : pack; } clazz = pack == null ? clazz : pack + "." + clazz; clazz = clazz.replaceAll("/", "."); if(clazz.startsWith(".")) { clazz = clazz.substring(1); } URLClassLoader loader = getClassLoader(); Class<?> c; //System.out.print(clazz + " loading... "); try { c = loader.loadClass(clazz); } catch (Throwable t) { String message = t.getMessage(); if(message != null && message.startsWith("com/sk89q/worldedit/")) { //System.out.println("Couldn't find WorldEdit class: " + message + ".class"); //System.out.println("Skipping SPC class: " + clazz + ".class"); } else { t.printStackTrace(); } return null; } //System.out.println("loaded."); return c; } /** * Gets all of the currently loaded items on the classpath * * @return An array of files that are specified on the classpath */ public static File[] getClasspath() { URLClassLoader urlLoader = DynamicClassLoader.getClassLoader(); URL urls[] = urlLoader.getURLs(); File classpath[] = new File[urls.length]; for (int i = 0; i < urls.length; i++) { try { classpath[i] = new File(urls[i].toURI()); } catch (Exception e) { } } return classpath; } }