pax_global_header 0000666 0000000 0000000 00000000064 12366471170 0014521 g ustar 00root root 0000000 0000000 52 comment=320b5312941aa5a7d0fd77bd34944871480aeca8
bridge-method-injector-bridge-method-injector-parent-1.13/ 0000775 0000000 0000000 00000000000 12366471170 0023604 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/.gitignore 0000664 0000000 0000000 00000000031 12366471170 0025566 0 ustar 00root root 0000000 0000000 target
*.iml
*.ipr
*.iws
bridge-method-injector-bridge-method-injector-parent-1.13/annotation/ 0000775 0000000 0000000 00000000000 12366471170 0025756 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/pom.xml 0000664 0000000 0000000 00000001345 12366471170 0027276 0 ustar 00root root 0000000 0000000 4.0.0com.infradna.toolbridge-method-injector-parent1.13../pom.xmlbridge-method-annotationBridge method injection annotationsorg.jenkins-ciannotation-indexer1.4
bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/ 0000775 0000000 0000000 00000000000 12366471170 0026545 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/ 0000775 0000000 0000000 00000000000 12366471170 0027471 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/ 0000775 0000000 0000000 00000000000 12366471170 0030412 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/ 0000775 0000000 0000000 00000000000 12366471170 0031170 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/infradna/ 0000775 0000000 0000000 00000000000 12366471170 0032752 5 ustar 00root root 0000000 0000000 0000775 0000000 0000000 00000000000 12366471170 0033650 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/infradna/tool 0000775 0000000 0000000 00000000000 12366471170 0040341 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/infradna/tool/bridge_method_injector BridgeMethodsAdded.java 0000664 0000000 0000000 00000003253 12366471170 0044651 0 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/infradna/tool/bridge_method_injector /*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.infradna.tool.bridge_method_injector;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.*;
/**
* This annotation is added after the class transformation to indicate that
* the class has already been processed by {@link MethodInjector}.
*
*
* Used for up-to-date check to avoid unnecessary transformations.
*
* @author Kohsuke Kawaguchi
*/
@Retention(CLASS)
@Target(TYPE)
public @interface BridgeMethodsAdded {
}
WithBridgeMethods.java 0000664 0000000 0000000 00000006704 12366471170 0044567 0 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/annotation/src/main/java/com/infradna/tool/bridge_method_injector /*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.infradna.tool.bridge_method_injector;
import org.jvnet.hudson.annotation_indexer.Indexed;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Request that bridge methods of the same name and same arguments be generated
* with each specified type as the return type. This helps you maintain binary compatibility
* as you evolve your classes.
*
*
* For example, if you have the following code:
*
*
* @WithBridgeMethods(Foo.class)
* public FooSubType getFoo() { ... }
*
*
*
* The Maven mojo will insert the following bridge method:
*
*
* public Foo getFoo() {
* return getFoo(); // invokevirtual to getFoo() that returns FooSubType
* }
*
*
*
*
* In some cases, it's necessary to widen the return type of a method, but in a way that legacy
* calls would still return instances of the original type. In this case, add
* {@link #castRequired() castRequired=true} to the annotation. For example, if you have the
* following code:
*
* The Maven mojo will insert the following bridge method:
*
*
* public FooSubType createFoo(Class clazz) {
* return (FooSubType) createFoo(clazz); // invokeVirtual to createFoo that returns Foo
* }
*
* @author Kohsuke Kawaguchi
*/
@Retention(CLASS)
@Target(METHOD)
@Documented
@Indexed
public @interface WithBridgeMethods {
/**
* Specifies the return types. These types must be assignable from the actual
* method return type, or {@link #castRequired()} should be set to true.
*/
Class>[] value();
/**
* Specifies whether the injected bridge methods should perform a cast prior to returning. Only
* set this to true when it is known that calls to the bridge methods will in fact return
* objects assignable to {@linkplain #value() the bridge method return type}, even though
* the declared method return type is not assignable to them.
*
* @since 1.4
*/
boolean castRequired() default false;
}
bridge-method-injector-bridge-method-injector-parent-1.13/injector/ 0000775 0000000 0000000 00000000000 12366471170 0025421 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/pom.xml 0000664 0000000 0000000 00000015375 12366471170 0026751 0 ustar 00root root 0000000 0000000 4.0.0com.infradna.toolbridge-method-injector-parent1.13../pom.xmlbridge-method-injectormaven-pluginbridge-method-injectorEvolve your classes without breaking compatibilitymaven-compiler-plugin1.51.5org.apache.maven.pluginsmaven-release-plugin2.0org.apache.maven.scmmaven-scm-provider-gitexe1.2maven-antrun-pluginruntestcom.suntools1.6system${java.home}/../lib/tools.jarorg.jvnet.wagon-svnwagon-svn1.9scm:git:git@github.com:infradna/bridge-method-injector.gitkohsukeKohsuke KawaguchiMIT Licenserepositoryhttp://www.opensource.org/licenses/mit-license.php${project.groupId}bridge-method-annotation${project.version}org.ow2.asmasm-debug-all4.2junitjunit3.8.1testorg.apache.mavenmaven-plugin-api2.0.1
bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/ 0000775 0000000 0000000 00000000000 12366471170 0026210 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/ 0000775 0000000 0000000 00000000000 12366471170 0027134 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/ 0000775 0000000 0000000 00000000000 12366471170 0030055 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/ 0000775 0000000 0000000 00000000000 12366471170 0030633 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/ 0000775 0000000 0000000 00000000000 12366471170 0032415 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/tool/ 0000775 0000000 0000000 00000000000 12366471170 0033372 5 ustar 00root root 0000000 0000000 0000775 0000000 0000000 00000000000 12366471170 0040004 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/tool/bridge_method_injector ClassAnnotationInjector.java 0000664 0000000 0000000 00000004722 12366471170 0045452 0 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/tool/bridge_method_injector /*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.infradna.tool.bridge_method_injector;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
/**
* Adds class annotations.
*
* @author Kohsuke Kawaguchi
*/
abstract class ClassAnnotationInjector extends ClassVisitor {
ClassAnnotationInjector(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
private boolean emitted = false;
private void maybeEmit() {
if (!emitted) {
emitted = true;
emit();
}
}
protected abstract void emit();
@Override
public void visitInnerClass(String name, String outerName, String innerName, int access) {
maybeEmit();
super.visitInnerClass(name, outerName, innerName, access);
}
@Override
public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
maybeEmit();
return super.visitField(access, name, desc, signature, value);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
maybeEmit();
return super.visitMethod(access, name, desc, signature, exceptions);
}
@Override
public void visitEnd() {
maybeEmit();
super.visitEnd();
}
}
MethodInjector.java 0000664 0000000 0000000 00000030742 12366471170 0043573 0 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/tool/bridge_method_injector /*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.infradna.tool.bridge_method_injector;
import static org.objectweb.asm.ClassWriter.COMPUTE_MAXS;
import static org.objectweb.asm.Opcodes.ACC_ABSTRACT;
import static org.objectweb.asm.Opcodes.ACC_BRIDGE;
import static org.objectweb.asm.Opcodes.ACC_STATIC;
import static org.objectweb.asm.Opcodes.ACC_SYNTHETIC;
import static org.objectweb.asm.Opcodes.ALOAD;
import static org.objectweb.asm.Opcodes.ILOAD;
import static org.objectweb.asm.Opcodes.INVOKESTATIC;
import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL;
import static org.objectweb.asm.Opcodes.IRETURN;
import static org.objectweb.asm.Opcodes.POP;
import static org.objectweb.asm.Opcodes.POP2;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
/**
* Injects bridge methods as per {@link WithBridgeMethods}.
*
* @author Kohsuke Kawaguchi
*/
public class MethodInjector {
public void handleRecursively(File f) throws IOException {
if (f.isDirectory()) {
for (File c : f.listFiles())
handleRecursively(c);
} else if (f.getName().endsWith(".class"))
handle(f);
}
public void handle(File classFile) throws IOException {
FileInputStream in = new FileInputStream(classFile);
byte[] image;
try {
ClassReader cr = new ClassReader(new BufferedInputStream(in));
/*
working around JENKINS-22525 by not passing in 'cr'
Javac as of 6u31 seems to produce duplicate constant pool entries in some cases.
When such a class is transformed through ClassReader -> ClassWriter, it creates
a class file where reference sites to those duplicate entries get reshuffled
(for example, say #15 and #30 are the duplicate entries, and after a transformation
some references that used to use #15 now refers to #30, and vice versa.)
This is because ClassVisitor interfaces passes around strings, and
ClassWriter looks up its constant pool to assign its index.
For whatever reasons, this triggers "incompatible InnerClasses attribute" problem
in IBM J9VM (observed with R26_Java726_SR7_20140409_1418_B195732), while
it is happy with the original class file.
It appears that this rare situation of having duplicate entries in constant pools
and have various references use them in a certain way induces this J9 VM bug.
To work around this problem, we make ASM rebuild the constant pool from scratch,
which totally eliminates any duplicate entries.
*/
ClassWriter cw = new ClassWriter(/*cr,*/COMPUTE_MAXS);
cr.accept(new Transformer(new ClassAnnotationInjectorImpl(cw)),0);
image = cw.toByteArray();
} catch (AlreadyUpToDate _) {
// no need to process this class. it's already up-to-date.
return;
} catch (IOException e) {
throw (IOException)new IOException("Failed to process "+classFile).initCause(e);
} catch (RuntimeException e) {
throw (IOException)new IOException("Failed to process "+classFile).initCause(e);
} finally {
in.close();
}
// write it back
FileOutputStream out = new FileOutputStream(classFile);
out.write(image);
out.close();
}
/**
* Thrown to indicate that there's no need to re-process this class file.
*/
class AlreadyUpToDate extends RuntimeException {
private static final long serialVersionUID = 1L;
}
static class ClassAnnotationInjectorImpl extends ClassAnnotationInjector {
ClassAnnotationInjectorImpl(ClassVisitor cv) {
super(cv);
}
@Override
protected void emit() {
AnnotationVisitor av = cv.visitAnnotation(SYNTHETIC_METHODS_ADDED, false);
av.visitEnd();
}
}
private static class WithBridgeMethodsAnnotationVisitor extends AnnotationVisitor {
protected boolean castRequired = false;
protected List types = new ArrayList();
public WithBridgeMethodsAnnotationVisitor(AnnotationVisitor av) {
super(Opcodes.ASM4, av);
}
@Override
public AnnotationVisitor visitArray(String name) {
return new AnnotationVisitor(Opcodes.ASM4, super.visitArray(name)) {
public void visit(String name, Object value) {
if (value instanceof Type) {
// assume this is a member of the array of classes named "value" in WithBridgeMethods
types.add((Type) value);
}
super.visit(name, value);
}
};
}
@Override
public void visit(String name, Object value) {
if ("castRequired".equals(name) && value instanceof Boolean) {
castRequired = (Boolean) value;
}
super.visit(name, value);
}
}
class Transformer extends ClassVisitor {
private String internalClassName;
/**
* Synthetic methods to be generated.
*/
private final List syntheticMethods = new ArrayList();
class SyntheticMethod {
final int access;
final String name;
final String desc;
final String originalSignature;
final String[] exceptions;
final boolean castRequired;
final Type returnType;
SyntheticMethod(int access, String name, String desc, String originalSignature, String[] exceptions, Type returnType, boolean castRequired) {
this.access = access;
this.name = name;
this.desc = desc;
this.originalSignature = originalSignature;
this.exceptions = exceptions;
this.returnType = returnType;
this.castRequired = castRequired;
}
/**
* Injects a synthetic method and send it to cv.
*/
public void inject(ClassVisitor cv) {
Type[] paramTypes = Type.getArgumentTypes(desc);
Type originalReturnType = Type.getReturnType(desc);
int access = this.access | ACC_SYNTHETIC | ACC_BRIDGE;
String methodDescriptor = Type.getMethodDescriptor(returnType, paramTypes);
MethodVisitor mv = cv.visitMethod(access, name,
methodDescriptor, null/*TODO:is this really correct?*/, exceptions);
if ((access&ACC_ABSTRACT)==0) {
GeneratorAdapter ga = new GeneratorAdapter(mv, access, name, methodDescriptor);
mv.visitCode();
int sz = 0;
boolean isStatic = (access & ACC_STATIC) != 0;
if (!isStatic) {
mv.visitVarInsn(ALOAD,0);
sz++;
}
for (Type p : paramTypes) {
mv.visitVarInsn(p.getOpcode(ILOAD), sz);
sz += p.getSize();
}
mv.visitMethodInsn(
isStatic ? INVOKESTATIC : INVOKEVIRTUAL,internalClassName,name,desc);
if (castRequired) {
ga.unbox(returnType);
} else {
ga.box(originalReturnType);
}
if (returnType.equals(Type.VOID_TYPE) || returnType.getClassName().equals("java.lang.Void")) {
// bridge to void, which means disregard the return value from the original method
switch (originalReturnType.getSize()) {
case 1:
mv.visitInsn(POP);
break;
case 2:
mv.visitInsn(POP2);
break;
default:
throw new AssertionError("Unexpected operand size: "+originalReturnType);
}
}
mv.visitInsn(returnType.getOpcode(IRETURN));
mv.visitMaxs(sz,0);
}
mv.visitEnd();
}
}
Transformer(ClassVisitor cv) {
super(Opcodes.ASM4, cv);
}
@Override
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
this.internalClassName = name;
super.visit(version, access, name, signature, superName, interfaces);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
if (desc.equals(SYNTHETIC_METHODS_ADDED))
throw new AlreadyUpToDate(); // no need to process this class
return super.visitAnnotation(desc, visible);
}
/**
* Look for methods annotated with {@link WithBridgeMethods}.
*/
@Override
public MethodVisitor visitMethod(final int access, final String name, final String mdesc, final String signature, final String[] exceptions) {
MethodVisitor mv = super.visitMethod(access, name, mdesc, signature, exceptions);
return new MethodVisitor(Opcodes.ASM4, mv) {
@Override
public AnnotationVisitor visitAnnotation(String adesc, boolean visible) {
AnnotationVisitor av = super.visitAnnotation(adesc, visible);
if (adesc.equals(WITH_SYNTHETIC_METHODS) && (access & ACC_SYNTHETIC) == 0)
return new WithBridgeMethodsAnnotationVisitor(av) {
@Override
public void visitEnd() {
super.visitEnd();
for (Type type : this.types)
syntheticMethods.add(new SyntheticMethod(
access,name,mdesc,signature,exceptions,type, this.castRequired
));
}
};
return av;
}
};
}
/**
* Inject methods at the end.
*/
@Override
public void visitEnd() {
for (SyntheticMethod m : syntheticMethods)
m.inject(cv);
super.visitEnd();
}
}
public static void main(String[] args) throws IOException {
MethodInjector mi = new MethodInjector();
for (String a : args) {
mi.handleRecursively(new File(a));
}
}
private static final String SYNTHETIC_METHODS_ADDED = Type.getDescriptor(BridgeMethodsAdded.class);
private static final String WITH_SYNTHETIC_METHODS = Type.getDescriptor(WithBridgeMethods.class);
}
ProcessMojo.java 0000664 0000000 0000000 00000005472 12366471170 0043122 0 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/main/java/com/infradna/tool/bridge_method_injector /*
* The MIT License
*
* Copyright (c) 2010, InfraDNA, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.infradna.tool.bridge_method_injector;
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @author Kohsuke Kawaguchi
* @goal process
* @phase process-classes
* @requiresDependencyResolution runtime
*/
public class ProcessMojo extends AbstractMojo {
/**
* The directory containing generated classes.
*
* @parameter expression="${project.build.outputDirectory}"
* @required
*/
private File classesDirectory;
public void execute() throws MojoExecutionException, MojoFailureException {
File index = new File(classesDirectory, "META-INF/annotations/" + WithBridgeMethods.class.getName());
if (!index.exists()) {
getLog().debug("Skipping because there's no "+index);
return;
}
BufferedReader r = null;
try {
r = new BufferedReader(new InputStreamReader(new FileInputStream(index),"UTF-8"));
String line;
while ((line=r.readLine())!=null) {
File classFile = new File(classesDirectory,line.replace('.','/')+".class");
getLog().debug("Processing "+line);
new MethodInjector().handle(classFile);
}
} catch (IOException e) {
throw new MojoExecutionException("Failed to process @WithBridgeMethods",e);
} finally {
try {
if (r!=null) r.close();
} catch (IOException _) {
}
}
}
}
bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/ 0000775 0000000 0000000 00000000000 12366471170 0027167 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/client/ 0000775 0000000 0000000 00000000000 12366471170 0030445 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/client/Main.java 0000664 0000000 0000000 00000002561 12366471170 0032200 0 ustar 00root root 0000000 0000000 public class Main {
public static void main(String[] args) throws Exception {
// invocation of void method. verify that it runs without error
Foo.hello();
Foo.hello2();
Foo.unbox();
Foo.box();
Object o = new Foo().getMessage();
assertEquals(args[0],o);
String n = new Foo().getString();
assertEquals(args[0],n);
Object s = Foo.getStaticMessage();
assertEquals(args[0],s);
Object w = Foo.methodToWiden(String.class);
assertEquals(args[0],w);
// using reflection to ensure that JIT isn't doing inlining
check((Foo)Foo.class.newInstance(),args[0]);
// still a work in progress
// check((Bar)Bar.class.newInstance(),args[0]);
}
private static void assertEquals(Object expected, Object actual) {
System.out.println("We got "+actual+", expecting "+expected);
if (!actual.equals(expected))
System.exit(1);
}
private static void check(IFoo f, String expected) {
Object o = f.getMessage();
assertEquals(expected,o);
String n = f.getString();
assertEquals(expected,n);
}
private static void check(IBar f, String expected) {
Object o = f.narrow();
assertEquals(expected,o);
String n = f.widen();
assertEquals(expected,n);
}
}
bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/synthetics/ 0000775 0000000 0000000 00000000000 12366471170 0031364 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/synthetics/A.java 0000664 0000000 0000000 00000000117 12366471170 0032406 0 ustar 00root root 0000000 0000000 public class A {
public Object getProperty() {
return null;
}
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/synthetics/B.java 0000664 0000000 0000000 00000000341 12366471170 0032406 0 ustar 00root root 0000000 0000000 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
public class B extends A {
@WithBridgeMethods(value=String.class,castRequired=true)
public CharSequence getProperty() {
return null;
}
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v1/ 0000775 0000000 0000000 00000000000 12366471170 0027515 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v1/Bar.java 0000664 0000000 0000000 00000000176 12366471170 0031070 0 ustar 00root root 0000000 0000000 public class Bar implements IBar {
public String widen() { return "foo"; }
public Object narrow() { return "foo"; }
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v1/Foo.java 0000664 0000000 0000000 00000001362 12366471170 0031105 0 ustar 00root root 0000000 0000000 public class Foo implements IFoo {
/**
* Testing narrowing. In v2, we'll narrow this to return the String type. This is type safe.
*/
public Object getMessage() {
return "foo";
}
/**
* Testing widening. In v2, we'll widen this to Object. Potentially type unsafe.
*/
public String getString() {
return "foo";
}
public static Object getStaticMessage() {
return "foo";
}
public static T methodToWiden(Class clazz) {
return clazz.cast("foo");
}
public static void hello() {}
public static void hello2() {}
public static int unbox() {return Integer.MIN_VALUE;}
public static int box() {return Integer.MAX_VALUE;}
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v1/IBar.java 0000664 0000000 0000000 00000000102 12366471170 0031166 0 ustar 00root root 0000000 0000000 public interface IBar {
String widen();
Object narrow();
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v1/IFoo.java 0000664 0000000 0000000 00000000130 12366471170 0031206 0 ustar 00root root 0000000 0000000 public interface IFoo {
public Object getMessage();
public String getString();
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v2/ 0000775 0000000 0000000 00000000000 12366471170 0027516 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v2/Bar.java 0000664 0000000 0000000 00000000176 12366471170 0031071 0 ustar 00root root 0000000 0000000 public class Bar implements IBar {
public Object widen() { return "bar"; }
public String narrow() { return "bar"; }
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v2/Foo.java 0000664 0000000 0000000 00000002116 12366471170 0031104 0 ustar 00root root 0000000 0000000 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
public class Foo implements IFoo {
@WithBridgeMethods(Object.class)
public String getMessage() {
return "bar";
}
/**
* Widening String to Object.
*/
@WithBridgeMethods(value=String.class,castRequired=true)
public Object getString() {
return "bar";
}
@WithBridgeMethods(Object.class)
public static String getStaticMessage() {
return "bar";
}
@WithBridgeMethods(value=String.class, castRequired=true)
public static T methodToWiden(Class clazz) {
return clazz.cast("bar");
}
@WithBridgeMethods(void.class)
public static boolean hello() {
return true;
}
@WithBridgeMethods(void.class)
public static String hello2() {
return "hello2";
}
@WithBridgeMethods(value=int.class, castRequired=true)
public static Integer unbox() {
return Integer.MIN_VALUE;
}
@WithBridgeMethods(value=Integer.class)
public static int box() {
return Integer.MAX_VALUE;
}
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v2/IBar.java 0000664 0000000 0000000 00000000350 12366471170 0031174 0 ustar 00root root 0000000 0000000 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
public interface IBar {
@WithBridgeMethods(value=String.class,castRequired=true)
Object widen();
@WithBridgeMethods(Object.class)
String narrow();
} bridge-method-injector-bridge-method-injector-parent-1.13/injector/src/test/v2/IFoo.java 0000664 0000000 0000000 00000000377 12366471170 0031224 0 ustar 00root root 0000000 0000000 import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
public interface IFoo {
@WithBridgeMethods(Object.class)
public String getMessage();
@WithBridgeMethods(value=String.class,castRequired=true)
public Object getString();
} bridge-method-injector-bridge-method-injector-parent-1.13/pom.xml 0000664 0000000 0000000 00000007256 12366471170 0025133 0 ustar 00root root 0000000 0000000 4.0.0com.infradna.toolbridge-method-injector-parent1.13pomBridge Method Injection Parent POMEvolve your classes without breaking compatibilityhttp://bridge-method-injector.infradna.com/annotationinjectormaven-compiler-plugin1.51.5org.apache.maven.pluginsmaven-release-plugin2.0releaseorg.apache.maven.scmmaven-scm-provider-gitexe1.2org.sonatype.pluginsnexus-staging-maven-plugin1.4.4truesonatype-nexus-staginghttps://oss.sonatype.org/org.jvnet.wagon-svnwagon-svn1.9scm:git:git@github.com:infradna/bridge-method-injector.githttps://github.com/infradna/bridge-method-injectorkohsukeKohsuke KawaguchiMIT Licenserepositoryhttp://www.opensource.org/licenses/mit-license.phpsonatype-nexus-stagingNexus Release Repositoryhttps://oss.sonatype.org/service/local/staging/deploy/maven2/sonatype-nexus-snapshotsSonatype Nexus Snapshotshttps://oss.sonatype.org/content/repositories/snapshots/org.apache.maven.pluginsmaven-javadoc-plugintruereleasemaven-gpg-pluginsign-artifactsverifysign
bridge-method-injector-bridge-method-injector-parent-1.13/src/ 0000775 0000000 0000000 00000000000 12366471170 0024373 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/src/site/ 0000775 0000000 0000000 00000000000 12366471170 0025337 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/src/site/apt/ 0000775 0000000 0000000 00000000000 12366471170 0026123 5 ustar 00root root 0000000 0000000 bridge-method-injector-bridge-method-injector-parent-1.13/src/site/apt/index.apt.vm 0000664 0000000 0000000 00000011357 12366471170 0030370 0 ustar 00root root 0000000 0000000 What's this?
When you are writing a library, there are various restrictions about the kind of changes you can make, in order to maintain binary compatibility.
One such restriction is an inability to restrict the return type. Say in v1 of your library you had the following code:
------------------------
public Foo getFoo() {
return new Foo();
}
------------------------
In v2, say if you introduce a subtype of <<>> called <<>>, and you want to change the getFoo method to return <<>>.
------------------------
public FooSubType getFoo() {
return new FooSubType();
}
------------------------
But if you do this, you break the binary compatibility. The clients need to be recompiled to be able to work with the new signature. This is where this bridge method injector can help. By adding an annotation like the following:
------------------------
@WithBridgeMethods(Foo.class)
public FooSubType getFoo() {
return new FooSubType();
}
------------------------
... and running the bytecode post processor, your class file will get the additional "bridge methods." In pseudo-code, it'll look like this:
------------------------
// your original definition
@WithBridgeMethods(Foo.class)
public FooSubType getFoo() {
return new FooSubType();
}
// added bridge method
public Foo getFoo() {
invokevirtual this.getFoo()LFooSubType;
areturn
}
------------------------
Such code isn't allowed in Java source files, but class files allow that. With this addition, existing clients will continue to function.
In this way, you can evolve your classes more easily without breaking backward compatibility.
Widening the return type
In some cases, it's convenient to widen the return type of a method. As this is potentially a type-unsafe change
(as the callee can return a type that's not assignable to what the caller expects), so
you as a programmer explicitly need to tell us that you know what you are doing by adding
<<>> to the annotation. For example, suppose that v1 had a method:
------------------------
public createFoo(Class clazz) {
return clazz.newInstance();
}
------------------------
and in v2 you wanted to widen this method to. Note that you can prove that this is still type-safe, while
your compile cannot:
------------------------
public createFoo(Class clazz) {
return clazz.newInstance();
}
------------------------
The annotation to provide backwards compatibility would be:
------------------------
@WithBridgeMethods(value=FooSubType.class, castRequired=true)
public createFoo(Class clazz) {
return clazz.newInstance();
}
------------------------
Running the bytecode post processor, the resulting class file will look like the following pseudo-code:
------------------------
// your original definition
@WithBridgeMethods(value=FooSubType.class, castRequired=true)
public createFoo(Class clazz) {
return clazz.newInstance();
}
// added bridge method
public FooSubType createFoo(Class clazz) {
invokevirtual this.createFoo(java/lang/Class)LFoo
checkcast FooSubType
areturn
}
------------------------
Bridge Methods and Interfaces
You can use <<<@WithBridgeMethods>>> with interfaces, too. However, making this work correctly is tricky,
as you have to ensure that bridge methods are implemented on all the classes that implement the interface,
for example by adding <<<@WithBridgeMethods>>> on every implementation of the method in question,
or by introducing a base class that provides a bridge method.
Integration into Your Build
Add the following dependency in your POM. (This dependency is not needed at runtime, but it is necessary
for compilation of source code that transitively depend on this, so it is the simplest to just treat
this like a regular library dependency)
------------------------
com.infradna.toolbridge-method-annotation1.12
------------------------
Then put the following fragment in your build to have the byte-code post processor kick in to inject the necessary bridge methods.
------------------------
com.infradna.toolbridge-method-injector1.4process
------------------------
The artifacts are deployed to {{{http://maven.dyndns.org/2/com/infradna/tool/bridge-method-injector/}java.net Maven2 repository}}.
bridge-method-injector-bridge-method-injector-parent-1.13/src/site/site.xml 0000664 0000000 0000000 00000001556 12366471170 0027034 0 ustar 00root root 0000000 0000000
Bridge Method Injectorhttp://bridge-method-injector.infradna.com/org.kohsukemaven-skin1.1