1 /* 2 * Copyright (C) 2010 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 tests.util; 18 19 import java.io.File; 20 import java.net.MalformedURLException; 21 import java.net.URL; 22 import java.net.URLClassLoader; 23 import java.util.ArrayList; 24 import java.util.HashSet; 25 import java.util.List; 26 import java.util.Set; 27 28 /** 29 * Builds a configured class loader. The constructed class loader can load a 30 * private copy of a class in addition to the copy in the application class 31 * loader. It can also refuse to load a class altogether, even if that class 32 * exists on the classpath. 33 */ 34 public final class ClassLoaderBuilder { 35 private ClassLoader parent = ClassLoaderBuilder.class.getClassLoader(); 36 private final Set<String> prefixesToNotInherit = new HashSet<String>(); 37 private final Set<String> prefixesToLoad = new HashSet<String>(); 38 39 public ClassLoaderBuilder parent(ClassLoader parent) { 40 this.parent = parent; 41 return this; 42 } 43 44 /** 45 * @param classNamePrefix the prefix of classes that can be loaded by both 46 * the constructed class loader and the application class loader. 47 */ 48 public ClassLoaderBuilder withPrivateCopy(String classNamePrefix) { 49 prefixesToLoad.add(classNamePrefix); 50 return this; 51 } 52 53 /** 54 * @param classNamePrefix the prefix of classes that will not be loaded by 55 * this class loader. Attempts to load such classes will fail at runtime 56 * with a NoClassDefFoundError. 57 */ 58 public ClassLoaderBuilder without(String classNamePrefix) { 59 prefixesToNotInherit.add(classNamePrefix); 60 return this; 61 } 62 63 public ClassLoader build() { 64 // make a defensive copy in case this builder is reused! 65 final Set<String> prefixesToNotInherit = new HashSet<String>(this.prefixesToNotInherit); 66 final Set<String> prefixesToLoad = new HashSet<String>(this.prefixesToLoad); 67 68 /* 69 * To load two copies of a given class in the VM, we end up creating two 70 * new class loaders: a bridge class loader and a leaf class loader. 71 * 72 * The bridge class loader is a child of the application class loader. 73 * It never loads any classes. All it does is decide when to delegate to 74 * the application class loader (which has a copy of everything) and 75 * when to fail. 76 * 77 * The leaf class loader is a child of the bridge class loader. It 78 * uses the same classpath as the application class loader. It loads 79 * anything that its parent failed on. 80 */ 81 82 ClassLoader bridge = new ClassLoader(parent) { 83 @Override protected Class<?> loadClass(String className, boolean resolve) 84 throws ClassNotFoundException { 85 for (String prefix : prefixesToLoad) { 86 if (className.startsWith(prefix)) { 87 /* ClassNotFoundException causes the child loader to load the class. */ 88 throw new ClassNotFoundException(); 89 } 90 } 91 92 for (String prefix : prefixesToNotInherit) { 93 if (className.startsWith(prefix)) { 94 /* NoClassDefFoundError prevents the class from being loaded at all. */ 95 throw new NoClassDefFoundError(); 96 } 97 } 98 99 return super.loadClass(className, resolve); 100 } 101 }; 102 103 try { 104 // first try to create a PathClassLoader for a dalvik VM... 105 String classPath = getApplicationClassPath(); 106 return (ClassLoader) Class.forName("dalvik.system.PathClassLoader") 107 .getConstructor(String.class, ClassLoader.class) 108 .newInstance(classPath, bridge); 109 } catch (Exception ignored) { 110 } 111 112 // fall back to a URLClassLoader on a JVM 113 List<URL> classpath = new ArrayList<URL>(); 114 classpath.addAll(classpathToUrls("java.class.path")); 115 classpath.addAll(classpathToUrls("sun.boot.class.path")); 116 return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), bridge); 117 } 118 119 /** 120 * Returns a path containing the application's classes. When running in the 121 * Android framework this will be the APK file; otherwise it's the runtime's 122 * reported class path. 123 */ 124 private String getApplicationClassPath() { 125 String manifestFile = "AndroidManifest.xml"; 126 String suffix = "!/" + manifestFile; 127 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 128 URL manifest = classLoader.getResource(manifestFile); 129 if (manifest != null) { 130 String manifestString = manifest.toString(); 131 if (manifestString.endsWith(suffix)) { 132 return manifestString.substring(0, manifestString.length() - suffix.length()); 133 } 134 } 135 return System.getProperty("java.class.path"); 136 } 137 138 private List<URL> classpathToUrls(String propertyName) { 139 try { 140 String classpath = System.getProperty(propertyName); 141 List<URL> result = new ArrayList<URL>(); 142 for (String pathElement : classpath.split(File.pathSeparator)) { 143 result.add(new File(pathElement).toURI().toURL()); 144 } 145 return result; 146 } catch (MalformedURLException e) { 147 throw new RuntimeException(e); 148 } 149 } 150 } 151