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 @SystemApi(client = MODULE_LIBRARIES) DelegateLastClassLoader( String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders)136 public DelegateLastClassLoader( 137 String dexPath, String librarySearchPath, ClassLoader parent, 138 ClassLoader[] sharedLibraryLoaders) { 139 this(dexPath, librarySearchPath, parent, sharedLibraryLoaders, null); 140 } 141 142 /** 143 * Creates a {@code DelegateLastClassLoader} that operates on a given {@code dexPath} 144 * and a {@code librarySearchPath}. 145 * 146 * The {@code dexPath} should consist of one or more of the following, separated by 147 * {@code File.pathSeparator}, which is {@code ":"} on Android. 148 * 149 * <ul> 150 * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file as well as arbitrary 151 * resources. 152 * <li>Raw ".dex" files (not inside a zip file). 153 * </ul> 154 * 155 * @param dexPath the list of jar/apk files containing classes and resources, delimited by 156 * {@code File.pathSeparator}, which defaults to {@code ":"} on Android. 157 * @param librarySearchPath the list of directories containing native libraries, delimited 158 * by {@code File.pathSeparator}; may be {@code null}. 159 * @param parent the parent class loader. May be {@code null} for the boot classloader. 160 * @param sharedLibraryLoaders class loaders of Java shared libraries 161 * used by this new class loader. The shared library loaders are 162 * always checked before the {@code dexPath} when looking 163 * up classes and resources. 164 * @param sharedLibraryLoadersAfter class loaders of Java shared libraries 165 * used by this new class loader. These shared library loaders are 166 * always checked <b>after</b> the {@code dexPath} when looking 167 * up classes and resources. 168 * 169 * @hide 170 */ 171 @SystemApi(client = MODULE_LIBRARIES) DelegateLastClassLoader( String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter)172 public DelegateLastClassLoader( 173 String dexPath, String librarySearchPath, ClassLoader parent, 174 ClassLoader[] sharedLibraryLoaders, ClassLoader[] sharedLibraryLoadersAfter) { 175 super(dexPath, librarySearchPath, parent, sharedLibraryLoaders, sharedLibraryLoadersAfter); 176 // Delegating is the default behavior. 177 this.delegateResourceLoading = true; 178 } 179 180 @Override loadClass(String name, boolean resolve)181 protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { 182 // First, check whether the class has already been loaded. Return it if that's the 183 // case. 184 Class<?> cl = findLoadedClass(name); 185 if (cl != null) { 186 return cl; 187 } 188 189 // Next, check whether the class in question is present in the boot classpath. 190 try { 191 return Object.class.getClassLoader().loadClass(name); 192 } catch (ClassNotFoundException ignored) { 193 } 194 195 // Next, check whether the class in question is present in the dexPath that this classloader 196 // operates on, or its shared libraries. 197 ClassNotFoundException fromSuper = null; 198 try { 199 return findClass(name); 200 } catch (ClassNotFoundException ex) { 201 fromSuper = ex; 202 } 203 204 // Finally, check whether the class in question is present in the parent classloader. 205 try { 206 return getParent().loadClass(name); 207 } catch (ClassNotFoundException cnfe) { 208 // The exception we're catching here is the CNFE thrown by the parent of this 209 // classloader. However, we would like to throw a CNFE that provides details about 210 // the class path / list of dex files associated with *this* classloader, so we choose 211 // to throw the exception thrown from that lookup. 212 throw fromSuper; 213 } 214 } 215 216 @Override getResource(String name)217 public URL getResource(String name) { 218 // The lookup order we use here is the same as for classes. 219 220 URL resource = Object.class.getClassLoader().getResource(name); 221 if (resource != null) { 222 return resource; 223 } 224 225 resource = findResource(name); 226 if (resource != null) { 227 return resource; 228 } 229 230 if (delegateResourceLoading) { 231 final ClassLoader cl = getParent(); 232 return (cl == null) ? null : cl.getResource(name); 233 } 234 return null; 235 } 236 237 @Override getResources(String name)238 public Enumeration<URL> getResources(String name) throws IOException { 239 @SuppressWarnings("unchecked") 240 final Enumeration<URL>[] resources = (Enumeration<URL>[]) new Enumeration<?>[] { 241 Object.class.getClassLoader().getResources(name), 242 findResources(name), 243 (getParent() == null || !delegateResourceLoading) 244 ? null : getParent().getResources(name) }; 245 246 return new CompoundEnumeration<>(resources); 247 } 248 } 249