blob: a5a72e02b68285dcf499238158c27b474026c70a [file] [log] [blame]
/*
* 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()));
}
}