git-svn-id: http://anonsvn.jboss.org/repos/javassist/trunk@701 30ef5769-5b8d-40dd-aea6-55b5d6557bb3tags/log
<h2>Changes</h2> | <h2>Changes</h2> | ||||
<p>-version 3.18 | |||||
<ul> | |||||
JIRA JASSIST-183, 189, 162. | |||||
</ul> | |||||
<p>-version 3.17.1 on December 3, 2012 | <p>-version 3.17.1 on December 3, 2012 | ||||
<ul> | <ul> | ||||
<li>JIRA JASSIST-177, 178, 182 | <li>JIRA JASSIST-177, 178, 182 |
</target> | </target> | ||||
<target name="jar" depends="compile14"> | <target name="jar" depends="compile14"> | ||||
<jar jarfile="${target.jar}" manifest="${src.dir}/META-INF/MANIFEST.MF"> | |||||
<jar jarfile="${target.jar}" update="true" manifest="${src.dir}/META-INF/MANIFEST.MF"> | |||||
<fileset dir="${build.classes.dir}"> | <fileset dir="${build.classes.dir}"> | ||||
<include name="**/*.class"/> | <include name="**/*.class"/> | ||||
</fileset> | </fileset> |
if (notBridgeMethod(minfo)) | if (notBridgeMethod(minfo)) | ||||
return true; | return true; | ||||
else { | else { | ||||
// if the bridge method with the same signature | |||||
// already exists, replace it. | |||||
it.remove(); | it.remove(); | ||||
return false; | return false; | ||||
} | } | ||||
} | } | ||||
else | else | ||||
return notBridgeMethod(minfo) && notBridgeMethod(newMethod); | |||||
return false; | |||||
// return notBridgeMethod(minfo) && notBridgeMethod(newMethod); | |||||
} | } | ||||
/* For a bridge method, see Sec. 15.12.4.5 of JLS 3rd Ed. | /* For a bridge method, see Sec. 15.12.4.5 of JLS 3rd Ed. |
{ | { | ||||
checkClassAndSuperName(); | checkClassAndSuperName(); | ||||
hasGetHandler = false; // getMethods() may set this to true. | |||||
HashMap allMethods = getMethods(superClass, interfaces); | HashMap allMethods = getMethods(superClass, interfaces); | ||||
signatureMethods = new ArrayList(allMethods.entrySet()); | signatureMethods = new ArrayList(allMethods.entrySet()); | ||||
hasGetHandler = allMethods.get(HANDLER_GETTER_KEY) != null; | |||||
Collections.sort(signatureMethods, sorter); | Collections.sort(signatureMethods, sorter); | ||||
} | } | ||||
Map.Entry e = (Map.Entry)it.next(); | Map.Entry e = (Map.Entry)it.next(); | ||||
String key = (String)e.getKey(); | String key = (String)e.getKey(); | ||||
Method meth = (Method)e.getValue(); | Method meth = (Method)e.getValue(); | ||||
int mod = meth.getModifiers(); | |||||
if (testBit(signature, index)) { | |||||
override(className, meth, prefix, index, | |||||
keyToDesc(key, meth), cf, cp, forwarders); | |||||
} | |||||
if (ClassFile.MAJOR_VERSION < ClassFile.JAVA_5 || !isBridge(meth)) | |||||
if (testBit(signature, index)) { | |||||
override(className, meth, prefix, index, | |||||
keyToDesc(key, meth), cf, cp, forwarders); | |||||
} | |||||
index++; | index++; | ||||
} | } | ||||
return index; | return index; | ||||
} | } | ||||
private static boolean isBridge(Method m) { | |||||
return m.isBridge(); | |||||
} | |||||
private void override(String thisClassname, Method meth, String prefix, | private void override(String thisClassname, Method meth, String prefix, | ||||
int index, String desc, ClassFile cf, ConstPool cp, ArrayList forwarders) | int index, String desc, ClassFile cf, ConstPool cp, ArrayList forwarders) | ||||
throws CannotCompileException | throws CannotCompileException | ||||
return name.substring(0, i); | return name.substring(0, i); | ||||
} | } | ||||
private static HashMap getMethods(Class superClass, Class[] interfaceTypes) { | |||||
/* getMethods() may set hasGetHandler to true. | |||||
*/ | |||||
private HashMap getMethods(Class superClass, Class[] interfaceTypes) { | |||||
HashMap hash = new HashMap(); | HashMap hash = new HashMap(); | ||||
HashSet set = new HashSet(); | HashSet set = new HashSet(); | ||||
for (int i = 0; i < interfaceTypes.length; i++) | for (int i = 0; i < interfaceTypes.length; i++) | ||||
return hash; | return hash; | ||||
} | } | ||||
private static void getMethods(HashMap hash, Class clazz, Set visitedClasses) { | |||||
private void getMethods(HashMap hash, Class clazz, Set visitedClasses) { | |||||
// This both speeds up scanning by avoiding duplicate interfaces and is needed to | // This both speeds up scanning by avoiding duplicate interfaces and is needed to | ||||
// ensure that superinterfaces are always scanned before subinterfaces. | // ensure that superinterfaces are always scanned before subinterfaces. | ||||
if (!visitedClasses.add(clazz)) | if (!visitedClasses.add(clazz)) | ||||
if (parent != null) | if (parent != null) | ||||
getMethods(hash, parent, visitedClasses); | getMethods(hash, parent, visitedClasses); | ||||
/* Java 5 or later allows covariant return types. | |||||
* It also allows contra-variant parameter types | |||||
* if a super class is a generics with concrete type arguments | |||||
* such as Foo<String>. So the method-overriding rule is complex. | |||||
*/ | |||||
Method[] methods = SecurityActions.getDeclaredMethods(clazz); | Method[] methods = SecurityActions.getDeclaredMethods(clazz); | ||||
for (int i = 0; i < methods.length; i++) | for (int i = 0; i < methods.length; i++) | ||||
if (!Modifier.isPrivate(methods[i].getModifiers())) { | if (!Modifier.isPrivate(methods[i].getModifiers())) { | ||||
Method m = methods[i]; | Method m = methods[i]; | ||||
// JIRA JASSIST-127 (covariant return types). | |||||
String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m.getParameterTypes(), null); | |||||
String key = m.getName() + ':' + RuntimeSupport.makeDescriptor(m); // see keyToDesc(). | |||||
if (key.startsWith(HANDLER_GETTER_KEY)) | |||||
hasGetHandler = true; | |||||
// JIRA JASSIST-85 | // JIRA JASSIST-85 | ||||
// put the method to the cache, retrieve previous definition (if any) | // put the method to the cache, retrieve previous definition (if any) | ||||
Method oldMethod = (Method)hash.put(key, methods[i]); | Method oldMethod = (Method)hash.put(key, methods[i]); | ||||
} | } | ||||
} | } | ||||
private static final String HANDLER_GETTER_KEY = HANDLER_GETTER + ":()"; | |||||
private static final String HANDLER_GETTER_KEY | |||||
= HANDLER_GETTER + ":()"; | |||||
private static String keyToDesc(String key, Method m) { | private static String keyToDesc(String key, Method m) { | ||||
String params = key.substring(key.indexOf(':') + 1); | |||||
return RuntimeSupport.makeDescriptor(params, m.getReturnType()); | |||||
return key.substring(key.indexOf(':') + 1); | |||||
} | } | ||||
private static MethodInfo makeConstructor(String thisClassName, Constructor cons, | private static MethodInfo makeConstructor(String thisClassName, Constructor cons, |
public void testAddMethod() throws Exception { | public void testAddMethod() throws Exception { | ||||
CtClass cc = sloader.get("test2.AddMethod"); | CtClass cc = sloader.get("test2.AddMethod"); | ||||
CtMethod m = CtNewMethod.make( | |||||
"public void f() { return 1; }", cc); | |||||
CtMethod m = CtNewMethod.make( | |||||
"public int f() { return 1; }", cc); | |||||
try { | try { | ||||
cc.addMethod(m); | cc.addMethod(m); | ||||
fail(); | fail(); | ||||
} | } | ||||
catch (CannotCompileException e) {} | catch (CannotCompileException e) {} | ||||
CtMethod m2 = CtNewMethod.make( | |||||
CtMethod m2 = CtNewMethod.make( | |||||
"public void f(int i, int j) { return 1; }", cc); | "public void f(int i, int j) { return 1; }", cc); | ||||
cc.addMethod(m2); | cc.addMethod(m2); | ||||
try { | try { |
// method. | // method. | ||||
} | } | ||||
}; | }; | ||||
Foo foo = (Foo) c.newInstance(); | |||||
Foo foo = (Foo)c.newInstance(); | |||||
try { | try { | ||||
((ProxyObject)foo).setHandler(mi); | ((ProxyObject)foo).setHandler(mi); | ||||
fail("foo is a ProxyObject!"); | fail("foo is a ProxyObject!"); | ||||
} catch (ClassCastException e) {} | } catch (ClassCastException e) {} | ||||
((Proxy)foo).setHandler(mi); | ((Proxy)foo).setHandler(mi); | ||||
assertEquals("I'm doing something!", foo.doSomething()); | assertEquals("I'm doing something!", foo.doSomething()); | ||||
assertEquals("This is a secret handler!", foo.getHandler()); | |||||
} | } | ||||
public void testGetHandler2() throws Exception { | public void testGetHandler2() throws Exception { | ||||
} catch (ClassCastException e) {} | } catch (ClassCastException e) {} | ||||
((Proxy)foo).setHandler(mi); | ((Proxy)foo).setHandler(mi); | ||||
assertEquals("do something!", foo.doSomething()); | assertEquals("do something!", foo.doSomething()); | ||||
assertEquals("return a string!", foo.getHandler()); | |||||
} | } | ||||
} | } |
package test1; | package test1; | ||||
class SuperDelegator { | class SuperDelegator { | ||||
public int f(int p) { return p + 1; } | |||||
public int f(int p) { return p + 1; } | |||||
public static int g(int p) { return p + 1; } | public static int g(int p) { return p + 1; } | ||||
} | } | ||||
public Object invoke(Object self, Method m, Method proceed, | public Object invoke(Object self, Method m, Method proceed, | ||||
Object[] args) throws Exception { | Object[] args) throws Exception { | ||||
System.out.println("intercept: " + m + ", proceed: " + proceed | |||||
+ ", modifier: " | |||||
+ Modifier.toString(proceed.getModifiers())); | |||||
System.out.println("intercept: " + m + ", proceed: " + proceed); | |||||
System.out.println(" modifier: " | |||||
+ Modifier.toString(proceed.getModifiers())); | |||||
counter++; | counter++; | ||||
return proceed.invoke(self, args); | return proceed.invoke(self, args); | ||||
} | } | ||||
public Object writeReplace(int i) { return new Integer(i); } | public Object writeReplace(int i) { return new Integer(i); } | ||||
} | } | ||||
public static void testJIRA189() throws Exception { | |||||
Class persistentClass = Target189.PublishedArticle.class; | |||||
ProxyFactory factory = new ProxyFactory(); | |||||
factory.writeDirectory = "."; | |||||
factory.setUseCache(false); | |||||
factory.setSuperclass(persistentClass); | |||||
factory.setInterfaces(new Class[] { Target189.TestProxy.class }); | |||||
Class cl = factory.createClass(); | |||||
Target189.TestProxy proxy = (Target189.TestProxy)cl.newInstance(); | |||||
Target189.TestMethodHandler methodHandler = new Target189.TestMethodHandler(); | |||||
((ProxyObject)proxy).setHandler(methodHandler); | |||||
((Target189.Article)proxy).getIssue(); | |||||
assertTrue(methodHandler.wasInvokedOnce()); | |||||
methodHandler.reset(); | |||||
Target189.PublishedArticle article = (Target189.PublishedArticle)proxy; | |||||
article.getIssue(); | |||||
assertTrue(methodHandler.wasInvokedOnce()); | |||||
} | |||||
public void testJIRA127() throws Exception { | |||||
ProxyFactory proxyFactory = new ProxyFactory(); | |||||
proxyFactory.writeDirectory = "."; | |||||
proxyFactory.setInterfaces(new Class[]{ Target127.Sub.class }); | |||||
Target127.Sub proxy = (Target127.Sub)proxyFactory.create(new Class[0], new Object[0], new MethodHandler() { | |||||
public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable { | |||||
return null; | |||||
} | |||||
}); | |||||
((Target127.Super)proxy).item(); // proxyFactory must generate a bridge method. | |||||
((Target127.Sub)proxy).item(); | |||||
} | |||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
// javassist.bytecode.ClassFile.MAJOR_VERSION = javassist.bytecode.ClassFile.JAVA_6; | // javassist.bytecode.ClassFile.MAJOR_VERSION = javassist.bytecode.ClassFile.JAVA_6; | ||||
junit.textui.TestRunner.run(ProxyTester.class); | junit.textui.TestRunner.run(ProxyTester.class); |
package testproxy; | |||||
public class Target127 { | |||||
public interface Item { } | |||||
public interface CovariantItem extends Item { } | |||||
public interface Super { | |||||
Item item(); | |||||
} | |||||
public interface Sub extends Super { | |||||
CovariantItem item(); | |||||
} | |||||
public static class RealSub implements Sub { | |||||
public CovariantItem item() { | |||||
return null; | |||||
} | |||||
} | |||||
} |
package testproxy; | |||||
import javassist.util.proxy.MethodHandler; | |||||
import java.lang.reflect.Method; | |||||
public class Target189 { | |||||
public interface TestProxy { | |||||
} | |||||
public static class TestMethodHandler implements MethodHandler { | |||||
int invoked = 0; | |||||
public Object invoke(Object self, Method thisMethod, Method proceed, | |||||
Object[] args) throws Throwable { | |||||
invoked++; | |||||
return proceed.invoke(self, args); | |||||
} | |||||
public boolean wasInvokedOnce() { | |||||
return invoked == 1; | |||||
} | |||||
public void reset() { | |||||
invoked = 0; | |||||
} | |||||
} | |||||
public static class Issue { | |||||
private Integer id; | |||||
public Integer getId() { | |||||
return id; | |||||
} | |||||
public void setId(Integer id) { | |||||
this.id = id; | |||||
} | |||||
} | |||||
public static class PublishedIssue extends Issue { | |||||
} | |||||
public static abstract class Article { | |||||
private Integer id; | |||||
public Integer getId() { | |||||
return id; | |||||
} | |||||
public void setId(Integer id) { | |||||
this.id = id; | |||||
} | |||||
public abstract Issue getIssue(); | |||||
} | |||||
public static class PublishedArticle extends Article { | |||||
private PublishedIssue issue; | |||||
@Override | |||||
public PublishedIssue getIssue() { | |||||
return issue; | |||||
} | |||||
public void setIssue(PublishedIssue issue) { | |||||
this.issue = issue; | |||||
} | |||||
} | |||||
} |