1649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson/*
2649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * Copyright (C) 2010 The Android Open Source Project
3649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson *
4649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * Licensed under the Apache License, Version 2.0 (the "License");
5649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * you may not use this file except in compliance with the License.
6649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * You may obtain a copy of the License at
7649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson *
8649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson *      http://www.apache.org/licenses/LICENSE-2.0
9649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson *
10649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * Unless required by applicable law or agreed to in writing, software
11649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * distributed under the License is distributed on an "AS IS" BASIS,
12649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * See the License for the specific language governing permissions and
14649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * limitations under the License.
15649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson */
16649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
17649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonpackage tests.util;
18649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
19649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.io.File;
20649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.net.MalformedURLException;
21649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.net.URL;
22649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.net.URLClassLoader;
23649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.util.ArrayList;
24649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.util.HashSet;
25649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.util.List;
26649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonimport java.util.Set;
27649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
28649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson/**
29649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * Builds a configured class loader. The constructed class loader can load a
30649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * private copy of a class in addition to the copy in the application class
31649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * loader. It can also refuse to load a class altogether, even if that class
32649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson * exists on the classpath.
33649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson */
34649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilsonpublic final class ClassLoaderBuilder {
35649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    private ClassLoader parent = ClassLoaderBuilder.class.getClassLoader();
36649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    private final Set<String> prefixesToNotInherit = new HashSet<String>();
37649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    private final Set<String> prefixesToLoad = new HashSet<String>();
38649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
39649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    public ClassLoaderBuilder parent(ClassLoader parent) {
40649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        this.parent = parent;
41649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        return this;
42649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    }
43649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
44649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    /**
45649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     * @param classNamePrefix the prefix of classes that can be loaded by both
46649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     *     the constructed class loader and the application class loader.
47649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     */
48649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    public ClassLoaderBuilder withPrivateCopy(String classNamePrefix) {
49649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        prefixesToLoad.add(classNamePrefix);
50649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        return this;
51649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    }
52649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
53649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    /**
54649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     * @param classNamePrefix the prefix of classes that will not be loaded by
55649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     *     this class loader. Attempts to load such classes will fail at runtime
56649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     *     with a NoClassDefFoundError.
57649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson     */
58649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    public ClassLoaderBuilder without(String classNamePrefix) {
59649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        prefixesToNotInherit.add(classNamePrefix);
60649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        return this;
61649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    }
62649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
63649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    public ClassLoader build() {
64649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        // make a defensive copy in case this builder is reused!
65649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        final Set<String> prefixesToNotInherit = new HashSet<String>(this.prefixesToNotInherit);
66649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        final Set<String> prefixesToLoad = new HashSet<String>(this.prefixesToLoad);
67649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
68649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        /*
69649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * To load two copies of a given class in the VM, we end up creating two
70649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * new class loaders: a bridge class loader and a leaf class loader.
71649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         *
72649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * The bridge class loader is a child of the application class loader.
73649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * It never loads any classes. All it does is decide when to delegate to
74649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * the application class loader (which has a copy of everything) and
75649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * when to fail.
76649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         *
77649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * The leaf class loader is a child of the bridge class loader. It
78649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * uses the same classpath as the application class loader. It loads
79649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         * anything that its parent failed on.
80649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson         */
81649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
82649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        ClassLoader bridge = new ClassLoader(parent) {
83649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            @Override protected Class<?> loadClass(String className, boolean resolve)
84649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    throws ClassNotFoundException {
85649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                for (String prefix : prefixesToLoad) {
86649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    if (className.startsWith(prefix)) {
87649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                        /* ClassNotFoundException causes the child loader to load the class. */
88649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                        throw new ClassNotFoundException();
89649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    }
90649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                }
91649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
92649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                for (String prefix : prefixesToNotInherit) {
93649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    if (className.startsWith(prefix)) {
94649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                        /* NoClassDefFoundError prevents the class from being loaded at all. */
95649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                        throw new NoClassDefFoundError();
96649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    }
97649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                }
98649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
99649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                return super.loadClass(className, resolve);
100649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            }
101649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        };
102649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
103649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        try {
104649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            // first try to create a PathClassLoader for a dalvik VM...
105ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson            String classPath = getApplicationClassPath();
106649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            return (ClassLoader) Class.forName("dalvik.system.PathClassLoader")
107649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    .getConstructor(String.class, ClassLoader.class)
108649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                    .newInstance(classPath, bridge);
109649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        } catch (Exception ignored) {
110649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        }
111649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
112649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        // fall back to a URLClassLoader on a JVM
113649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        List<URL> classpath = new ArrayList<URL>();
114649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        classpath.addAll(classpathToUrls("java.class.path"));
115649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        classpath.addAll(classpathToUrls("sun.boot.class.path"));
116649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), bridge);
117649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    }
118649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson
119ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson    /**
120ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson     * Returns a path containing the application's classes. When running in the
121ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson     * Android framework this will be the APK file; otherwise it's the runtime's
122ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson     * reported class path.
123ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson     */
124ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson    private String getApplicationClassPath() {
125ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        String manifestFile = "AndroidManifest.xml";
126ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        String suffix = "!/" + manifestFile;
127ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
128ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        URL manifest = classLoader.getResource(manifestFile);
129ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        if (manifest != null) {
130ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson            String manifestString = manifest.toString();
131ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson            if (manifestString.endsWith(suffix)) {
132ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson                return manifestString.substring(0, manifestString.length() - suffix.length());
133ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson            }
134ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        }
135ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson        return System.getProperty("java.class.path");
136ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson    }
137ec4d64d1a6421b89fbc65da9d18aae89814474d7Jesse Wilson
138649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    private List<URL> classpathToUrls(String propertyName) {
139649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        try {
140649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            String classpath = System.getProperty(propertyName);
141649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            List<URL> result = new ArrayList<URL>();
142649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            for (String pathElement : classpath.split(File.pathSeparator)) {
143649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson                result.add(new File(pathElement).toURI().toURL());
144649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            }
145649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            return result;
146649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        } catch (MalformedURLException e) {
147649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson            throw new RuntimeException(e);
148649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson        }
149649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson    }
150649e3c0004d965fe388ad454eeb20e307a12edd2Jesse Wilson}
151