1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package dalvik.system; 18 19 import static android.annotation.SystemApi.Client.MODULE_LIBRARIES; 20 21 import android.annotation.SystemApi; 22 23 import sun.misc.CompoundEnumeration; 24 25 import java.io.IOException; 26 import java.net.URL; 27 import java.util.Enumeration; 28 29 import libcore.util.NonNull; 30 import libcore.util.Nullable; 31 32 /** 33 * A {@code ClassLoader} implementation that implements a <b>delegate last</b> lookup policy. 34 * For every class or resource this loader is requested to load, the following lookup order 35 * is employed: 36 * 37 * <ul> 38 * <li>The boot classpath is always searched first</li> 39 * <li>Then, the list of {@code dex} files associated with this classloaders's 40 * {@code dexPath} is searched.</li> 41 * <li>Finally, this classloader will delegate to the specified {@code parent}.</li> 42 * </ul> 43 */ 44 public final class DelegateLastClassLoader extends PathClassLoader { 45 46 /** 47 * Whether resource loading delegates to the parent class loader. True by default. 48 */ 49 private final boolean delegateResourceLoading; 50 51 /** 52 * Equivalent to calling {@link #DelegateLastClassLoader(String, String, ClassLoader, boolean)} 53 * with {@code librarySearchPath = null, delegateResourceLoading = true}. 54 */ DelegateLastClassLoader(String dexPath, ClassLoader parent)55 public DelegateLastClassLoader(String dexPath, ClassLoader parent) { 56 this(dexPath, null, parent, true); 57 } 58 59 /** 60 * Equivalent to calling {@link #DelegateLastClassLoader(String, String, ClassLoader, boolean)} 61 * with {@code delegateResourceLoading = true}. 62 */ DelegateLastClassLoader(String dexPath, String librarySearchPath, ClassLoader parent)63 public DelegateLastClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) { 64 this(dexPath, librarySearchPath, parent, true); 65 } 66 67 /** 68 * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} 69 * and a {@code librarySearchPath}. 70 * 71 * The {@code dexPath} should consist of one or more of the following, separated by 72 * {@code File.pathSeparator}, which is {@code ":"} on Android. 73 * 74 * <ul> 75 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as well as arbitrary 76 * resources. 77 * <li>Raw ".dex" files (not inside a zip file). 78 * </ul> 79 * 80 * Unlike {@link PathClassLoader}, this classloader will attempt to locate classes 81 * (or resources) using the following lookup order. 82 * <ul> 83 * <li>The boot classpath is always searched first.</li> 84 * <li>Then, the list of {@code dex} files contained in {@code dexPath} is searched./li> 85 * <li>Lastly, this classloader will delegate to the specified {@code parent}.</li> 86 * </ul> 87 * 88 * Note that this is in contrast to other standard classloaders that follow the delegation 89 * model. In those classloaders, the {@code parent} is always searched first. 90 * 91 * {@code librarySearchPath} specifies one more directories containing native library files, 92 * separated by {@code File.pathSeparator}. 93 * 94 * @param dexPath the list of jar/apk files containing classes and resources, delimited by 95 * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. 96 * @param librarySearchPath the list of directories containing native libraries, delimited 97 * by {@code File.pathSeparator}; may be {@code null}. 98 * @param parent the parent class loader. May be {@code null} for the boot classloader. 99 * @param delegateResourceLoading whether to delegate resource loading to the parent if 100 * the resource is not found. This does not affect class 101 * loading delegation. 102 */ 103 DelegateLastClassLoader(@onNull String dexPath, @Nullable String librarySearchPath, @Nullable ClassLoader parent, boolean delegateResourceLoading)104 public DelegateLastClassLoader(@NonNull String dexPath, @Nullable String librarySearchPath, 105 @Nullable ClassLoader parent, boolean delegateResourceLoading) { 106 super(dexPath, librarySearchPath, parent); 107 this.delegateResourceLoading = delegateResourceLoading; 108 } 109 110 /** 111 * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} 112 * and a {@code librarySearchPath}. 113 * 114 * The {@code dexPath} should consist of one or more of the following, separated by 115 * {@code File.pathSeparator}, which is {@code ":"} on Android. 116 * 117 * <ul> 118 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as well as arbitrary 119 * resources. 120 * <li>Raw ".dex" files (not inside a zip file). 121 * </ul> 122 * 123 * @param dexPath the list of jar/apk files containing classes and resources, delimited by 124 * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. 125 * @param librarySearchPath the list of directories containing native libraries, delimited 126 * by {@code File.pathSeparator}; may be {@code null}. 127 * @param parent the parent class loader. May be {@code null} for the boot classloader. 128 * @param sharedLibraryLoaders class loaders of Java shared libraries 129 * used by this new class loader. The shared library loaders are 130 * always checked before the {@code dexPath} when looking 131 * up classes and resources. 132 * 133 * @hide 134 */ 135 @libcore.api.CorePlatformApi(status = libcore.api.CorePlatformApi.Status.STABLE) 136 @SystemApi(client = MODULE_LIBRARIES) DelegateLastClassLoader( String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders)137 public DelegateLastClassLoader( 138 String dexPath, String librarySearchPath, ClassLoader parent, 139 ClassLoader[] sharedLibraryLoaders) { 140 super(dexPath, librarySearchPath, parent, sharedLibraryLoaders); 141 // Delegating is the default behavior. 142 this.delegateResourceLoading = true; 143 } 144 145 @Override loadClass(String name, boolean resolve)146 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 147 // First, check whether the class has already been loaded. Return it if that's the 148 // case. 149 Class<?> cl = findLoadedClass(name); 150 if (cl != null) { 151 return cl; 152 } 153 154 // Next, check whether the class in question is present in the boot classpath. 155 try { 156 return Object.class.getClassLoader().loadClass(name); 157 } catch (ClassNotFoundException ignored) { 158 } 159 160 // Next, check whether the class in question is present in the dexPath that this classloader 161 // operates on, or its shared libraries. 162 ClassNotFoundException fromSuper = null; 163 try { 164 return findClass(name); 165 } catch (ClassNotFoundException ex) { 166 fromSuper = ex; 167 } 168 169 // Finally, check whether the class in question is present in the parent classloader. 170 try { 171 return getParent().loadClass(name); 172 } catch (ClassNotFoundException cnfe) { 173 // The exception we're catching here is the CNFE thrown by the parent of this 174 // classloader. However, we would like to throw a CNFE that provides details about 175 // the class path / list of dex files associated with *this* classloader, so we choose 176 // to throw the exception thrown from that lookup. 177 throw fromSuper; 178 } 179 } 180 181 @Override getResource(String name)182 public URL getResource(String name) { 183 // The lookup order we use here is the same as for classes. 184 185 URL resource = Object.class.getClassLoader().getResource(name); 186 if (resource != null) { 187 return resource; 188 } 189 190 resource = findResource(name); 191 if (resource != null) { 192 return resource; 193 } 194 195 if (delegateResourceLoading) { 196 final ClassLoader cl = getParent(); 197 return (cl == null) ? null : cl.getResource(name); 198 } 199 return null; 200 } 201 202 @Override getResources(String name)203 public Enumeration<URL> getResources(String name) throws IOException { 204 @SuppressWarnings("unchecked") 205 final Enumeration<URL>[] resources = (Enumeration<URL>[]) new Enumeration<?>[] { 206 Object.class.getClassLoader().getResources(name), 207 findResources(name), 208 (getParent() == null || !delegateResourceLoading) 209 ? null : getParent().getResources(name) }; 210 211 return new CompoundEnumeration<>(resources); 212 } 213 } 214