| /******************************************************************************* |
| * 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; |
| } |
| } |