Fix FELIX-3461 - Issue with collected metadata and manipulation metadata on re-manipulation
* Fix the constructor argument shift by not moving the annotations
* Fix the method name when it is a manipulated method
* Cleanup the manipulation metadata
git-svn-id: https://svn.apache.org/repos/asf/felix/trunk@1327153 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/ipojo/manipulator/manipulator/pom.xml b/ipojo/manipulator/manipulator/pom.xml
index 99cb26d..a178fbe 100644
--- a/ipojo/manipulator/manipulator/pom.xml
+++ b/ipojo/manipulator/manipulator/pom.xml
@@ -55,12 +55,18 @@
<artifactId>org.apache.felix.ipojo.annotations</artifactId>
<version>1.8.0</version>
</dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-all</artifactId>
- <version>1.8.5</version>
- <scope>test</scope>
- </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <version>1.8.5</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <version>4.2.0</version>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
<plugins>
@@ -140,9 +146,10 @@
<directory>.</directory>
<targetPath>META-INF</targetPath>
<includes>
- <include>LICENSE*</include>
- <include>NOTICE*</include>
- <include>DEPENDENCIES*</include>
+ <include>LICENSE</include>
+ <include>LICENSE.asm</include>
+ <include>NOTICE</include>
+ <include>DEPENDENCIES</include>
</includes>
</resource>
</resources>
diff --git a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ClassChecker.java b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ClassChecker.java
index fe073f3..8e23bf1 100644
--- a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ClassChecker.java
+++ b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ClassChecker.java
@@ -102,6 +102,10 @@
return null;
}
+ if (isManipulatedField(name)) {
+ return null;
+ }
+
Type type = Type.getType(desc);
if (type.getSort() == Type.ARRAY) {
if (type.getInternalName().startsWith("L")) {
@@ -120,6 +124,12 @@
return null;
}
+ private boolean isManipulatedField(String name) {
+ return ((MethodCreator.IM_FIELD.equals(name))
+ || (name.startsWith(MethodCreator.FIELD_FLAG_PREFIX))
+ || (name.startsWith(MethodCreator.METHOD_FLAG_PREFIX)));
+ }
+
/**
* Add the inner class to the list of inner class to manipulate.
* The method checks that the inner class is really owned by the implementation class.
@@ -189,17 +199,16 @@
if (!name.equals("<clinit>")) {
if (name.equals("<init>")) {
- final MethodDescriptor md = new MethodDescriptor("$init", desc);
- m_methods.add(md);
- if (m_supportAnnotation) {
- return new AnnotationCollector(md);
+ if (!isGeneratedConstructor(name, desc)) {
+ final MethodDescriptor md = new MethodDescriptor("$init", desc);
+ m_methods.add(md);
+ if (m_supportAnnotation) {
+ return new AnnotationCollector(md);
+ }
}
} else {
// no constructors.
- if (!(name.startsWith("_get") || // Avoid getter method
- name.startsWith("_set") || // Avoid setter method
- name.equals("_setComponentManager") || // Avoid the set method
- name.equals("getComponentInstance"))) { // Avoid the getComponentInstance method
+ if (!isGeneratedMethod(name, desc)) {
final MethodDescriptor md = new MethodDescriptor(name, desc);
m_methods.add(md);
if (m_supportAnnotation) {
@@ -213,6 +222,56 @@
return null;
}
+ private boolean isGeneratedConstructor(String name, String desc) {
+ return ("<init>".equals(name) && isFirstArgumentInstanceManager(desc));
+ }
+
+ private boolean isFirstArgumentInstanceManager(String desc) {
+ Type[] types = Type.getArgumentTypes(desc);
+ if (types != null && (types.length >= 1)) {
+ return Type.getType("Lorg/apache/felix/ipojo/InstanceManager;")
+ .equals(types[0]);
+ }
+ return false;
+ }
+
+ private boolean isGeneratedMethod(String name, String desc) {
+ return isGetterMethod(name, desc)
+ || isSetterMethod(name, desc)
+ || isSetInstanceManagerMethod(name)
+ || isGetComponentInstanceMethod(name, desc)
+ || isManipulatedMethod(name);
+ }
+
+ private boolean isGetterMethod(String name, String desc) {
+ // TYPE __getXXX()
+ Type[] arguments = Type.getArgumentTypes(desc);
+ return (name.startsWith("__get")
+ && (arguments.length == 0)
+ && !Type.VOID_TYPE.equals(Type.getReturnType(desc)));
+ }
+
+ private boolean isSetterMethod(String name, String desc) {
+ // void __setXXX(TYPE)
+ Type[] arguments = Type.getArgumentTypes(desc);
+ return (name.startsWith("__set")
+ && (arguments.length == 1)
+ && Type.VOID_TYPE.equals(Type.getReturnType(desc)));
+ }
+
+ private boolean isSetInstanceManagerMethod(String name) {
+ return name.startsWith("_setInstanceManager");
+ }
+
+ private boolean isGetComponentInstanceMethod(String name, String desc) {
+ return (name.startsWith("getComponentInstance")
+ && Type.getType("Lorg/apache/felix/ipojo/ComponentInstance;").equals(Type.getReturnType(desc)));
+ }
+
+ private boolean isManipulatedMethod(String name) {
+ return (name.startsWith(MethodCreator.PREFIX));
+ }
+
/**
* Get collected interfaces.
* @return the interfaces implemented by the component class.
@@ -293,6 +352,18 @@
m_method.addParameterAnnotation(id, ann);
return ann;
}
+
+ /*
+ * It is harmless to keep injected parameter annotations on original constructor
+ * for correct property resolution in case of re-manipulation
+ */
+ if(m_method.getName().equals("$init"))
+ {
+ AnnotationDescriptor ann = new AnnotationDescriptor(name, visible);
+ m_method.addParameterAnnotation(id, ann);
+ return ann;
+ }
+
return null;
}
diff --git a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ConstructorCodeAdapter.java b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ConstructorCodeAdapter.java
index 144219c..4cc76ee 100644
--- a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ConstructorCodeAdapter.java
+++ b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/ConstructorCodeAdapter.java
@@ -20,10 +20,14 @@
import java.util.Set;
+import org.apache.felix.ipojo.manipulation.ClassChecker.AnnotationDescriptor;
+import org.apache.felix.ipojo.manipulation.annotations.CustomAnnotationVisitor;
+import org.apache.felix.ipojo.manipulation.annotations.MetadataCollector;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
import org.objectweb.asm.commons.GeneratorAdapter;
@@ -32,6 +36,7 @@
* This class adds an instance manager argument (so switch variable index).
* Moreover, it adapts field accesses to delegate accesses to the instance
* manager if needed.
+ *
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class ConstructorCodeAdapter extends GeneratorAdapter implements Opcodes {
@@ -61,12 +66,13 @@
/**
* PropertyCodeAdapter constructor.
* A new FiledCodeAdapter should be create for each method visit.
- * @param mv the MethodVisitor
- * @param owner the name of the class
+ *
+ * @param mv the MethodVisitor
+ * @param owner the name of the class
* @param fields the list of contained fields
* @param access the constructor access
- * @param desc the constructor descriptor
- * @param name the name
+ * @param desc the constructor descriptor
+ * @param name the name
*/
public ConstructorCodeAdapter(final MethodVisitor mv, final String owner, Set<String> fields, int access, String name, String desc, String superClass) {
super(mv, access, name, desc);
@@ -81,10 +87,11 @@
* If the annotation is visible, the annotation is removed. In fact
* the annotation was already moved to the method replacing this one.
* If the annotation is not visible, this annotation is kept on this method.
- * @param name the name of the annotation
+ *
+ * @param name the name of the annotation
* @param visible the annotation visibility
* @return the <code>null</code> if the annotation is visible, otherwise returns
- * {@link GeneratorAdapter#visitAnnotation(String, boolean)}
+ * {@link GeneratorAdapter#visitAnnotation(String, boolean)}
* @see org.objectweb.asm.MethodAdapter#visitAnnotation(java.lang.String, boolean)
*/
public AnnotationVisitor visitAnnotation(String name, boolean visible) {
@@ -96,6 +103,37 @@
}
}
+ /**
+ * Visits a parameter annotation.
+ * Parameter annotations are moved to replacing constructor except
+ * they are injection annotations(-@Property and -@Requires).
+ * Because injection annotations shouldn't be copied to generated one
+ * in case of re-manipulation, since this is caused to wrong type resolution
+ * of injected parameters.
+ *
+ * @param parameter parameter index
+ * @param desc annotation description(annotation name)
+ * @param visible is parameter annotation visible
+ * @return @AnnotationVisitor
+ */
+ public AnnotationVisitor visitParameterAnnotation(
+ final int parameter,
+ final String desc,
+ final boolean visible) {
+
+ /*
+ * Generated constructor shouldn't inherit injection annotations
+ */
+ if (desc.equals("Lorg/apache/felix/ipojo/annotations/Property;")
+ || desc.equals("Lorg/apache/felix/ipojo/annotations/Requires;")
+ || CustomAnnotationVisitor.isCustomAnnotation(desc)) {
+ return null;
+ } else {
+ return super.visitParameterAnnotation(parameter, desc, visible);
+ }
+
+ }
+
/**
* Adapts field accesses.
@@ -104,11 +142,12 @@
* <li><code>GETFIELD</code> are changed to a <code>__getX</code> invocation.</li>
* <li><code>SETFIELD</code> are changed to a <code>__setX</code> invocation.</li>
* </ul>
- * @see org.objectweb.asm.MethodVisitor#visitFieldInsn(int, String, String, String)
+ *
* @param opcode the visited operation code
- * @param owner the owner of the field
- * @param name the name of the field
- * @param desc the descriptor of the field
+ * @param owner the owner of the field
+ * @param name the name of the field
+ * @param desc the descriptor of the field
+ * @see org.objectweb.asm.MethodVisitor#visitFieldInsn(int, String, String, String)
*/
public void visitFieldInsn(
final int opcode,
@@ -120,12 +159,11 @@
String gDesc = "()" + desc;
mv.visitMethodInsn(INVOKEVIRTUAL, owner, "__get" + name, gDesc);
return;
- } else
- if (opcode == PUTFIELD) {
- String sDesc = "(" + desc + ")V";
- mv.visitMethodInsn(INVOKEVIRTUAL, owner, "__set" + name, sDesc);
- return;
- }
+ } else if (opcode == PUTFIELD) {
+ String sDesc = "(" + desc + ")V";
+ mv.visitMethodInsn(INVOKEVIRTUAL, owner, "__set" + name, sDesc);
+ return;
+ }
}
super.visitFieldInsn(opcode, owner, name, desc);
}
@@ -134,23 +172,24 @@
* Visits a method invocation instruction.
* After the super constructor invocation, insert the _setComponentManager invocation.
* Otherwise, the method invocation doesn't change
+ *
* @param opcode the opcode
- * @param owner the class owning the invoked method
- * @param name the method name
- * @param desc the method descriptor
+ * @param owner the class owning the invoked method
+ * @param name the method name
+ * @param desc the method descriptor
* @see org.objectweb.asm.MethodAdapter#visitMethodInsn(int, java.lang.String, java.lang.String, java.lang.String)
*/
public void visitMethodInsn(int opcode, String owner, String name, String desc) {
// A method call is detected, check if it is the super call :
// the first init is not necessary the super call, so check that it is really the super class.
- if (!m_superDetected && name.equals("<init>") && owner.equals(m_superClass)) {
+ if (!m_superDetected && name.equals("<init>") && owner.equals(m_superClass)) {
m_superDetected = true;
// The first invocation is the super call
// 1) Visit the super constructor :
//mv.visitVarInsn(ALOAD, 0); The ALOAD 0 was already visited. This previous visit allows
- // Super constructor parameters.
+ // Super constructor parameters.
mv.visitMethodInsn(opcode, owner, name, desc); // Super constructor invocation
// 2) Load the object and the component manager argument
@@ -171,8 +210,9 @@
* it is not <code>this</code> (i.e. 0). This increment
* is due to the instance manager parameter added in the method
* signature.
+ *
* @param opcode the opcode
- * @param var the variable index
+ * @param var the variable index
* @see org.objectweb.asm.MethodAdapter#visitVarInsn(int, int)
*/
public void visitVarInsn(int opcode, int var) {
@@ -180,7 +220,7 @@
mv.visitVarInsn(opcode, var); // ALOAD 0 (THIS)
} else {
mv.visitVarInsn(opcode, var + 1); // All other variable index must be incremented (due to
- // the instance manager argument
+ // the instance manager argument
}
}
@@ -191,7 +231,8 @@
* it is not <code>this</code> (i.e. 0). This increment
* is due to the instance manager parameter added in the method
* signature.
- * @param var the variable index
+ *
+ * @param var the variable index
* @param increment the increment
* @see org.objectweb.asm.MethodAdapter#visitIincInsn(int, int)
*/
@@ -208,12 +249,13 @@
* Adds _manager and increment others variable indexes.
* This variable has the same scope than <code>this</code> and
* has the <code>1</code> index.
- * @param name the variable name
- * @param desc the variable descriptor
+ *
+ * @param name the variable name
+ * @param desc the variable descriptor
* @param signature the variable signature
- * @param start the beginning label
- * @param end the ending label
- * @param index the variable index
+ * @param start the beginning label
+ * @param end the ending label
+ * @param index the variable index
* @see org.objectweb.asm.MethodAdapter#visitLocalVariable(java.lang.String, java.lang.String, java.lang.String, org.objectweb.asm.Label, org.objectweb.asm.Label, int)
*/
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
@@ -228,7 +270,8 @@
* Visit max method.
* The stack size is incremented of 1. The
* local variable count is incremented of 2.
- * @param maxStack the stack size.
+ *
+ * @param maxStack the stack size.
* @param maxLocals the local variable count.
* @see org.objectweb.asm.MethodAdapter#visitMaxs(int, int)
*/
diff --git a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/MethodCreator.java b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/MethodCreator.java
index 4bfb240..3b65dff 100644
--- a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/MethodCreator.java
+++ b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/MethodCreator.java
@@ -59,12 +59,12 @@
/**
* Filed flag prefix.
*/
- private static final String FIELD_FLAG_PREFIX = "__F";
+ public static final String FIELD_FLAG_PREFIX = "__F";
/**
* Method flag prefix.
*/
- private static final String METHOD_FLAG_PREFIX = "__M";
+ public static final String METHOD_FLAG_PREFIX = "__M";
/**
* onEntry method name.
diff --git a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/CustomAnnotationVisitor.java b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/CustomAnnotationVisitor.java
index e3e14f0..58399be 100644
--- a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/CustomAnnotationVisitor.java
+++ b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/CustomAnnotationVisitor.java
@@ -100,7 +100,7 @@
* @param root is the annotation a root
* @param clazz the annotation is a class annotation.
* @param index the index of the argument
- * @param the descriptor of the method
+ * @param descriptor the descriptor of the method
*/
public CustomAnnotationVisitor(Element elem, MetadataCollector collector, boolean root, boolean clazz, int index, String descriptor) {
m_elem = elem;
diff --git a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/MethodCollector.java b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/MethodCollector.java
index e7f18cd..fb9bb72 100644
--- a/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/MethodCollector.java
+++ b/ipojo/manipulator/manipulator/src/main/java/org/apache/felix/ipojo/manipulation/annotations/MethodCollector.java
@@ -18,6 +18,7 @@
*/
package org.apache.felix.ipojo.manipulation.annotations;
+import org.apache.felix.ipojo.manipulation.MethodCreator;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.objectweb.asm.AnnotationVisitor;
@@ -26,6 +27,7 @@
/**
* This class collects method annotations, and give them to the metadata collector.
+ *
* @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
*/
public class MethodCollector extends EmptyVisitor {
@@ -47,7 +49,8 @@
/**
* Constructor.
- * @param name : name of the method.
+ *
+ * @param name : name of the method.
* @param collector : parent collector.
*/
public MethodCollector(String name, String descriptor, MetadataCollector collector) {
@@ -58,10 +61,11 @@
/**
* Visit a parameter annotation.
+ *
* @see org.objectweb.asm.commons.EmptyVisitor#visitParameterAnnotation(int, java.lang.String, boolean)
*/
public AnnotationVisitor visitParameterAnnotation(int index, String annotation,
- boolean visible) {
+ boolean visible) {
if (m_name.equals("<init>")) {
if (annotation.equals("Lorg/apache/felix/ipojo/annotations/Property;")) {
return processProperty(true, index);
@@ -80,9 +84,9 @@
}
-
/**
* Visit method annotations.
+ *
* @param arg0 : annotation name.
* @param arg1 : is the annotation visible at runtime.
* @return the visitor paring the visited annotation.
@@ -119,7 +123,7 @@
if (CustomAnnotationVisitor.isCustomAnnotation(arg0)) {
Element elem = CustomAnnotationVisitor.buildElement(arg0);
- elem.addAttribute(new Attribute("method", m_name));
+ elem.addAttribute(new Attribute("method", computeEffectiveMethodName(m_name)));
return new CustomAnnotationVisitor(elem, m_collector, true, false);
}
@@ -128,11 +132,12 @@
/**
* Process @Updated annotation.
+ *
* @return null.
*/
private AnnotationVisitor processUpdated() {
Element parent = null;
- if (! m_collector.getIds().containsKey("properties")) {
+ if (!m_collector.getIds().containsKey("properties")) {
parent = new Element("Properties", "");
m_collector.getIds().put("properties", parent);
m_collector.getElements().put(parent, null);
@@ -147,6 +152,7 @@
/**
* Process @PostRegistration annotation.
+ *
* @return null.
*/
private AnnotationVisitor processPostRegistration() {
@@ -163,6 +169,7 @@
/**
* Process @PostRegistration annotation.
+ *
* @return null.
*/
private AnnotationVisitor processPostUnregistration() {
@@ -179,6 +186,7 @@
/**
* Process @bind, @modified, @unbind.
+ *
* @param type : bind or unbind
* @return the visitor parsing @bind & @unbind annotations.
*/
@@ -188,37 +196,40 @@
/**
* Process @validate annotation.
+ *
* @return null.
*/
private AnnotationVisitor processValidate() {
Element cb = new Element("callback", "");
cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("transition", "validate"));
- cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("method", m_name));
+ cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("method", computeEffectiveMethodName(m_name)));
m_collector.getElements().put(cb, null);
return null;
}
/**
* Process @invalidate annotation.
+ *
* @return null.
*/
private AnnotationVisitor processInvalidate() {
Element cb = new Element("callback", "");
cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("transition", "invalidate"));
- cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("method", m_name));
+ cb.addAttribute(new org.apache.felix.ipojo.metadata.Attribute("method", computeEffectiveMethodName(m_name)));
m_collector.getElements().put(cb, null);
return null;
}
/**
* Process @property annotation.
+ *
* @param parameter true if we're processing a parameter
- * @param index the index, meaningful only if parameter is true
+ * @param index the index, meaningful only if parameter is true
* @return the visitor parsing the visited annotation.
*/
private AnnotationVisitor processProperty(boolean parameter, int index) {
Element prop = null;
- if (! m_collector.getIds().containsKey("properties")) {
+ if (!m_collector.getIds().containsKey("properties")) {
prop = new Element("Properties", "");
m_collector.getIds().put("properties", prop);
m_collector.getElements().put(prop, null);
@@ -291,6 +302,7 @@
/**
* Constructor.
+ *
* @param bind : method name.
* @param type : is the callback a bind or an unbind method.
*/
@@ -305,6 +317,7 @@
/**
* Visit annotation attribute.
+ *
* @param arg0 : annotation name
* @param arg1 : annotation value
* @see org.objectweb.asm.commons.EmptyVisitor#visit(java.lang.String, java.lang.Object)
@@ -349,20 +362,23 @@
/**
* End of the visit.
* Create or append the requirement info to a created or already existing "requires" element.
+ *
* @see org.objectweb.asm.commons.EmptyVisitor#visitEnd()
*/
public void visitEnd() {
if (m_id == null) {
- if (m_name != null && m_name.startsWith("bind")) {
- m_id = m_name.substring("bind".length());
- } else if (m_name != null && m_name.startsWith("unbind")) {
- m_id = m_name.substring("unbind".length());
- } else if (m_name != null && m_name.startsWith("modified")) {
- m_id = m_name.substring("modified".length());
+ String effectiveName = computeEffectiveMethodName(m_name);
+
+ if (effectiveName != null && effectiveName.startsWith("bind")) {
+ m_id = effectiveName.substring("bind".length());
+ } else if (effectiveName != null && effectiveName.startsWith("unbind")) {
+ m_id = effectiveName.substring("unbind".length());
+ } else if (effectiveName != null && effectiveName.startsWith("modified")) {
+ m_id = effectiveName.substring("modified".length());
} else if (m_index != -1) {
m_id = "" + m_index;
} else {
- System.err.println("Cannot determine the id of the " + m_type + " method : " + m_name);
+ System.err.println("Cannot determine the id of the " + m_type + " method : " + effectiveName);
return;
}
}
@@ -407,7 +423,7 @@
if (m_specification != null) {
if (itf == null) {
req.addAttribute(new Attribute("specification", m_specification));
- } else if (! m_specification.equals(itf)) {
+ } else if (!m_specification.equals(itf)) {
System.err.println("The required specification is not the same as previouly : " + m_specification + " & " + itf);
return;
}
@@ -416,7 +432,7 @@
if (m_optional != null) {
if (optional == null) {
req.addAttribute(new Attribute("optional", m_optional));
- } else if (! m_optional.equals(optional)) {
+ } else if (!m_optional.equals(optional)) {
System.err.println("The optional attribute is not always the same");
return;
}
@@ -425,7 +441,7 @@
if (m_aggregate != null) {
if (aggregate == null) {
req.addAttribute(new Attribute("aggregate", m_aggregate));
- } else if (! m_aggregate.equals(aggregate)) {
+ } else if (!m_aggregate.equals(aggregate)) {
System.err.println("The aggregate attribute is not always the same");
return;
}
@@ -434,7 +450,7 @@
if (m_filter != null) {
if (filter == null) {
req.addAttribute(new Attribute("filter", m_filter));
- } else if (! m_filter.equals(filter)) {
+ } else if (!m_filter.equals(filter)) {
System.err.println("The filter attribute is not always the same");
return;
}
@@ -443,7 +459,7 @@
if (m_policy != null) {
if (policy == null) {
req.addAttribute(new Attribute("policy", m_policy));
- } else if (! m_policy.equals(policy)) {
+ } else if (!m_policy.equals(policy)) {
System.err.println("The policy attribute is not always the same");
return;
}
@@ -452,7 +468,7 @@
if (m_comparator != null) {
if (comparator == null) {
req.addAttribute(new Attribute("comparator", m_comparator));
- } else if (! m_comparator.equals(comparator)) {
+ } else if (!m_comparator.equals(comparator)) {
System.err.println("The comparator attribute is not always the same");
return;
}
@@ -461,7 +477,7 @@
if (m_from != null) {
if (from == null) {
req.addAttribute(new Attribute("from", m_from));
- } else if (! m_from.equals(from)) {
+ } else if (!m_from.equals(from)) {
System.err.println("The from attribute is not always the same");
return;
}
@@ -470,7 +486,7 @@
}
if (m_name != null) {
Element method = new Element("callback", "");
- method.addAttribute(new Attribute("method", m_name));
+ method.addAttribute(new Attribute("method", computeEffectiveMethodName(m_name)));
method.addAttribute(new Attribute("type", m_type));
req.addElement(method);
} else {
@@ -527,10 +543,11 @@
/**
* Constructor.
+ *
* @param parent : parent element.
* @param method : attached method.
- * @param param : we're processing a parameter
- * @param index : the parameter index
+ * @param param : we're processing a parameter
+ * @param index : the parameter index
*/
private PropertyAnnotationParser(Element parent, String method, boolean param, int index) {
m_parent = parent;
@@ -541,6 +558,7 @@
/**
* Visit annotation attributes.
+ *
* @param arg0 : annotation name
* @param arg1 : annotation value
* @see org.objectweb.asm.commons.EmptyVisitor#visit(java.lang.String, java.lang.Object)
@@ -567,17 +585,20 @@
/**
* End of the visit.
* Append the computed element to the parent element.
+ *
* @see org.objectweb.asm.commons.EmptyVisitor#visitEnd()
*/
public void visitEnd() {
+ m_method = computeEffectiveMethodName(m_method);
+
// If neither name not id, try to extract the name
- if (m_name == null && m_id == null && m_method.startsWith("set")) {
+ if (m_name == null && m_id == null && m_method.startsWith("set")) {
m_name = m_method.substring("set".length());
m_id = m_name;
- // Else align the two values
- } else if (m_name != null && m_id == null) {
+ // Else align the two values
+ } else if (m_name != null && m_id == null) {
m_id = m_name;
- } else if (m_id != null && m_name == null) {
+ } else if (m_id != null && m_name == null) {
m_name = m_id;
}
@@ -615,4 +636,19 @@
}
}
+
+ /**
+ * Computes the real method name. This method is useful when the annotation is collected on an manipulated method
+ * (prefixed by <code>__M_</code>). This method just removes the prefix if found.
+ * @param name the collected method name
+ * @return the effective method name, can be the collected method name if the method name does not start with
+ * the prefix.
+ */
+ public static String computeEffectiveMethodName(String name) {
+ if (name != null && name.startsWith(MethodCreator.PREFIX)) {
+ return name.substring(MethodCreator.PREFIX.length());
+ } else {
+ return name;
+ }
+ }
}
diff --git a/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ClassCheckerTestCase.java b/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ClassCheckerTestCase.java
new file mode 100644
index 0000000..40f4740
--- /dev/null
+++ b/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ClassCheckerTestCase.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.felix.ipojo.manipulation;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import junit.framework.TestCase;
+import org.apache.felix.ipojo.manipulator.util.Streams;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Type;
+import org.objectweb.asm.commons.EmptyVisitor;
+import org.osgi.framework.BundleContext;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ClassCheckerTestCase extends TestCase {
+
+ public void testIsAlreadyManipulatedWithNotManipulatedResource() throws Exception {
+ ClassChecker checker = check(resource("test/SimplePojo.class"));
+ assertFalse(checker.isalreadyManipulated());
+ }
+
+ public void testIsAlreadyManipulatedWithManipulatedResource() throws Exception {
+ ClassChecker checker = check(manipulate(resource("test/SimplePojo.class")));
+ assertTrue(checker.isalreadyManipulated());
+ }
+
+ public void testMetadataForAlreadyManipulatedClassAreCleaned() throws Exception {
+ ClassChecker checker = check(manipulate(resource("test/AnnotatedComponent.class")));
+
+ // Check implemented interfaces
+ List<String> interfaces = checker.getInterfaces();
+ assertTrue(interfaces.isEmpty());
+
+ // Check super class
+ assertNull(checker.getSuperClass());
+
+ // Check inner classes
+ List<String> inner = checker.getInnerClasses();
+ assertTrue(inner.isEmpty());
+
+ // Ensure fields are correctly filtered
+ Map<String, String> fields = checker.getFields();
+ assertEquals(1, fields.size());
+ assertEquals("java.lang.String", fields.get("prop"));
+
+ // Ensure methods are also correctly filtered
+ List<MethodDescriptor> descriptors = checker.getMethods();
+ assertEquals(2, descriptors.size());
+
+ // AnnotatedComponent(BundleContext)
+ MethodDescriptor constructor = searchMethod("$init", descriptors);
+ assertNotNull(constructor);
+ Type[] arguments = Type.getArgumentTypes(constructor.getDescriptor());
+ assertEquals(1, arguments.length);
+ assertEquals(Type.getType(BundleContext.class), arguments[0]);
+
+ // @FakeAnnotation
+ // AnnotatedComponent.annotatedMethod():Void
+ MethodDescriptor method = searchMethod("annotatedMethod", descriptors);
+ assertNotNull(method);
+ assertEquals("()V", method.getDescriptor()); // return void + no params
+ assertAnnotationIsAlone(method, "Ltest/FakeAnnotation;");
+
+
+ }
+
+ private void assertAnnotationIsAlone(MethodDescriptor method, String desc) {
+ List<ClassChecker.AnnotationDescriptor> annotations = method.getAnnotations();
+
+ assertEquals(1, annotations.size());
+ ClassChecker.AnnotationDescriptor annotationDescriptor = annotations.get(0);
+ MethodVisitor mv = mock(MethodVisitor.class);
+ when(mv.visitAnnotation(desc, true)).thenReturn(new EmptyVisitor());
+ annotationDescriptor.visitAnnotation(mv);
+ }
+
+ private MethodDescriptor searchMethod(String methodName, List<MethodDescriptor> descriptors) {
+ for (MethodDescriptor descriptor : descriptors) {
+ if (methodName.equals(descriptor.getName())) {
+ return descriptor;
+ }
+ }
+
+ return null;
+ }
+
+ private byte[] manipulate(byte[] input) throws Exception {
+ Manipulator manipulator = new Manipulator();
+ return manipulator.manipulate(input);
+ }
+
+ private byte[] resource(String name) throws Exception {
+ return ManipulatorTest.getBytesFromFile(new File("target/test-classes/" + name));
+ }
+
+ private ClassChecker check(byte[] resource) throws Exception {
+ ClassChecker checker = new ClassChecker();
+ ByteArrayInputStream is = new ByteArrayInputStream(resource);
+ try {
+ ClassReader classReader = new ClassReader(is);
+ classReader.accept(checker, ClassReader.SKIP_FRAMES);
+ } finally {
+ Streams.close(is);
+ }
+ return checker;
+ }
+}
diff --git a/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ManipulatorTest.java b/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ManipulatorTest.java
index f5964dd..9bd1b89 100644
--- a/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ManipulatorTest.java
+++ b/ipojo/manipulator/manipulator/src/test/java/org/apache/felix/ipojo/manipulation/ManipulatorTest.java
@@ -29,7 +29,6 @@
import org.apache.felix.ipojo.InstanceManager;
import org.apache.felix.ipojo.Pojo;
-import org.apache.felix.ipojo.manipulation.annotations.MetadataCollector;
import org.mockito.Mockito;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.util.CheckClassAdapter;
@@ -39,7 +38,7 @@
public void testClusterDaemon() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/ClusterDaemon.class")));
- TestClassLoader classloader = new TestClassLoader("test.ClusterDaemon", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.ClusterDaemon", clazz);
//Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -62,7 +61,7 @@
public void testManipulatingTheSimplePojo() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/SimplePojo.class")));
- TestClassLoader classloader = new TestClassLoader("test.SimplePojo", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.SimplePojo", clazz);
Class cl = classloader.findClass("test.SimplePojo");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -113,7 +112,7 @@
public void testManipulatingChild() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/Child.class")));
- TestClassLoader classloader = new TestClassLoader("test.Child", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.Child", clazz);
Class cl = classloader.findClass("test.Child");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -160,7 +159,7 @@
public void _testManipulatingTheInner() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/PojoWithInner.class")));
- TestClassLoader classloader = new TestClassLoader("test.PojoWithInner", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.PojoWithInner", clazz);
Class cl = classloader.findClass("test.PojoWithInner");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -214,7 +213,7 @@
public void testManipulatingWithConstructorModification() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/Child.class")));
- TestClassLoader classloader = new TestClassLoader("test.Child", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.Child", clazz);
Class cl = classloader.findClass("test.Child");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -279,7 +278,7 @@
public void testManipulatingWithNoValidConstructor() throws Exception {
Manipulator manipulator = new Manipulator();
byte[] clazz = manipulator.manipulate(getBytesFromFile(new File("target/test-classes/test/NoValidConstructor.class")));
- TestClassLoader classloader = new TestClassLoader("test.NoValidConstructor", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.NoValidConstructor", clazz);
Class cl = classloader.findClass("test.NoValidConstructor");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -322,7 +321,7 @@
// fos.write(clazz);
// fos.close();
- TestClassLoader classloader = new TestClassLoader("test.ConstructorCheck", clazz);
+ ManipulatedClassLoader classloader = new ManipulatedClassLoader("test.ConstructorCheck", clazz);
Class cl = classloader.findClass("test.ConstructorCheck");
Assert.assertNotNull(cl);
Assert.assertNotNull(manipulator.getManipulationMetadata());
@@ -337,6 +336,8 @@
Assert.assertEquals("toto", f.get(o));
}
+
+
public static byte[] getBytesFromFile(File file) throws IOException {
InputStream is = new FileInputStream(file);
long length = file.length();
@@ -360,29 +361,6 @@
return bytes;
}
- class TestClassLoader extends ClassLoader {
- private String name;
- private byte[] clazz;
-
- public TestClassLoader(String name, byte[] clazz) {
- this.name = name;
- this.clazz = clazz;
- }
-
- public Class findClass(String name) throws ClassNotFoundException {
- if (name.equals(this.name)) {
- return defineClass(name, clazz, 0, clazz.length);
- }
- return super.findClass(name);
- }
-
- public Class loadClass(String arg0) throws ClassNotFoundException {
- return super.loadClass(arg0);
- }
-
-
-
- }
}
diff --git a/ipojo/manipulator/manipulator/src/test/java/test/AnnotatedComponent.java b/ipojo/manipulator/manipulator/src/test/java/test/AnnotatedComponent.java
index 569a100..ccd8d9c 100644
--- a/ipojo/manipulator/manipulator/src/test/java/test/AnnotatedComponent.java
+++ b/ipojo/manipulator/manipulator/src/test/java/test/AnnotatedComponent.java
@@ -20,10 +20,19 @@
import org.apache.felix.ipojo.annotations.Component;
import org.apache.felix.ipojo.annotations.Property;
+import org.apache.felix.ipojo.annotations.Requires;
+import org.osgi.framework.BundleContext;
@Component
public class AnnotatedComponent {
@Property
private String prop;
+
+ public AnnotatedComponent(BundleContext bundleContext) {}
+
+ @FakeAnnotation
+ public void annotatedMethod() {
+
+ }
}
diff --git a/ipojo/manipulator/manipulator/src/test/java/test/FakeAnnotation.java b/ipojo/manipulator/manipulator/src/test/java/test/FakeAnnotation.java
new file mode 100644
index 0000000..21d2076
--- /dev/null
+++ b/ipojo/manipulator/manipulator/src/test/java/test/FakeAnnotation.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 test;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * A {@code FakeAnnotation} is ...
+ *
+ * @author Guillaume Sauthier
+ */
+@Target({ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface FakeAnnotation {
+}