blob: 10ae89de1641f0255334a1bcc39dfb2998c4aee0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.osgi.internal.loader.classpath;
import java.io.*;
import java.net.URL;
import java.util.*;
import org.eclipse.osgi.container.*;
import org.eclipse.osgi.container.ModuleContainerAdaptor.ContainerEvent;
import org.eclipse.osgi.container.namespaces.EquinoxModuleDataNamespace;
import org.eclipse.osgi.framework.util.ArrayMap;
import org.eclipse.osgi.internal.debug.Debug;
import org.eclipse.osgi.internal.framework.EquinoxConfiguration;
import org.eclipse.osgi.internal.hookregistry.ClassLoaderHook;
import org.eclipse.osgi.internal.hookregistry.HookRegistry;
import org.eclipse.osgi.internal.loader.ModuleClassLoader;
import org.eclipse.osgi.internal.loader.ModuleClassLoader.DefineClassResult;
import org.eclipse.osgi.internal.messages.Msg;
import org.eclipse.osgi.internal.weaving.WeavingHookConfigurator;
import org.eclipse.osgi.storage.BundleInfo.Generation;
import org.eclipse.osgi.storage.NativeCodeFinder;
import org.eclipse.osgi.storage.Storage;
import org.eclipse.osgi.storage.bundlefile.BundleEntry;
import org.eclipse.osgi.storage.bundlefile.BundleFile;
import org.eclipse.osgi.util.NLS;
import org.osgi.framework.BundleException;
import org.osgi.framework.namespace.HostNamespace;
/**
* A helper class for {@link ModuleClassLoader} implementations. This class will keep track of
* {@link ClasspathEntry} objects for the host bundle and any attached fragment bundles. This
* class takes care of searching the {@link ClasspathEntry} objects for a module class loader
* implementation. Additional behavior may be added to a classpath manager by configuring a
* {@link ClassLoaderHook}.
* @see ModuleClassLoader
* @see ClassLoaderHook
* @since 3.2
*/
public class ClasspathManager {
private static final FragmentClasspath[] emptyFragments = new FragmentClasspath[0];
private static final String[] DEFAULT_CLASSPATH = new String[] {"."}; //$NON-NLS-1$
@SuppressWarnings("unchecked")
private static final Enumeration<URL> EMPTY_ENUMERATION = Collections.enumeration(Collections.EMPTY_LIST);
private final Generation generation;
private final ModuleClassLoader classloader;
private final HookRegistry hookRegistry;
private final Debug debug;
// TODO Note that PDE has internal dependency on this field type/name (bug 267238)
private final ClasspathEntry[] entries;
// TODO Note that PDE has internal dependency on this field type/name (bug 267238)
private volatile FragmentClasspath[] fragments;
// a Map<String,String> where "libname" is the key and libpath" is the value
private ArrayMap<String, String> loadedLibraries = null;
// used to detect recusive defineClass calls for the same class on the same class loader (bug 345500)
private ThreadLocal<DefineContext> currentDefineContext = new ThreadLocal<>();
/**
* Constructs a classpath manager for the given generation and module class loader
* @param generation the host generation for this classpath manager
* @param classloader the ModuleClassLoader for this classpath manager
*/
public ClasspathManager(Generation generation, ModuleClassLoader classloader) {
EquinoxConfiguration configuration = generation.getBundleInfo().getStorage().getConfiguration();
this.debug = configuration.getDebug();
this.hookRegistry = configuration.getHookRegistry();
this.generation = generation;
this.classloader = classloader;
String[] cp = getClassPath(generation.getRevision());
this.fragments = buildFragmentClasspaths(this.classloader, this);
this.entries = buildClasspath(cp, this, this.generation);
}
private static String[] getClassPath(ModuleRevision revision) {
List<ModuleCapability> moduleDatas = revision.getModuleCapabilities(EquinoxModuleDataNamespace.MODULE_DATA_NAMESPACE);
@SuppressWarnings("unchecked")
List<String> cp = moduleDatas.isEmpty() ? null : (List<String>) moduleDatas.get(0).getAttributes().get(EquinoxModuleDataNamespace.CAPABILITY_CLASSPATH);
return cp == null ? DEFAULT_CLASSPATH : cp.toArray(new String[cp.size()]);
}
private FragmentClasspath[] buildFragmentClasspaths(ModuleClassLoader hostloader, ClasspathManager manager) {
if (hostloader == null) {
return emptyFragments;
}
List<ModuleWire> fragmentWires = hostloader.getBundleLoader().getWiring().getProvidedModuleWires(HostNamespace.HOST_NAMESPACE);
if (fragmentWires == null) {
// we don't hold locks while checking the graph, just return if no longer valid
return emptyFragments;
}
List<FragmentClasspath> result = new ArrayList<>(fragmentWires.size());
for (ModuleWire fragmentWire : fragmentWires) {
ModuleRevision revision = fragmentWire.getRequirer();
Generation fragGeneration = (Generation) revision.getRevisionInfo();
String[] cp = getClassPath(revision);
ClasspathEntry[] fragEntries = buildClasspath(cp, manager, fragGeneration);
FragmentClasspath fragClasspath = new FragmentClasspath(fragGeneration, fragEntries);
insertFragment(fragClasspath, result);
}
return result.toArray(new FragmentClasspath[result.size()]);
}
private static void insertFragment(FragmentClasspath fragClasspath, List<FragmentClasspath> existing) {
// Find a place in the fragment list to insert this fragment.
long fragID = fragClasspath.getGeneration().getRevision().getRevisions().getModule().getId();
for (ListIterator<FragmentClasspath> iExisting = existing.listIterator(); iExisting.hasNext();) {
long otherID = iExisting.next().getGeneration().getRevision().getRevisions().getModule().getId();
if (fragID < otherID) {
iExisting.previous();
iExisting.add(fragClasspath);
return;
}
}
existing.add(fragClasspath);
}
/**
* Closes all the classpath entry resources for this classpath manager.
*
*/
public void close() {
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
try {
entries[i].getBundleFile().close();
} catch (IOException e) {
generation.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(ContainerEvent.ERROR, generation.getRevision().getRevisions().getModule(), e);
}
}
}
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++)
currentFragments[i].close();
}
private ClasspathEntry[] buildClasspath(String[] cp, ClasspathManager hostloader, Generation source) {
ArrayList<ClasspathEntry> result = new ArrayList<>(cp.length);
// add the regular classpath entries.
for (int i = 0; i < cp.length; i++)
findClassPathEntry(result, cp[i], hostloader, source);
return result.toArray(new ClasspathEntry[result.size()]);
}
/**
* Finds all the ClasspathEntry objects for the requested classpath. This method will first call all
* the configured class loading hooks {@link ClassLoaderHook#addClassPathEntry(ArrayList, String, ClasspathManager, Generation)}
* methods. This allows class loading hooks to add additional ClasspathEntry objects to the result for the
* requested classpath. Then the local host classpath entries and attached fragment classpath entries are
* searched.
* @param result a list of ClasspathEntry objects. This list is used to add new ClasspathEntry objects to.
* @param cp the requested classpath.
* @param hostloader the host classpath manager for the classpath
* @param sourceGeneration the source generation to search for the classpath
*/
private void findClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostloader, Generation sourceGeneration) {
List<ClassLoaderHook> loaderHooks = hookRegistry.getClassLoaderHooks();
boolean hookAdded = false;
for (ClassLoaderHook hook : loaderHooks) {
hookAdded |= hook.addClassPathEntry(result, cp, hostloader, sourceGeneration);
}
if (!addClassPathEntry(result, cp, hostloader, sourceGeneration) && !hookAdded) {
BundleException be = new BundleException(NLS.bind(Msg.BUNDLE_CLASSPATH_ENTRY_NOT_FOUND_EXCEPTION, cp, sourceGeneration.getRevision().toString()), BundleException.MANIFEST_ERROR);
sourceGeneration.getBundleInfo().getStorage().getAdaptor().publishContainerEvent(ContainerEvent.INFO, sourceGeneration.getRevision().getRevisions().getModule(), be);
}
}
/**
* Adds a ClasspathEntry for the requested classpath to the result. The local host classpath entries
* are searched first and then attached fragments classpath entries are searched. The search stops once the first
* classpath entry is found.
* @param result a list of ClasspathEntry objects. This list is used to add new ClasspathEntry objects to.
* @param cp the requested classpath.
* @param hostManager the host classpath manager for the classpath
* @param source the source generation to search for the classpath
* @return true if a ClasspathEntry was added to the result
*/
public boolean addClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostManager, Generation source) {
return addStandardClassPathEntry(result, cp, hostManager, source) || addEclipseClassPathEntry(result, cp, hostManager, source);
}
public static boolean addStandardClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostManager, Generation generation) {
if (cp.equals(".")) { //$NON-NLS-1$
result.add(hostManager.createClassPathEntry(generation.getBundleFile(), generation));
return true;
}
ClasspathEntry element = hostManager.getClasspath(cp, generation);
if (element != null) {
result.add(element);
return true;
}
// need to check in fragments for the classpath entry.
// only check for fragments if the generation is the host's generation.
if (hostManager.generation == generation) {
FragmentClasspath[] hostFrags = hostManager.getFragmentClasspaths();
for (int i = 0; i < hostFrags.length; i++) {
FragmentClasspath fragCP = hostFrags[i];
element = hostManager.getClasspath(cp, fragCP.getGeneration());
if (element != null) {
result.add(element);
return true;
}
}
}
return false;
}
private boolean addEclipseClassPathEntry(ArrayList<ClasspathEntry> result, String cp, ClasspathManager hostManager, Generation source) {
String var = hasPrefix(cp);
if (var != null)
// find internal library using eclipse predefined vars
return addInternalClassPath(var, result, cp, hostManager, source);
if (cp.startsWith(NativeCodeFinder.EXTERNAL_LIB_PREFIX)) {
cp = cp.substring(NativeCodeFinder.EXTERNAL_LIB_PREFIX.length());
// find external library using system property substitution
ClasspathEntry cpEntry = hostManager.getExternalClassPath(source.getBundleInfo().getStorage().getConfiguration().substituteVars(cp), source);
if (cpEntry != null) {
result.add(cpEntry);
return true;
}
}
return false;
}
private boolean addInternalClassPath(String var, ArrayList<ClasspathEntry> cpEntries, String cp, ClasspathManager hostManager, Generation source) {
EquinoxConfiguration configuration = source.getBundleInfo().getStorage().getConfiguration();
if (var.equals("ws")) //$NON-NLS-1$
return ClasspathManager.addStandardClassPathEntry(cpEntries, "ws/" + configuration.getWS() + cp.substring(4), hostManager, source); //$NON-NLS-1$
if (var.equals("os")) //$NON-NLS-1$
return ClasspathManager.addStandardClassPathEntry(cpEntries, "os/" + configuration.getOS() + cp.substring(4), hostManager, source); //$NON-NLS-1$
if (var.equals("nl")) { //$NON-NLS-1$
cp = cp.substring(4);
List<String> NL_JAR_VARIANTS = source.getBundleInfo().getStorage().getConfiguration().ECLIPSE_NL_JAR_VARIANTS;
for (String nlVariant : NL_JAR_VARIANTS) {
if (ClasspathManager.addStandardClassPathEntry(cpEntries, "nl/" + nlVariant + cp, hostManager, source)) //$NON-NLS-1$
return true;
}
}
return false;
}
//return a String representing the string found between the $s
private static String hasPrefix(String libPath) {
if (libPath.startsWith("$ws$")) //$NON-NLS-1$
return "ws"; //$NON-NLS-1$
if (libPath.startsWith("$os$")) //$NON-NLS-1$
return "os"; //$NON-NLS-1$
if (libPath.startsWith("$nl$")) //$NON-NLS-1$
return "nl"; //$NON-NLS-1$
return null;
}
/**
* Creates a new ClasspathEntry object for the requested classpath if the source exists.
* @param cp the requested classpath.
* @param cpGeneration the source generation to search for the classpath
* @return a new ClasspathEntry for the requested classpath or null if the source does not exist.
*/
public ClasspathEntry getClasspath(String cp, Generation cpGeneration) {
BundleFile bundlefile = null;
File file;
BundleEntry cpEntry = cpGeneration.getBundleFile().getEntry(cp);
// check for internal library directories in a bundle jar file
if (cpEntry != null && cpEntry.getName().endsWith("/")) //$NON-NLS-1$
bundlefile = createBundleFile(cp, cpGeneration);
// check for internal library jars
else if ((file = cpGeneration.getBundleFile().getFile(cp, false)) != null)
bundlefile = createBundleFile(file, cpGeneration);
if (bundlefile != null)
return createClassPathEntry(bundlefile, cpGeneration);
return null;
}
/**
* Uses the requested classpath as an absolute path to locate a source for a new ClasspathEntry.
* @param cp the requested classpath
* @param cpGeneration the source generation to search for the classpath
* @return a classpath entry which uses an absolut path as a source
*/
public ClasspathEntry getExternalClassPath(String cp, Generation cpGeneration) {
File file = new File(cp);
if (!file.isAbsolute())
return null;
BundleFile bundlefile = createBundleFile(file, cpGeneration);
if (bundlefile != null)
return createClassPathEntry(bundlefile, cpGeneration);
return null;
}
public synchronized void loadFragments(Collection<ModuleRevision> addedFragments) {
List<FragmentClasspath> result = new ArrayList<>(Arrays.asList(fragments));
for (ModuleRevision addedFragment : addedFragments) {
Generation fragGeneration = (Generation) addedFragment.getRevisionInfo();
String[] cp = getClassPath(addedFragment);
ClasspathEntry[] fragEntries = buildClasspath(cp, this, fragGeneration);
FragmentClasspath fragClasspath = new FragmentClasspath(fragGeneration, fragEntries);
insertFragment(fragClasspath, result);
}
fragments = result.toArray(new FragmentClasspath[result.size()]);
}
private static BundleFile createBundleFile(File content, Generation generation) {
if (!content.exists()) {
return null;
}
return generation.getBundleInfo().getStorage().createBundleFile(content, generation, content.isDirectory(), false);
}
private static BundleFile createBundleFile(String nestedDir, Generation generation) {
return generation.getBundleInfo().getStorage().createNestedBundleFile(nestedDir, generation.getBundleFile(), generation);
}
private ClasspathEntry createClassPathEntry(BundleFile bundlefile, Generation source) {
ClasspathEntry entry;
if (classloader != null)
entry = classloader.createClassPathEntry(bundlefile, source);
else
entry = new ClasspathEntry(bundlefile, source.getDomain(), source);
return entry;
}
/**
* Finds a local resource by searching the ClasspathEntry objects of the classpath manager.
* This method will first call all the configured class loading hooks
* {@link ClassLoaderHook#preFindLocalResource(String, ClasspathManager)} methods. Then it
* will search for the resource. Finally it will call all the configured class loading hooks
* {@link ClassLoaderHook#postFindLocalResource(String, URL, ClasspathManager)} methods.
* @param resource the requested resource name.
* @return the requested resource URL or null if the resource does not exist
*/
public URL findLocalResource(String resource) {
List<ClassLoaderHook> hooks = hookRegistry.getClassLoaderHooks();
for (ClassLoaderHook hook : hooks) {
hook.preFindLocalResource(resource, this);
}
URL result = null;
try {
result = findLocalResourceImpl(resource, -1);
return result;
} finally {
for (ClassLoaderHook hook : hooks) {
hook.postFindLocalResource(resource, result, this);
}
}
}
private URL findLocalResourceImpl(String resource, int classPathIndex) {
URL result = null;
int curIndex = 0;
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
result = findResourceImpl(resource, entries[i].getBundleFile(), curIndex);
if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
return result;
}
curIndex++;
}
// look in fragments
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++) {
ClasspathEntry[] fragEntries = currentFragments[i].getEntries();
for (int j = 0; j < fragEntries.length; j++) {
result = findResourceImpl(resource, fragEntries[j].getBundleFile(), curIndex);
if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
return result;
curIndex++;
}
}
return null;
}
/**
* Finds the local resources by searching the ClasspathEntry objects of the classpath manager.
* @param resource the requested resource name.
* @return an enumeration of the the requested resources
*/
public Enumeration<URL> findLocalResources(String resource) {
List<URL> resources = new ArrayList<>(6);
int classPathIndex = 0;
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
URL url = findResourceImpl(resource, entries[i].getBundleFile(), classPathIndex);
if (url != null)
resources.add(url);
}
classPathIndex++;
}
// look in fragments
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++) {
ClasspathEntry[] fragEntries = currentFragments[i].getEntries();
for (int j = 0; j < fragEntries.length; j++) {
URL url = findResourceImpl(resource, fragEntries[j].getBundleFile(), classPathIndex);
if (url != null)
resources.add(url);
classPathIndex++;
}
}
if (resources.size() > 0)
return Collections.enumeration(resources);
return EMPTY_ENUMERATION;
}
private URL findResourceImpl(String name, BundleFile bundlefile, int index) {
return bundlefile.getResourceURL(name, generation.getRevision().getRevisions().getModule(), index);
}
/**
* Finds a local entry by searching the ClasspathEntry objects of the classpath manager.
* @param path the requested entry path.
* @return the requested entry or null if the entry does not exist
*/
public BundleEntry findLocalEntry(String path) {
return findLocalEntry(path, -1);
}
/**
* Finds a local entry by searching the ClasspathEntry with the specified
* class path index.
* @param path the requested entry path.
* @param classPathIndex the index of the ClasspathEntry to search
* @return the requested entry or null if the entry does not exist
*/
public BundleEntry findLocalEntry(String path, int classPathIndex) {
BundleEntry result = null;
int curIndex = 0;
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
result = findEntryImpl(path, entries[i].getBundleFile());
if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
return result;
}
curIndex++;
}
// look in fragments
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++) {
ClasspathEntry[] fragEntries = currentFragments[i].getEntries();
for (int j = 0; j < fragEntries.length; j++) {
result = findEntryImpl(path, fragEntries[j].getBundleFile());
if (result != null && (classPathIndex == -1 || classPathIndex == curIndex))
return result;
curIndex++;
}
}
return null;
}
/**
* Finds the local entries by searching the ClasspathEntry objects of the classpath manager.
* @param path the requested entry path.
* @return an enumeration of the the requested entries or null if the entries do not exist
*/
public Enumeration<BundleEntry> findLocalEntries(String path) {
List<BundleEntry> objects = new ArrayList<>(6);
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
BundleEntry result = findEntryImpl(path, entries[i].getBundleFile());
if (result != null)
objects.add(result);
}
}
// look in fragments
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++) {
ClasspathEntry[] fragEntries = currentFragments[i].getEntries();
for (int j = 0; j < fragEntries.length; j++) {
BundleEntry result = findEntryImpl(path, fragEntries[j].getBundleFile());
if (result != null)
objects.add(result);
}
}
if (objects.size() > 0)
return Collections.enumeration(objects);
return null;
}
private BundleEntry findEntryImpl(String path, BundleFile bundleFile) {
return bundleFile.getEntry(path);
}
/**
* Finds a local class by searching the ClasspathEntry objects of the classpath manager.
* This method will first call all the configured class loader hooks
* {@link ClassLoaderHook#preFindLocalClass(String, ClasspathManager)} methods. Then it
* will search for the class. If a class is found then
* <ol>
* <li>All configured class loader hooks
* {@link ClassLoaderHook#processClass(String, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
* methods will be called.</li>
* <li>The class is then defined.</li>
* <li>Finally, all configured class loading
* stats hooks {@link ClassLoaderHook#recordClassDefine(String, Class, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
* methods are called.</li>
* </ol>
* Finally all the configured class loading hooks
* {@link ClassLoaderHook#postFindLocalClass(String, Class, ClasspathManager)} methods are called.
* @param classname the requested class name.
* @return the requested class
* @throws ClassNotFoundException if the class does not exist
*/
public Class<?> findLocalClass(String classname) throws ClassNotFoundException {
Class<?> result = null;
List<ClassLoaderHook> hooks = hookRegistry.getClassLoaderHooks();
try {
for (ClassLoaderHook hook : hooks) {
hook.preFindLocalClass(classname, this);
}
result = classloader.publicFindLoaded(classname);
if (result != null)
return result;
result = findLocalClassImpl(classname, hooks);
return result;
} finally {
for (ClassLoaderHook hook : hooks) {
hook.postFindLocalClass(classname, result, this);
}
}
}
private Class<?> findLocalClassImpl(String classname, List<ClassLoaderHook> hooks) throws ClassNotFoundException {
Class<?> result = null;
for (int i = 0; i < entries.length; i++) {
if (entries[i] != null) {
result = findClassImpl(classname, entries[i], hooks);
if (result != null)
return result;
}
}
// look in fragments.
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (int i = 0; i < currentFragments.length; i++) {
ClasspathEntry[] fragEntries = currentFragments[i].getEntries();
for (int j = 0; j < fragEntries.length; j++) {
result = findClassImpl(classname, fragEntries[j], hooks);
if (result != null)
return result;
}
}
throw new ClassNotFoundException(classname);
}
private Class<?> findClassImpl(String name, ClasspathEntry classpathEntry, List<ClassLoaderHook> hooks) {
if (debug.DEBUG_LOADER)
Debug.println("ModuleClassLoader[" + classloader.getBundleLoader() + " - " + classpathEntry.getBundleFile() + "].findClassImpl(" + name + ")"); //$NON-NLS-1$ //$NON-NLS-2$//$NON-NLS-3$ //$NON-NLS-4$
String filename = name.replace('.', '/').concat(".class"); //$NON-NLS-1$
BundleEntry entry = classpathEntry.getBundleFile().getEntry(filename);
if (entry == null)
return null;
byte[] classbytes;
try {
classbytes = entry.getBytes();
} catch (IOException e) {
if (debug.DEBUG_LOADER)
Debug.println(" IOException reading " + filename + " from " + classpathEntry.getBundleFile()); //$NON-NLS-1$ //$NON-NLS-2$
throw (LinkageError) new LinkageError("Error reading class bytes: " + name).initCause(e); //$NON-NLS-1$
}
if (debug.DEBUG_LOADER) {
Debug.println(" read " + classbytes.length + " bytes from " + classpathEntry.getBundleFile() + "!/" + filename); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
Debug.println(" defining class " + name); //$NON-NLS-1$
}
try {
return defineClass(name, classbytes, classpathEntry, entry, hooks);
} catch (Error e) {
if (debug.DEBUG_LOADER)
Debug.println(" error defining class " + name); //$NON-NLS-1$
throw e;
}
}
static class DefineContext {
Collection<String> currentlyProcessing = new ArrayList<>(5);
Collection<String> currentlyDefining = new ArrayList<>(5);
}
/**
* Defines the specified class. This method will first call all the configured class loader hooks
* {@link ClassLoadingHook#processClass(String, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
* methods. If any hook modifies the bytes the all configured hook
* {@link ClassLoaderHook#rejectTransformation(String, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}
* methods are called. Then it will call the {@link ModuleClassLoader#defineClass(String, byte[], ClasspathEntry, BundleEntry)}
* method to define the class. After that, the class loader hooks are called to announce the class
* definition by calling {@link ClassLoaderHook#recordClassDefine(String, Class, byte[], ClasspathEntry, BundleEntry, ClasspathManager)}.
* @param name the name of the class to define
* @param classbytes the class bytes
* @param classpathEntry the classpath entry used to load the class bytes
* @param entry the BundleEntry used to load the class bytes
* @param hooks the class loader hooks
* @return the defined class
*/
private Class<?> defineClass(String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, List<ClassLoaderHook> hooks) {
DefineClassResult result = null;
boolean recursionDetected = false;
try {
definePackage(name, classpathEntry);
DefineContext context = currentDefineContext.get();
if (context == null) {
context = new DefineContext();
currentDefineContext.set(context);
}
// First call the hooks that do not handle recursion themselves
if (!hookRegistry.getContainer().isProcessClassRecursionSupportedByAll()) {
// One or more hooks do not support recursive class processing.
// We need to detect recursions for this set of hooks.
if (context.currentlyProcessing.contains(name)) {
// Avoid recursion for the same class name for these hooks
recursionDetected = true;
// TODO consider thrown a ClassCircularityError here
return null;
}
context.currentlyProcessing.add(name);
try {
for (ClassLoaderHook hook : hooks) {
if (!hook.isProcessClassRecursionSupported()) {
classbytes = processClass(hook, name, classbytes, classpathEntry, entry, this, hooks);
}
}
} finally {
context.currentlyProcessing.remove(name);
}
}
// Now call the hooks that do support recursion without the check.
for (ClassLoaderHook hook : hooks) {
if (hook.isProcessClassRecursionSupported()) {
// Note if the hooks don't take protective measures for a recursive class load here
// it will result in a stack overflow.
classbytes = processClass(hook, name, classbytes, classpathEntry, entry, this, hooks);
}
}
if (context.currentlyDefining.contains(name)) {
// TODO consider thrown a ClassCircularityError here
return null; // avoid recursive defines (bug 345500)
}
context.currentlyDefining.add(name);
try {
result = classloader.defineClass(name, classbytes, classpathEntry);
} finally {
context.currentlyDefining.remove(name);
}
} finally {
// only call hooks if we properly called processClass above
if (!recursionDetected) {
// only pass the newly defined class to the hook
Class<?> defined = result != null && result.defined ? result.clazz : null;
for (ClassLoaderHook hook : hooks) {
hook.recordClassDefine(name, defined, classbytes, classpathEntry, entry, this);
}
}
}
// return either the pre-loaded class or the newly defined class
return result == null ? null : result.clazz;
}
private byte[] processClass(ClassLoaderHook hook, String name, byte[] classbytes, ClasspathEntry classpathEntry, BundleEntry entry, ClasspathManager classpathManager, List<ClassLoaderHook> hooks) {
byte[] modifiedBytes = hook.processClass(name, classbytes, classpathEntry, entry, this);
if (modifiedBytes != null) {
// the WeavingHookConfigurator already calls the rejectTransformation method; avoid calling it again.
if (!(hook instanceof WeavingHookConfigurator)) {
for (ClassLoaderHook rejectHook : hooks) {
if (rejectHook.rejectTransformation(name, modifiedBytes, classpathEntry, entry, this)) {
modifiedBytes = null;
break;
}
}
}
if (modifiedBytes != null) {
classbytes = modifiedBytes;
}
}
return classbytes;
}
private void definePackage(String name, ClasspathEntry classpathEntry) {
// Define the package if it is not the default package.
int lastIndex = name.lastIndexOf('.');
if (lastIndex < 0) {
return;
}
String packageName = name.substring(0, lastIndex);
Object pkg = classloader.publicGetPackage(packageName);
if (pkg != null) {
return;
}
// get info about the package from the classpath entry's manifest.
String specTitle = null, specVersion = null, specVendor = null, implTitle = null, implVersion = null, implVendor = null;
if (generation.getBundleInfo().getStorage().getConfiguration().DEFINE_PACKAGE_ATTRIBUTES) {
ManifestPackageAttributes manifestPackageAttributes = classpathEntry.manifestPackageAttributesFor(packageName);
TitleVersionVendor specification = manifestPackageAttributes.getSpecification();
TitleVersionVendor implementation = manifestPackageAttributes.getImplementation();
specTitle = specification.getTitle();
specVersion = specification.getVersion();
specVendor = specification.getVendor();
implTitle = implementation.getTitle();
implVersion = implementation.getVersion();
implVendor = implementation.getVendor();
}
// The package is not defined yet define it before we define the class.
// TODO still need to seal packages.
classloader.publicDefinePackage(packageName, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, null);
}
/**
* Returns the fragment classpaths of this classpath manager
* @return the fragment classpaths of this classpath manager
*/
public FragmentClasspath[] getFragmentClasspaths() {
return fragments;
}
/**
* Returns the host classpath entries for this classpath manager
* @return the host classpath entries for this classpath manager
*/
public ClasspathEntry[] getHostClasspathEntries() {
return entries;
}
/**
* Finds a library for the bundle represented by this class path manager
* @param libname the library name
* @return The absolution path to the library or null if not found
*/
public String findLibrary(String libname) {
synchronized (this) {
if (loadedLibraries == null)
loadedLibraries = new ArrayMap<>(1);
}
synchronized (loadedLibraries) {
// we assume that each classloader will load a small number of of libraries
// instead of wasting space with a map we iterate over our collection of found libraries
// each element is a String[2], each array is {"libname", "libpath"}
String libpath = loadedLibraries.get(libname);
if (libpath != null)
return libpath;
libpath = findLibrary0(libname);
if (libpath != null)
loadedLibraries.put(libname, libpath);
return libpath;
}
}
private String findLibrary0(String libname) {
List<ClassLoaderHook> hooks = hookRegistry.getClassLoaderHooks();
String result = null;
for (ClassLoaderHook hook : hooks) {
try {
result = hook.preFindLibrary(libname, classloader);
if (result != null) {
return result;
}
} catch (FileNotFoundException e) {
return null;
}
}
result = generation.findLibrary(libname);
if (result != null) {
return result;
}
// look in fragment generations
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (FragmentClasspath fragment : currentFragments) {
result = fragment.getGeneration().findLibrary(libname);
if (result != null) {
return result;
}
}
for (ClassLoaderHook hook : hooks) {
result = hook.postFindLibrary(libname, classloader);
if (result != null) {
return result;
}
}
return result;
}
/**
* @see ModuleClassLoader#findEntries(String, String, int)
*/
public List<URL> findEntries(String path, String filePattern, int options) {
List<Generation> generations = new ArrayList<>();
// first get the host bundle file
generations.add(generation);
// next get the attached fragments bundle files
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (FragmentClasspath fragmentClasspath : currentFragments)
generations.add(fragmentClasspath.getGeneration());
List<URL> result = Collections.<URL> emptyList();
// now search over all the bundle files
Enumeration<URL> eURLs = Storage.findEntries(generations, path, filePattern, options);
if (eURLs == null)
return result;
result = new ArrayList<>();
while (eURLs.hasMoreElements())
result.add(eURLs.nextElement());
return Collections.unmodifiableList(result);
}
/**
* @see ModuleClassLoader#listLocalResources(String, String, int)
*/
public Collection<String> listLocalResources(String path, String filePattern, int options) {
List<BundleFile> bundleFiles = new ArrayList<>();
ClasspathEntry[] cpEntries = getHostClasspathEntries();
for (ClasspathEntry cpEntry : cpEntries)
bundleFiles.add(cpEntry.getBundleFile());
FragmentClasspath[] currentFragments = getFragmentClasspaths();
for (FragmentClasspath fragmentClasspath : currentFragments) {
ClasspathEntry[] fragEntries = fragmentClasspath.getEntries();
for (ClasspathEntry cpEntry : fragEntries)
bundleFiles.add(cpEntry.getBundleFile());
}
return Storage.listEntryPaths(bundleFiles, path, filePattern, options);
}
public Generation getGeneration() {
return generation;
}
public ModuleClassLoader getClassLoader() {
return classloader;
}
}