/* * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package dalvik.system; import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; import android.annotation.SystemApi; import sun.misc.CompoundEnumeration; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import libcore.util.NonNull; import libcore.util.Nullable; /** * A {@code ClassLoader} implementation that implements a delegate last lookup policy. * For every class or resource this loader is requested to load, the following lookup order * is employed: * * */ public final class DelegateLastClassLoader extends PathClassLoader { /** * Whether resource loading delegates to the parent class loader. True by default. */ private final boolean delegateResourceLoading; /** * Equivalent to calling {@link #DelegateLastClassLoader(String, String, ClassLoader, boolean)} * with {@code librarySearchPath = null, delegateResourceLoading = true}. */ public DelegateLastClassLoader(String dexPath, ClassLoader parent) { this(dexPath, null, parent, true); } /** * Equivalent to calling {@link #DelegateLastClassLoader(String, String, ClassLoader, boolean)} * with {@code delegateResourceLoading = true}. */ public DelegateLastClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { this(dexPath, librarySearchPath, parent, true); } /** * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} * and a {@code librarySearchPath}. * * The {@code dexPath} should consist of one or more of the following, separated by * {@code File.pathSeparator}, which is {@code ":"} on Android. * * * * Unlike {@link PathClassLoader}, this classloader will attempt to locate classes * (or resources) using the following lookup order. * * * Note that this is in contrast to other standard classloaders that follow the delegation * model. In those classloaders, the {@code parent} is always searched first. * * {@code librarySearchPath} specifies one more directories containing native library files, * separated by {@code File.pathSeparator}. * * @param dexPath the list of jar/apk files containing classes and resources, delimited by * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. * @param librarySearchPath the list of directories containing native libraries, delimited * by {@code File.pathSeparator}; may be {@code null}. * @param parent the parent class loader. May be {@code null} for the boot classloader. * @param delegateResourceLoading whether to delegate resource loading to the parent if * the resource is not found. This does not affect class * loading delegation. */ public DelegateLastClassLoader(@NonNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent, boolean delegateResourceLoading) { super(dexPath, librarySearchPath, parent); this.delegateResourceLoading = delegateResourceLoading; } /** * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} * and a {@code librarySearchPath}. * * The {@code dexPath} should consist of one or more of the following, separated by * {@code File.pathSeparator}, which is {@code ":"} on Android. * * * * @param dexPath the list of jar/apk files containing classes and resources, delimited by * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. * @param librarySearchPath the list of directories containing native libraries, delimited * by {@code File.pathSeparator}; may be {@code null}. * @param parent the parent class loader. May be {@code null} for the boot classloader. * @param sharedLibraryLoaders class loaders of Java shared libraries * used by this new class loader. The shared library loaders are * always checked before the {@code dexPath} when looking * up classes and resources. * * @hide */ @SystemApi(client = MODULE_LIBRARIES) public DelegateLastClassLoader( String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders) { this(dexPath, librarySearchPath, parent, sharedLibraryLoaders, null); } /** * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} * and a {@code librarySearchPath}. * * The {@code dexPath} should consist of one or more of the following, separated by * {@code File.pathSeparator}, which is {@code ":"} on Android. * * * * @param dexPath the list of jar/apk files containing classes and resources, delimited by * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. * @param librarySearchPath the list of directories containing native libraries, delimited * by {@code File.pathSeparator}; may be {@code null}. * @param parent the parent class loader. May be {@code null} for the boot classloader. * @param sharedLibraryLoaders class loaders of Java shared libraries * used by this new class loader. The shared library loaders are * always checked before the {@code dexPath} when looking * up classes and resources. * @param sharedLibraryLoadersAfter class loaders of Java shared libraries * used by this new class loader. These shared library loaders are * always checked after the {@code dexPath} when looking * up classes and resources. * * @hide */ @SystemApi(client = MODULE_LIBRARIES) public DelegateLastClassLoader( String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter) { super(dexPath, librarySearchPath, parent, sharedLibraryLoaders, sharedLibraryLoadersAfter); // Delegating is the default behavior. this.delegateResourceLoading = true; } @Override protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { // First, check whether the class has already been loaded. Return it if that's the // case. Class cl = findLoadedClass(name); if (cl != null) { return cl; } // Next, check whether the class in question is present in the boot classpath. try { return Object.class.getClassLoader().loadClass(name); } catch (ClassNotFoundException ignored) { } // Next, check whether the class in question is present in the dexPath that this classloader // operates on, or its shared libraries. ClassNotFoundException fromSuper = null; try { return findClass(name); } catch (ClassNotFoundException ex) { fromSuper = ex; } // Finally, check whether the class in question is present in the parent classloader. try { return getParent().loadClass(name); } catch (ClassNotFoundException cnfe) { // The exception we're catching here is the CNFE thrown by the parent of this // classloader. However, we would like to throw a CNFE that provides details about // the class path / list of dex files associated with *this* classloader, so we choose // to throw the exception thrown from that lookup. throw fromSuper; } } @Override public URL getResource(String name) { // The lookup order we use here is the same as for classes. URL resource = Object.class.getClassLoader().getResource(name); if (resource != null) { return resource; } resource = findResource(name); if (resource != null) { return resource; } if (delegateResourceLoading) { final ClassLoader cl = getParent(); return (cl == null) ? null : cl.getResource(name); } return null; } @Override public Enumeration getResources(String name) throws IOException { @SuppressWarnings("unchecked") final Enumeration[] resources = (Enumeration[]) new Enumeration[] { Object.class.getClassLoader().getResources(name), findResources(name), (getParent() == null || !delegateResourceLoading) ? null : getParent().getResources(name) }; return new CompoundEnumeration<>(resources); } }