| /* |
| * Copyright (C) 2010 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 libcore.java.lang.reflect; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.net.MalformedURLException; |
| import java.net.URL; |
| import java.net.URLClassLoader; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.concurrent.Callable; |
| import junit.framework.TestCase; |
| |
| /** |
| * This class creates another class loader to load multiple copies of various |
| * classes into the VM at once. Then it verifies that reflection resolves the |
| * class names using the correct class loader. |
| */ |
| public final class ClassLoaderReflectionTest extends TestCase { |
| |
| /* |
| * Each of these class instances points to a different copy of the class |
| * than the one in the application class loader! |
| */ |
| private Class<?> aClass; |
| private Class<?> aListClass; |
| private Class<?> bClass; |
| private Class<?> bStringClass; |
| private Class<?> cClass; |
| private Class<?> dClass; |
| private Class<?> eClass; |
| private Class<?> fClass; |
| |
| @Override protected void setUp() throws Exception { |
| String prefix = ClassLoaderReflectionTest.class.getName(); |
| ClassLoader loader = twoCopiesClassLoader(prefix, getClass().getClassLoader()); |
| aClass = loader.loadClass(prefix + "$A"); |
| bClass = loader.loadClass(prefix + "$B"); |
| cClass = loader.loadClass(prefix + "$C"); |
| dClass = loader.loadClass(prefix + "$D"); |
| eClass = loader.loadClass(prefix + "$E"); |
| fClass = loader.loadClass(prefix + "$F"); |
| aListClass = loader.loadClass(prefix + "$AList"); |
| bStringClass = loader.loadClass(prefix + "$BString"); |
| } |
| |
| public void testLoadOneClassInTwoClassLoadersSimultaneously() throws Exception { |
| assertEquals(aClass.getName(), A.class.getName()); |
| assertNotSame(aClass, A.class); |
| } |
| |
| public void testField() throws Exception { |
| assertEquals(aClass, aListClass.getDeclaredField("field").getType()); |
| } |
| |
| /** |
| * http://code.google.com/p/android/issues/detail?id=10111 |
| */ |
| public void testGenericSuperclassParameter() throws Exception { |
| assertParameterizedType(aListClass.getGenericSuperclass(), ArrayList.class, aClass); |
| } |
| |
| public void testGenericSuperclassRawType() throws Exception { |
| assertParameterizedType(bStringClass.getGenericSuperclass(), bClass, String.class); |
| } |
| |
| public void testTypeParameters() throws Exception { |
| TypeVariable<? extends Class<?>>[] typeVariables = cClass.getTypeParameters(); |
| assertEquals(2, typeVariables.length); |
| assertTypeVariable(typeVariables[0], "K", String.class); |
| assertTypeVariable(typeVariables[1], "V", aClass); |
| } |
| |
| public void testGenericInterfaces() throws Exception { |
| Type[] types = eClass.getGenericInterfaces(); |
| assertEquals(2, types.length); |
| // TODO: this test incorrectly assumes that interfaces will be returned in source order! |
| assertParameterizedType(types[0], Callable.class, aClass); |
| assertParameterizedType(types[1], dClass, aClass); |
| } |
| |
| public void testFieldGenericType() throws Exception { |
| Field bString = fClass.getDeclaredField("bString"); |
| assertParameterizedType(bString.getGenericType(), bClass, String.class); |
| Field listA = fClass.getDeclaredField("listA"); |
| assertParameterizedType(listA.getGenericType(), List.class, aClass); |
| } |
| |
| public void testConstructorGenericType() throws Exception { |
| Constructor<?> constructor = fClass.getDeclaredConstructors()[0]; |
| Type[] parameters = constructor.getGenericParameterTypes(); |
| assertParameterizedType(parameters[0], bClass, String.class); |
| assertParameterizedType(parameters[1], List.class, aClass); |
| } |
| |
| public void testMethodGenericReturnType() throws Exception { |
| Method method = fClass.getDeclaredMethod("method", bClass, List.class); |
| assertParameterizedType(method.getGenericReturnType(), bClass, String.class); |
| } |
| |
| public void testMethodGenericParameterTypes() throws Exception { |
| Method method = fClass.getDeclaredMethod("method", bClass, List.class); |
| Type[] types = method.getGenericParameterTypes(); |
| assertEquals(2, types.length); |
| assertParameterizedType(types[0], bClass, String.class); |
| assertParameterizedType(types[1], List.class, aClass); |
| } |
| |
| static class A {} |
| static class B<T> { |
| T field; |
| } |
| static class C<K extends String, V extends A> {} |
| interface D<T> {} |
| class E implements Callable<A>, D<A> { |
| public A call() throws Exception { |
| return null; |
| } |
| } |
| class F { |
| B<String> bString; |
| List<A> listA; |
| F(B<String> parameter, List<A> anotherParameter) {} |
| B<String> method(B<String> parameter, List<A> anotherParameter) { |
| return null; |
| } |
| } |
| static class AList extends ArrayList<A> { |
| A field; |
| } |
| static class BString extends B<String> {} |
| |
| /** |
| * Returns a class loader that permits multiple copies of the same class to |
| * be loaded into the same VM at the same time. This loads classes using the |
| * same classpath as the application class loader. |
| * |
| * @param prefix the prefix of classes that can be loaded by both the |
| * returned class loader and the application class loader. |
| */ |
| private ClassLoader twoCopiesClassLoader(final String prefix, ClassLoader parent) |
| throws IOException, InterruptedException { |
| |
| /* |
| * To load two copies of a given class in the VM, we end up creating two |
| * new class loaders: a bridge class loader and a leaf class loader. |
| * |
| * The bridge class loader is a child of the application class loader. |
| * It never loads any classes. All it does is decide when to delegate to |
| * the application class loader (which has a copy of everything) and |
| * when to fail. |
| * |
| * The leaf class loader is a child of the bridge class loader. It |
| * uses the same classpath as the application class loader. It loads |
| * anything that its parent failed on. |
| */ |
| |
| ClassLoader bridge = new ClassLoader(parent) { |
| @Override protected Class<?> loadClass(String className, boolean resolve) |
| throws ClassNotFoundException { |
| if (className.startsWith(prefix)) { |
| /* throwing will cause the child class loader to load the class. */ |
| throw new ClassNotFoundException(); |
| } else { |
| return super.loadClass(className, resolve); |
| } |
| } |
| }; |
| |
| try { |
| // first try to create a PathClassLoader for a dalvik VM... |
| String classPath = System.getProperty("java.class.path"); |
| return (ClassLoader) Class.forName("dalvik.system.PathClassLoader") |
| .getConstructor(String.class, ClassLoader.class) |
| .newInstance(classPath, bridge); |
| } catch (Exception ignored) { |
| } |
| |
| // fall back to a URLClassLoader on a JVM |
| List<URL> classpath = new ArrayList<URL>(); |
| classpath.addAll(classpathToUrls("java.class.path")); |
| classpath.addAll(classpathToUrls("sun.boot.class.path")); |
| return new URLClassLoader(classpath.toArray(new URL[classpath.size()]), bridge); |
| } |
| |
| private List<URL> classpathToUrls(String propertyName) throws MalformedURLException { |
| String classpath = System.getProperty(propertyName); |
| List<URL> result = new ArrayList<URL>(); |
| for (String pathElement : classpath.split(File.pathSeparator)) { |
| result.add(new File(pathElement).toURI().toURL()); |
| } |
| return result; |
| } |
| |
| private void assertParameterizedType(Type actual, Type raw, Type... args) { |
| assertTrue(actual.toString(), actual instanceof ParameterizedType); |
| ParameterizedType parameterizedType = (ParameterizedType) actual; |
| assertEquals(raw, parameterizedType.getRawType()); |
| assertEquals(Arrays.<Type>asList(args), |
| Arrays.asList(parameterizedType.getActualTypeArguments())); |
| } |
| |
| private void assertTypeVariable(TypeVariable actual, String name, Type... bounds) { |
| assertEquals(name, actual.getName()); |
| assertEquals(Arrays.<Type>asList(bounds), Arrays.asList(actual.getBounds())); |
| } |
| } |