/* * Copyright 2010-2015 Institut Pasteur. * * This file is part of Icy. * * Icy is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Icy 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with Icy. If not, see <http://www.gnu.org/licenses/>. */ package icy.plugin.classloader; import icy.plugin.classloader.exception.JclException; import icy.plugin.classloader.exception.ResourceNotFoundException; import icy.system.IcyExceptionHandler; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.Collections; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; /** * Reads the class bytes from jar files and other resources using * ClasspathResources * * @author Kamran Zafar * @author Stephane Dallongeville */ @SuppressWarnings("rawtypes") public class JarClassLoader extends AbstractClassLoader { /** * Class cache */ protected final Map<String, Class> loadedClasses; protected final ClasspathResources classpathResources; private char classNameReplacementChar; private final ProxyClassLoader localLoader = new LocalLoader(); private static Logger logger = Logger.getLogger(JarClassLoader.class.getName()); public JarClassLoader(ClassLoader parent) { super(parent); classpathResources = new ClasspathResources(); loadedClasses = Collections.synchronizedMap(new HashMap<String, Class>()); addLoader(localLoader); } public JarClassLoader() { this(getSystemClassLoader()); } /** * Loads classes from different sources * * @param sources */ public JarClassLoader(Object[] sources) { this(); addAll(sources); } /** * Loads classes from different sources * * @param sources */ public JarClassLoader(List sources) { this(); addAll(sources); } /** * Add all jar/class sources * * @param sources */ public void addAll(Object[] sources) { for (Object source : sources) add(source); } /** * Add all jar/class sources * * @param sources */ public void addAll(List sources) { for (Object source : sources) add(source); } /** * Loads local/remote source * * @param source */ public void add(Object source) { if (source instanceof InputStream) throw new JclException("Unsupported resource type"); else if (source instanceof URL) add((URL) source); else if (source instanceof String) add((String) source); else throw new JclException("Unknown Resource type"); } /** * Loads local/remote resource * * @param resourceName */ public void add(String resourceName) { classpathResources.loadResource(resourceName); } /** * Loads classes from InputStream. * * @deprecated Not anymore supported (we need URL for getResource(..) method) */ @Deprecated public void add(InputStream jarStream) { // classpathResources.loadJar(jarStream); } /** * Loads local/remote resource * * @param url */ public void add(URL url) { classpathResources.loadResource(url); } /** * Release all loaded resources and classes. * The ClassLoader cannot be used anymore to load any new resource. */ public void unloadAll() { // unload resources classpathResources.entryContents.clear(); // unload classes loadedClasses.clear(); } /** * Reads the class bytes from different local and remote resources using * ClasspathResources * * @param className * @return byte[] * @throws IOException */ protected byte[] getClassBytes(String className) throws IOException { return classpathResources.getResourceContent(formatClassName(className)); } /** * Attempts to unload class, it only unloads the locally loaded classes by * JCL * * @param className */ public void unloadClass(String className) { if (logger.isLoggable(Level.FINEST)) logger.finest("Unloading class " + className); if (loadedClasses.containsKey(className)) { if (logger.isLoggable(Level.FINEST)) logger.finest("Removing loaded class " + className); loadedClasses.remove(className); try { classpathResources.unload(formatClassName(className)); } catch (ResourceNotFoundException e) { throw new JclException("Something is very wrong!!!" + "The locally loaded classes must be in synch with ClasspathResources", e); } } else { try { classpathResources.unload(formatClassName(className)); } catch (ResourceNotFoundException e) { throw new JclException("Class could not be unloaded " + "[Possible reason: Class belongs to the system]", e); } } } /** * @param className * @return String */ protected String formatClassName(String className) { String cname = className.replace('/', '~'); if (classNameReplacementChar == '\u0000') // '/' is used to map the package to the path cname = cname.replace('.', '/') + ".class"; else // Replace '.' with custom char, such as '_' cname = cname.replace('.', classNameReplacementChar) + ".class"; return cname.replace('~', '/'); } /** * Local class loader */ class LocalLoader extends ProxyClassLoader { private final Logger logger = Logger.getLogger(LocalLoader.class.getName()); public LocalLoader() { super(50); enabled = Configuration.isLocalLoaderEnabled(); } @Override public Object getLoader() { return this; } @Override public Class loadClass(String className, boolean resolveIt) throws ClassNotFoundException { Class result = null; byte[] classBytes; result = loadedClasses.get(className); if (result != null) { if (logger.isLoggable(Level.FINEST)) logger.finest("Returning local loaded class [" + className + "] from cache"); return result; } // try to find from already loaded class (by other method) result = findLoadedClass(className); // not loaded ? if (result == null) { try { classBytes = getClassBytes(className); } catch (IOException e) { // we got a severe error here --> throw an exception throw new ClassNotFoundException(className, e); } if (classBytes == null) return null; result = defineClass(className, classBytes, 0, classBytes.length); if (result == null) return null; } /* * Preserve package name. */ if (result.getPackage() == null) { int lastDotIndex = className.lastIndexOf('.'); String packageName = (lastDotIndex >= 0) ? className.substring(0, lastDotIndex) : ""; definePackage(packageName, null, null, null, null, null, null, null); } if (resolveIt) resolveClass(result); loadedClasses.put(className, result); if (logger.isLoggable(Level.FINEST)) logger.finest("Return new local loaded class " + className); return result; } @Override public InputStream getResourceAsStream(String name) { try { byte[] arr = classpathResources.getResourceContent(name); if (arr != null) { if (logger.isLoggable(Level.FINEST)) logger.finest("Returning newly loaded resource " + name); return new ByteArrayInputStream(arr); } } catch (IOException e) { IcyExceptionHandler.showErrorMessage(e, false, true); } return null; } @Override public URL getResource(String name) { URL url = classpathResources.getResource(name); if (url != null) { if (logger.isLoggable(Level.FINEST)) logger.finest("Returning newly loaded resource " + name); return url; } return null; } @Override public Enumeration<URL> getResources(String name) throws IOException { final URL url = getResource(name); return new Enumeration<URL>() { boolean hasMore = (url != null); @Override public boolean hasMoreElements() { return hasMore; } @Override public URL nextElement() { if (hasMore) { hasMore = false; return url; } return null; } }; } } public char getClassNameReplacementChar() { return classNameReplacementChar; } public void setClassNameReplacementChar(char classNameReplacementChar) { this.classNameReplacementChar = classNameReplacementChar; } /** * Returns an immutable Set of all resources name */ public Set<String> getResourcesName() { return classpathResources.getResourcesName(); } /** * Returns an immutable Map of all resources */ public Map<String, URL> getResources() { return classpathResources.getResources(); } /** * Returns all currently loaded classes and resources. */ public Map<String, byte[]> getLoadedResources() { return classpathResources.getLoadedResources(); } /** * @return Local JCL ProxyClassLoader */ public ProxyClassLoader getLocalLoader() { return localLoader; } /** * Returns all JCL-loaded classes as an immutable Map * * @return Map */ public Map<String, Class> getLoadedClasses() { return Collections.unmodifiableMap(loadedClasses); } }