提交 efb39d2b 编写于 作者: J jfranck

8009411: (reflect) Class.getMethods should not include static methods from interfaces

Summary: Update getMethods() and getMethod() to filter out interface statics
Reviewed-by: darcy
Contributed-by: joel.franck@oracle.com, andreas.lundblad@oracle.com, amy.lu@oracle.com, peter.levart@gmail.com
上级 7580bffe
...@@ -1571,6 +1571,10 @@ public final class Class<T> implements java.io.Serializable, ...@@ -1571,6 +1571,10 @@ public final class Class<T> implements java.io.Serializable,
* <p> If this {@code Class} object represents a primitive type or void, * <p> If this {@code Class} object represents a primitive type or void,
* then the returned array has length 0. * then the returned array has length 0.
* *
* <p> Static methods declared in superinterfaces of the class or interface
* represented by this {@code Class} object are not considered members of
* the class or interface.
*
* <p> The elements in the returned array are not sorted and are not in any * <p> The elements in the returned array are not sorted and are not in any
* particular order. * particular order.
* *
...@@ -1729,6 +1733,10 @@ public final class Class<T> implements java.io.Serializable, ...@@ -1729,6 +1733,10 @@ public final class Class<T> implements java.io.Serializable,
* <p> If this {@code Class} object represents an array type, then this * <p> If this {@code Class} object represents an array type, then this
* method does not find the {@code clone()} method. * method does not find the {@code clone()} method.
* *
* <p> Static methods declared in superinterfaces of the class or interface
* represented by this {@code Class} object are not considered members of
* the class or interface.
*
* @param name the name of the method * @param name the name of the method
* @param parameterTypes the list of parameters * @param parameterTypes the list of parameters
* @return the {@code Method} object that matches the specified * @return the {@code Method} object that matches the specified
...@@ -1752,7 +1760,7 @@ public final class Class<T> implements java.io.Serializable, ...@@ -1752,7 +1760,7 @@ public final class Class<T> implements java.io.Serializable,
public Method getMethod(String name, Class<?>... parameterTypes) public Method getMethod(String name, Class<?>... parameterTypes)
throws NoSuchMethodException, SecurityException { throws NoSuchMethodException, SecurityException {
checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true); checkMemberAccess(Member.PUBLIC, Reflection.getCallerClass(), true);
Method method = getMethod0(name, parameterTypes); Method method = getMethod0(name, parameterTypes, true);
if (method == null) { if (method == null) {
throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes)); throw new NoSuchMethodException(getName() + "." + name + argumentTypesToString(parameterTypes));
} }
...@@ -2727,6 +2735,14 @@ public final class Class<T> implements java.io.Serializable, ...@@ -2727,6 +2735,14 @@ public final class Class<T> implements java.io.Serializable,
} }
} }
void addAllNonStatic(Method[] methods) {
for (Method candidate : methods) {
if (!Modifier.isStatic(candidate.getModifiers())) {
add(candidate);
}
}
}
int length() { int length() {
return length; return length;
} }
...@@ -2797,7 +2813,7 @@ public final class Class<T> implements java.io.Serializable, ...@@ -2797,7 +2813,7 @@ public final class Class<T> implements java.io.Serializable,
MethodArray inheritedMethods = new MethodArray(); MethodArray inheritedMethods = new MethodArray();
Class<?>[] interfaces = getInterfaces(); Class<?>[] interfaces = getInterfaces();
for (int i = 0; i < interfaces.length; i++) { for (int i = 0; i < interfaces.length; i++) {
inheritedMethods.addAll(interfaces[i].privateGetPublicMethods()); inheritedMethods.addAllNonStatic(interfaces[i].privateGetPublicMethods());
} }
if (!isInterface()) { if (!isInterface()) {
Class<?> c = getSuperclass(); Class<?> c = getSuperclass();
...@@ -2900,7 +2916,7 @@ public final class Class<T> implements java.io.Serializable, ...@@ -2900,7 +2916,7 @@ public final class Class<T> implements java.io.Serializable,
} }
private Method getMethod0(String name, Class<?>[] parameterTypes) { private Method getMethod0(String name, Class<?>[] parameterTypes, boolean includeStaticMethods) {
// Note: the intent is that the search algorithm this routine // Note: the intent is that the search algorithm this routine
// uses be equivalent to the ordering imposed by // uses be equivalent to the ordering imposed by
// privateGetPublicMethods(). It fetches only the declared // privateGetPublicMethods(). It fetches only the declared
...@@ -2913,25 +2929,23 @@ public final class Class<T> implements java.io.Serializable, ...@@ -2913,25 +2929,23 @@ public final class Class<T> implements java.io.Serializable,
if ((res = searchMethods(privateGetDeclaredMethods(true), if ((res = searchMethods(privateGetDeclaredMethods(true),
name, name,
parameterTypes)) != null) { parameterTypes)) != null) {
return res; if (includeStaticMethods || !Modifier.isStatic(res.getModifiers()))
return res;
} }
// Search superclass's methods // Search superclass's methods
if (!isInterface()) { if (!isInterface()) {
Class<? super T> c = getSuperclass(); Class<? super T> c = getSuperclass();
if (c != null) { if (c != null) {
if ((res = c.getMethod0(name, parameterTypes)) != null) { if ((res = c.getMethod0(name, parameterTypes, true)) != null) {
return res; return res;
} }
} }
} }
// Search superinterfaces' methods // Search superinterfaces' methods
Class<?>[] interfaces = getInterfaces(); Class<?>[] interfaces = getInterfaces();
for (int i = 0; i < interfaces.length; i++) { for (Class<?> c : interfaces)
Class<?> c = interfaces[i]; if ((res = c.getMethod0(name, parameterTypes, false)) != null)
if ((res = c.getMethod0(name, parameterTypes)) != null) {
return res; return res;
}
}
// Not found // Not found
return null; return null;
} }
......
...@@ -44,30 +44,86 @@ import static org.testng.Assert.assertEquals; ...@@ -44,30 +44,86 @@ import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue; import static org.testng.Assert.assertTrue;
import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertNotNull; import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.fail;
import org.testng.annotations.Test; import org.testng.annotations.Test;
import static helper.Mod.*; import static helper.Mod.*;
import static helper.Declared.*; import static helper.Declared.*;
import helper.Mod; import helper.Mod;
public class DefaultStaticInvokeTest { public class DefaultStaticInvokeTest {
// getMethods(): Make sure getMethods returns the expected methods.
@Test(dataProvider = "testCasesAll", @Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class) dataProviderClass = DefaultStaticTestData.class)
public void testGetMethods(String testTarget, Object param) public void testGetMethods(String testTarget, Object param)
throws Exception { throws Exception {
// test the methods retrieved by getMethods()
testMethods(ALL_METHODS, testTarget, param); testMethods(ALL_METHODS, testTarget, param);
} }
// getDeclaredMethods(): Make sure getDeclaredMethods returns the expected methods.
@Test(dataProvider = "testCasesAll", @Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class) dataProviderClass = DefaultStaticTestData.class)
public void testGetDeclaredMethods(String testTarget, Object param) public void testGetDeclaredMethods(String testTarget, Object param)
throws Exception { throws Exception {
// test the methods retrieved by getDeclaredMethods()
testMethods(DECLARED_ONLY, testTarget, param); testMethods(DECLARED_ONLY, testTarget, param);
} }
// getMethod(): Make sure that getMethod finds all methods it should find.
@Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class)
public void testGetMethod(String testTarget, Object param)
throws Exception {
Class<?> typeUnderTest = Class.forName(testTarget);
MethodDesc[] descs = typeUnderTest.getAnnotationsByType(MethodDesc.class);
for (MethodDesc desc : descs) {
assertTrue(isFoundByGetMethod(typeUnderTest,
desc.name(),
argTypes(param)));
}
}
// getMethod(): Make sure that getMethod does *not* find certain methods.
@Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class)
public void testGetMethodSuperInterfaces(String testTarget, Object param)
throws Exception {
// Make sure static methods in superinterfaces are not found (unless the type under
// test declares a static method with the same signature).
Class<?> typeUnderTest = Class.forName(testTarget);
for (Class<?> interfaze : typeUnderTest.getInterfaces()) {
for (MethodDesc desc : interfaze.getAnnotationsByType(MethodDesc.class)) {
boolean isStatic = desc.mod() == STATIC;
boolean declaredInThisType = isMethodDeclared(typeUnderTest,
desc.name());
boolean expectedToBeFound = !isStatic || declaredInThisType;
if (expectedToBeFound)
continue; // already tested in testGetMethod()
assertFalse(isFoundByGetMethod(typeUnderTest,
desc.name(),
argTypes(param)));
}
}
}
// Method.invoke(): Make sure Method.invoke returns the expected value.
@Test(dataProvider = "testCasesAll", @Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class) dataProviderClass = DefaultStaticTestData.class)
public void testMethodInvoke(String testTarget, Object param) public void testMethodInvoke(String testTarget, Object param)
...@@ -78,11 +134,13 @@ public class DefaultStaticInvokeTest { ...@@ -78,11 +134,13 @@ public class DefaultStaticInvokeTest {
// test the method retrieved by Class.getMethod(String, Object[]) // test the method retrieved by Class.getMethod(String, Object[])
for (MethodDesc toTest : expectedMethods) { for (MethodDesc toTest : expectedMethods) {
String name = toTest.name(); String name = toTest.name();
Method m = getTestMethod(typeUnderTest, name, param); Method m = typeUnderTest.getMethod(name, argTypes(param));
testThisMethod(toTest, m, typeUnderTest, param); testThisMethod(toTest, m, typeUnderTest, param);
} }
} }
// MethodHandle.invoke(): Make sure MethodHandle.invoke returns the expected value.
@Test(dataProvider = "testCasesAll", @Test(dataProvider = "testCasesAll",
dataProviderClass = DefaultStaticTestData.class) dataProviderClass = DefaultStaticTestData.class)
public void testMethodHandleInvoke(String testTarget, Object param) public void testMethodHandleInvoke(String testTarget, Object param)
...@@ -116,6 +174,7 @@ public class DefaultStaticInvokeTest { ...@@ -116,6 +174,7 @@ public class DefaultStaticInvokeTest {
} }
// Lookup.findStatic / .findVirtual: Make sure IllegalAccessException is thrown as expected.
@Test(dataProvider = "testClasses", @Test(dataProvider = "testClasses",
dataProviderClass = DefaultStaticTestData.class) dataProviderClass = DefaultStaticTestData.class)
public void testIAE(String testTarget, Object param) public void testIAE(String testTarget, Object param)
...@@ -128,7 +187,7 @@ public class DefaultStaticInvokeTest { ...@@ -128,7 +187,7 @@ public class DefaultStaticInvokeTest {
String mName = toTest.name(); String mName = toTest.name();
Mod mod = toTest.mod(); Mod mod = toTest.mod();
if (mod != STATIC && typeUnderTest.isInterface()) { if (mod != STATIC && typeUnderTest.isInterface()) {
return; continue;
} }
Exception caught = null; Exception caught = null;
try { try {
...@@ -136,10 +195,12 @@ public class DefaultStaticInvokeTest { ...@@ -136,10 +195,12 @@ public class DefaultStaticInvokeTest {
} catch (Exception e) { } catch (Exception e) {
caught = e; caught = e;
} }
assertTrue(caught != null); assertNotNull(caught);
assertEquals(caught.getClass(), IllegalAccessException.class); assertEquals(caught.getClass(), IllegalAccessException.class);
} }
} }
private static final String[] OBJECT_METHOD_NAMES = { private static final String[] OBJECT_METHOD_NAMES = {
"equals", "equals",
"hashCode", "hashCode",
...@@ -192,15 +253,15 @@ public class DefaultStaticInvokeTest { ...@@ -192,15 +253,15 @@ public class DefaultStaticInvokeTest {
myMethods.put(mName, m); myMethods.put(mName, m);
} }
} }
assertEquals(expectedMethods.length, myMethods.size());
assertEquals(myMethods.size(), expectedMethods.length);
for (MethodDesc toTest : expectedMethods) { for (MethodDesc toTest : expectedMethods) {
String name = toTest.name(); String name = toTest.name();
Method candidate = myMethods.get(name); Method candidate = myMethods.remove(name);
assertNotNull(candidate); assertNotNull(candidate);
myMethods.remove(name);
testThisMethod(toTest, candidate, typeUnderTest, param); testThisMethod(toTest, candidate, typeUnderTest, param);
...@@ -210,6 +271,7 @@ public class DefaultStaticInvokeTest { ...@@ -210,6 +271,7 @@ public class DefaultStaticInvokeTest {
assertTrue(myMethods.isEmpty()); assertTrue(myMethods.isEmpty());
} }
private void testThisMethod(MethodDesc toTest, Method method, private void testThisMethod(MethodDesc toTest, Method method,
Class<?> typeUnderTest, Object param) throws Exception { Class<?> typeUnderTest, Object param) throws Exception {
// Test modifiers, and invoke // Test modifiers, and invoke
...@@ -256,37 +318,52 @@ public class DefaultStaticInvokeTest { ...@@ -256,37 +318,52 @@ public class DefaultStaticInvokeTest {
assertFalse(method.isDefault()); assertFalse(method.isDefault());
break; break;
default: default:
assertFalse(true); //this should never happen fail(); //this should never happen
break; break;
} }
} }
private boolean isMethodDeclared(Class<?> type, String name) {
MethodDesc[] methDescs = type.getAnnotationsByType(MethodDesc.class);
for (MethodDesc desc : methDescs) {
if (desc.declared() == YES && desc.name().equals(name))
return true;
}
return false;
}
private boolean isFoundByGetMethod(Class<?> c, String method, Class<?>... argTypes) {
try {
c.getMethod(method, argTypes);
return true;
} catch (NoSuchMethodException notFound) {
return false;
}
}
private Class<?>[] argTypes(Object param) {
return param == null ? new Class[0] : new Class[] { Object.class };
}
private Object tryInvoke(Method m, Class<?> receiverType, Object param) private Object tryInvoke(Method m, Class<?> receiverType, Object param)
throws Exception { throws Exception {
Object receiver = receiverType == null ? null : receiverType.newInstance(); Object receiver = receiverType == null ? null : receiverType.newInstance();
Object result = null; Object[] args = param == null ? new Object[0] : new Object[] { param };
if (param == null) { return m.invoke(receiver, args);
result = m.invoke(receiver);
} else {
result = m.invoke(receiver, param);
}
return result;
} }
private Method getTestMethod(Class clazz, String methodName, Object param)
throws NoSuchMethodException {
Class[] paramsType = (param != null)
? new Class[]{Object.class}
: new Class[]{};
return clazz.getMethod(methodName, paramsType);
}
private MethodHandle getTestMH(Class clazz, String methodName, Object param) private MethodHandle getTestMH(Class clazz, String methodName, Object param)
throws Exception { throws Exception {
return getTestMH(clazz, methodName, param, false); return getTestMH(clazz, methodName, param, false);
} }
private MethodHandle getTestMH(Class clazz, String methodName, private MethodHandle getTestMH(Class clazz, String methodName,
Object param, boolean isNegativeTest) Object param, boolean isNegativeTest)
throws Exception { throws Exception {
......
...@@ -172,7 +172,7 @@ interface TestIF8<E> { ...@@ -172,7 +172,7 @@ interface TestIF8<E> {
@MethodDesc(name = "defaultMethod", retval = "TestIF8.TestClass8", mod = DEFAULT, declared = NO) @MethodDesc(name = "defaultMethod", retval = "TestIF8.TestClass8", mod = DEFAULT, declared = NO)
class TestClass8<T> implements TestIF8<T> { class TestClass8<T> implements TestIF8<T> {
}; }
@MethodDesc(name = "defaultMethod", retval = "TestIF9.defaultMethod", mod = DEFAULT, declared = YES) @MethodDesc(name = "defaultMethod", retval = "TestIF9.defaultMethod", mod = DEFAULT, declared = YES)
interface TestIF9 extends TestIF1 { interface TestIF9 extends TestIF1 {
...@@ -218,7 +218,6 @@ class TestClass11 implements TestIF11 { ...@@ -218,7 +218,6 @@ class TestClass11 implements TestIF11 {
} }
@MethodDesc(name = "defaultMethod", retval = "TestIF12.defaultMethod", mod = DEFAULT, declared = YES) @MethodDesc(name = "defaultMethod", retval = "TestIF12.defaultMethod", mod = DEFAULT, declared = YES)
@MethodDesc(name = "staticMethod", retval = "TestIF2.staticMethod", mod = STATIC, declared = NO)
interface TestIF12 extends TestIF2 { interface TestIF12 extends TestIF2 {
default String defaultMethod() { default String defaultMethod() {
...@@ -299,7 +298,7 @@ interface TestIF16 { ...@@ -299,7 +298,7 @@ interface TestIF16 {
@MethodDesc(name = "defaultMethod", retval = "TestIF16.defaultMethod", mod = DEFAULT, declared = NO) @MethodDesc(name = "defaultMethod", retval = "TestIF16.defaultMethod", mod = DEFAULT, declared = NO)
class TestClass16 implements TestIF16 { class TestClass16 implements TestIF16 {
}; }
@MethodDesc(name = "defaultMethod", retval = "TestIF17.defaultMethod", mod = DEFAULT, declared = YES) @MethodDesc(name = "defaultMethod", retval = "TestIF17.defaultMethod", mod = DEFAULT, declared = YES)
@MethodDesc(name = "staticMethod", retval = "TestIF17.staticMethod", mod = STATIC, declared = YES) @MethodDesc(name = "staticMethod", retval = "TestIF17.staticMethod", mod = STATIC, declared = YES)
...@@ -318,6 +317,12 @@ interface TestIF17 { ...@@ -318,6 +317,12 @@ interface TestIF17 {
class TestClass17 implements TestIF17 { class TestClass17 implements TestIF17 {
} }
@MethodDesc(name = "defaultMethod", retval = "TestIF17.defaultMethod", mod = DEFAULT, declared = NO)
class TestClass18 extends TestClass17 {
}
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Repeatable(MethodDescs.class) @Repeatable(MethodDescs.class)
@interface MethodDesc { @interface MethodDesc {
...@@ -332,6 +337,41 @@ class TestClass17 implements TestIF17 { ...@@ -332,6 +337,41 @@ class TestClass17 implements TestIF17 {
MethodDesc[] value(); MethodDesc[] value();
} }
//Diamond Case for static method
@MethodDesc(name = "staticMethod", retval = "TestIF2A.staticMethod", mod = STATIC, declared = YES)
interface TestIF2A extends TestIF2 {
static String staticMethod() {
return "TestIF2A.staticMethod";
}
}
@MethodDesc(name = "method", retval = "", mod = ABSTRACT, declared = YES)
interface TestIF2B extends TestIF2 {
String method();
}
@MethodDesc(name = "method", retval = "", mod = ABSTRACT, declared = YES)
interface TestIF18 extends TestIF10, TestIF2A {
String method();
}
@MethodDesc(name = "method", retval = "", mod = ABSTRACT, declared = NO)
@MethodDesc(name = "defaultMethod", retval = "TestIF12.defaultMethod", mod = DEFAULT, declared = NO)
interface TestIF19 extends TestIF12, TestIF2B {
}
@MethodDesc(name = "staticMethod", retval = "TestIF20.staticMethod", mod = STATIC, declared = YES)
@MethodDesc(name = "defaultMethod", retval = "TestIF12.defaultMethod", mod = DEFAULT, declared = NO)
interface TestIF20 extends TestIF12, TestIF2A {
static String staticMethod() {
return "TestIF20.staticMethod";
}
}
@MethodDesc(name = "method", retval = "", mod = ABSTRACT, declared = NO)
interface TestIF21 extends TestIF2A, TestIF2B {
}
public class DefaultStaticTestData { public class DefaultStaticTestData {
/** /**
...@@ -343,22 +383,23 @@ public class DefaultStaticTestData { ...@@ -343,22 +383,23 @@ public class DefaultStaticTestData {
static Object[][] testClasses() { static Object[][] testClasses() {
return new Object[][]{ return new Object[][]{
{"TestClass1", null}, {"TestClass1", null},
//{"TestClass2", null}, @ignore due to JDK-8009411 {"TestClass2", null},
{"TestClass3", null}, {"TestClass3", null},
//{"TestClass4", null}, @ignore due to JDK-8009411 {"TestClass4", null},
//{"TestClass5", null}, @ignore due to JDK-8009411 {"TestClass5", null},
//{"TestClass6", null}, @ignore due to JDK-8009411 {"TestClass6", null},
{"TestClass7", "TestIF7.TestClass7"}, {"TestClass7", "TestIF7.TestClass7"},
{"TestClass8", "TestIF8.TestClass8"}, {"TestClass8", "TestIF8.TestClass8"},
{"TestClass9", null}, {"TestClass9", null},
{"TestClass91", null}, {"TestClass91", null},
//{"TestClass11", null}, @ignore due to JDK-8009411 {"TestClass11", null},
//{"TestClass12", null}, @ignore due to JDK-8009411 {"TestClass12", null},
{"TestClass13", null}, {"TestClass13", null},
{"TestClass14", null}, {"TestClass14", null},
{"TestClass15", null}, {"TestClass15", null},
{"TestClass16", null} {"TestClass16", null},
//{"TestClass17", null} @ignore due to JDK-8009411 {"TestClass17", null},
{"TestClass18", null},
}; };
} }
...@@ -372,6 +413,8 @@ public class DefaultStaticTestData { ...@@ -372,6 +413,8 @@ public class DefaultStaticTestData {
return new Object[][]{ return new Object[][]{
{"TestIF1", null}, {"TestIF1", null},
{"TestIF2", null}, {"TestIF2", null},
{"TestIF2A", null},
{"TestIF2B", null},
{"TestIF3", null}, {"TestIF3", null},
{"TestIF4", null}, {"TestIF4", null},
{"TestIF5", null}, {"TestIF5", null},
...@@ -388,7 +431,12 @@ public class DefaultStaticTestData { ...@@ -388,7 +431,12 @@ public class DefaultStaticTestData {
{"TestIF1D", null}, {"TestIF1D", null},
{"TestIF15", null}, {"TestIF15", null},
{"TestIF16", null}, {"TestIF16", null},
{"TestIF17", null},}; {"TestIF17", null},
{"TestIF18", null},
{"TestIF19", null},
{"TestIF20", null},
{"TestIF21", null},
};
} }
@DataProvider @DataProvider
......
/*
* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/*
* @test
* @bug 8009411
* @summary Test that a static method on an interface doesn't hide a default
* method with the same name and signature in a separate compilation
* scenario.
*/
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.Callable;
import sun.misc.IOUtils;
public class StaticInterfaceMethodInWayOfDefault {
public interface A_v1 {
}
public interface A_v2 {
default void m() {
System.err.println("A.m() called");
}
}
public interface B extends A_v1 {
static void m() {
System.err.println("B.m() called");
}
}
public interface C_v1 extends B {
default void m() {
System.err.println("C.m() called");
}
}
public interface C_v2 extends B {
}
public static class TestTask implements Callable<String> {
@Override
public String call() {
try {
Method m = C_v1.class.getMethod("m", (Class<?>[])null);
return m.getDeclaringClass().getSimpleName();
} catch (NoSuchMethodException e) {
System.err.println("Couldn't find method");
return "ERROR";
}
}
}
public static void main(String[] args) throws Exception {
int errors = 0;
Callable<String> v1Task = new TestTask();
ClassLoader v2Loader = new V2ClassLoader(
StaticInterfaceMethodInWayOfDefault.class.getClassLoader());
Callable<String> v2Task = (Callable<String>) Class.forName(
TestTask.class.getName(),
true,
v2Loader).newInstance();
System.err.println("Running using _v1 classes:");
String res = v1Task.call();
if(!res.equals("C_v1")) {
System.err.println("Got wrong method, expecting C_v1, got: " + res);
errors++;
}
System.err.println("Running using _v2 classes:");
res = v2Task.call();
if(!res.equals("A_v1")) {
System.err.println("Got wrong method, expecting A_v1, got: " + res);
errors++;
}
if (errors != 0)
throw new RuntimeException("Errors found, check log for details");
}
/**
* A ClassLoader implementation that loads alternative implementations of
* classes. If class name ends with "_v1" it locates instead a class with
* name ending with "_v2" and loads that class instead.
*/
static class V2ClassLoader extends ClassLoader {
V2ClassLoader(ClassLoader parent) {
super(parent);
}
@Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
if (name.indexOf('.') < 0) { // root package is our class
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
else { // not our class
return super.loadClass(name, resolve);
}
}
@Override
protected Class<?> findClass(String name)
throws ClassNotFoundException {
// special class name -> replace it with alternative name
if (name.endsWith("_v1")) {
String altName = name.substring(0, name.length() - 3) + "_v2";
String altPath = altName.replace('.', '/').concat(".class");
try (InputStream is = getResourceAsStream(altPath)) {
if (is != null) {
byte[] bytes = IOUtils.readFully(is, -1, true);
// patch class bytes to contain original name
for (int i = 0; i < bytes.length - 2; i++) {
if (bytes[i] == '_' &&
bytes[i + 1] == 'v' &&
bytes[i + 2] == '2') {
bytes[i + 2] = '1';
}
}
return defineClass(name, bytes, 0, bytes.length);
}
else {
throw new ClassNotFoundException(name);
}
}
catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
else { // not special class name -> just load the class
String path = name.replace('.', '/').concat(".class");
try (InputStream is = getResourceAsStream(path)) {
if (is != null) {
byte[] bytes = IOUtils.readFully(is, -1, true);
return defineClass(name, bytes, 0, bytes.length);
}
else {
throw new ClassNotFoundException(name);
}
}
catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册