/*
* 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.annotations;
import java.awt.image.renderable.ParameterBlock;
import org.apache.felix.ipojo.metadata.Attribute;
import org.apache.felix.ipojo.metadata.Element;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.Type;
import org.objectweb.asm.commons.EmptyVisitor;
/**
* 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 {
/**
* Parent collector.
*/
private MetadataCollector m_collector;
/**
* Method name.
*/
private String m_name;
/**
* Method Descriptor.
*/
private String m_descriptor;
/**
* Constructor.
* @param name : name of the method.
* @param collector : parent collector.
*/
public MethodCollector(String name, String descriptor, MetadataCollector collector) {
m_collector = collector;
m_name = name;
m_descriptor = descriptor;
}
/**
* 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) {
if (m_name.equals("<init>")) {
if (annotation.equals("Lorg/apache/felix/ipojo/annotations/Property;")) {
return processProperty(true, index);
}
if (annotation.equals("Lorg/apache/felix/ipojo/annotations/Requires;")) {
return new BindAnnotationParser(index);
}
if (CustomAnnotationVisitor.isCustomAnnotation(annotation)) {
Element elem = CustomAnnotationVisitor.buildElement(annotation);
elem.addAttribute(new Attribute("index", "" + index));
return new CustomAnnotationVisitor(elem, m_collector, true, false, index, m_descriptor);
}
}
return super.visitParameterAnnotation(index, annotation, visible);
}
/**
* Visit method annotations.
* @param arg0 : annotation name.
* @param arg1 : is the annotation visible at runtime.
* @return the visitor paring the visited annotation.
* @see org.objectweb.asm.commons.EmptyVisitor#visitAnnotation(java.lang.String, boolean)
*/
public AnnotationVisitor visitAnnotation(String arg0, boolean arg1) {
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Property;")) {
return processProperty(false, -1);
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Validate;")) {
return processValidate();
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Invalidate;")) {
return processInvalidate();
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Updated;")) {
return processUpdated();
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Bind;")) {
return processBind("bind");
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Modified;")) {
return processBind("modified");
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/Unbind;")) {
return processBind("unbind");
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/PostRegistration;")) {
return processPostRegistration();
}
if (arg0.equals("Lorg/apache/felix/ipojo/annotations/PostUnregistration;")) {
return processPostUnregistration();
}
if (CustomAnnotationVisitor.isCustomAnnotation(arg0)) {
Element elem = CustomAnnotationVisitor.buildElement(arg0);
elem.addAttribute(new Attribute("method", m_name));
return new CustomAnnotationVisitor(elem, m_collector, true, false);
}
return null;
}
/**
* Process @Updated annotation.
* @return null.
*/
private AnnotationVisitor processUpdated() {
Element parent = null;
if (! m_collector.getIds().containsKey("properties")) {
parent = new Element("Properties", "");
m_collector.getIds().put("properties", parent);
m_collector.getElements().put(parent, null);
} else {
parent = (Element) m_collector.getIds().get("properties");
}
parent.addAttribute(new Attribute("updated", m_name));
return null;
}
/**
* Process @PostRegistration annotation.
* @return null.
*/
private AnnotationVisitor processPostRegistration() {
Element parent = null;
if (m_collector.getIds().containsKey("provides")) {
parent = (Element) m_collector.getIds().get("provides");
parent.addAttribute(new Attribute("post-registration", m_name));
} else {
// Ignore annotation...
}
return null;
}
/**
* Process @PostRegistration annotation.
* @return null.
*/
private AnnotationVisitor processPostUnregistration() {
Element parent = null;
if (m_collector.getIds().containsKey("provides")) {
parent = (Element) m_collector.getIds().get("provides");
parent.addAttribute(new Attribute("post-unregistration", m_name));
} else {
// Ignore annotation...
}
return null;
}
/**
* Process @bind, @modified, @unbind.
* @param type : bind or unbind
* @return the visitor parsing @bind & @unbind annotations.
*/
private AnnotationVisitor processBind(String type) {
return new BindAnnotationParser(m_name, type);
}
/**
* 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));
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));
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
* @return the visitor parsing the visited annotation.
*/
private AnnotationVisitor processProperty(boolean parameter, int index) {
Element prop = null;
if (! m_collector.getIds().containsKey("properties")) {
prop = new Element("Properties", "");
m_collector.getIds().put("properties", prop);
m_collector.getElements().put(prop, null);
} else {
prop = (Element) m_collector.getIds().get("properties");
}
return new PropertyAnnotationParser(prop, m_name, parameter, index);
}
/**
* Parse @bind & @unbind annotations.
*/
private final class BindAnnotationParser extends EmptyVisitor implements AnnotationVisitor {
/**
* Method name.
*/
private String m_name;
/**
* Requirement filter.
*/
private String m_filter;
/**
* Is the requirement optional?
*/
private String m_optional;
/**
* Is the requirement aggregate?
*/
private String m_aggregate;
/**
* Required specification.
*/
private String m_specification;
/**
* Requirement id.
*/
private String m_id;
/**
* Bind, Modify or Unbind method?
*/
private String m_type;
/**
* Binding policy.
*/
private String m_policy;
/**
* Comparator.
*/
private String m_comparator;
/**
* From attribute.
*/
private String m_from;
/**
* For annotation parameter,
* the parameter index.
*/
private int m_index = -1;
/**
* Constructor.
* @param bind : method name.
* @param type : is the callback a bind or an unbind method.
*/
private BindAnnotationParser(String bind, String type) {
m_name = bind;
m_type = type;
}
private BindAnnotationParser(int index) {
m_index = index;
}
/**
* Visit annotation attribute.
* @param arg0 : annotation name
* @param arg1 : annotation value
* @see org.objectweb.asm.commons.EmptyVisitor#visit(java.lang.String, java.lang.Object)
*/
public void visit(String arg0, Object arg1) {
if (arg0.equals("filter")) {
m_filter = arg1.toString();
return;
}
if (arg0.equals("optional")) {
m_optional = arg1.toString();
return;
}
if (arg0.equals("aggregate")) {
m_aggregate = arg1.toString();
return;
}
if (arg0.equals("specification")) {
m_specification = arg1.toString();
return;
}
if (arg0.equals("policy")) {
m_policy = arg1.toString();
return;
}
if (arg0.equals("id")) {
m_id = arg1.toString();
return;
}
if (arg0.equals("comparator")) {
Type type = Type.getType(arg1.toString());
m_comparator = type.getClassName();
return;
}
if (arg0.equals("from")) {
m_from = arg1.toString();
return;
}
}
/**
* 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());
} else if (m_index != -1) {
m_id = "" + m_index;
} else {
System.err.println("Cannot determine the id of the " + m_type + " method : " + m_name);
return;
}
}
// Check if it is a full-determined requirement
Element req = (Element) m_collector.getIds().get(m_id);
if (req == null) {
// Add the complete requires
req = new Element("requires", "");
if (m_specification != null) {
req.addAttribute(new Attribute("specification", m_specification));
}
if (m_aggregate != null) {
req.addAttribute(new Attribute("aggregate", m_aggregate));
}
if (m_filter != null) {
req.addAttribute(new Attribute("filter", m_filter));
}
if (m_optional != null) {
req.addAttribute(new Attribute("optional", m_optional));
}
if (m_policy != null) {
req.addAttribute(new Attribute("policy", m_policy));
}
if (m_id != null) {
req.addAttribute(new Attribute("id", m_id));
}
if (m_comparator != null) {
req.addAttribute(new Attribute("comparator", m_comparator));
}
if (m_from != null) {
req.addAttribute(new Attribute("from", m_from));
}
} else {
String itf = req.getAttribute("specification");
String aggregate = req.getAttribute("aggregate");
String optional = req.getAttribute("optional");
String filter = req.getAttribute("filter");
String policy = req.getAttribute("policy");
String comparator = req.getAttribute("comparator");
String from = req.getAttribute("from");
if (m_specification != null) {
if (itf == null) {
req.addAttribute(new Attribute("specification", m_specification));
} else if (! m_specification.equals(itf)) {
System.err.println("The required specification is not the same as previouly : " + m_specification + " & " + itf);
return;
}
}
if (m_optional != null) {
if (optional == null) {
req.addAttribute(new Attribute("optional", m_optional));
} else if (! m_optional.equals(optional)) {
System.err.println("The optional attribute is not always the same");
return;
}
}
if (m_aggregate != null) {
if (aggregate == null) {
req.addAttribute(new Attribute("aggregate", m_aggregate));
} else if (! m_aggregate.equals(aggregate)) {
System.err.println("The aggregate attribute is not always the same");
return;
}
}
if (m_filter != null) {
if (filter == null) {
req.addAttribute(new Attribute("filter", m_filter));
} else if (! m_filter.equals(filter)) {
System.err.println("The filter attribute is not always the same");
return;
}
}
if (m_policy != null) {
if (policy == null) {
req.addAttribute(new Attribute("policy", m_policy));
} else if (! m_policy.equals(policy)) {
System.err.println("The policy attribute is not always the same");
return;
}
}
if (m_comparator != null) {
if (comparator == null) {
req.addAttribute(new Attribute("comparator", m_comparator));
} else if (! m_comparator.equals(comparator)) {
System.err.println("The comparator attribute is not always the same");
return;
}
}
if (m_from != null) {
if (from == null) {
req.addAttribute(new Attribute("from", m_from));
} else if (! m_from.equals(from)) {
System.err.println("The from attribute is not always the same");
return;
}
}
}
if (m_name != null) {
Element method = new Element("callback", "");
method.addAttribute(new Attribute("method", m_name));
method.addAttribute(new Attribute("type", m_type));
req.addElement(method);
} else {
req.addAttribute(new Attribute("constructor-parameter", Integer.toString(m_index)));
}
m_collector.getIds().put(m_id, req);
m_collector.getElements().put(req, null);
return;
}
}
private final class PropertyAnnotationParser extends EmptyVisitor implements AnnotationVisitor {
/**
* Parent element.
*/
private Element m_parent;
/**
* Attached method.
*/
private String m_method;
/**
* Property name.
*/
private String m_name;
/**
* Property id.
*/
private String m_id;
/**
* Property value.
*/
private String m_value;
/**
* Property mandatory aspect.
*/
private String m_mandatory;
/**
* Flag set to true if we're processing an annotation parameter.
*/
private boolean m_isParameterAnnotation = false;
/**
* If this is a parameter annotation, the index of the parameter.
*/
private int m_index = -1;
/**
* Constructor.
* @param parent : parent element.
* @param method : attached method.
* @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;
m_method = method;
m_isParameterAnnotation = param;
m_index = index;
}
/**
* Visit annotation attributes.
* @param arg0 : annotation name
* @param arg1 : annotation value
* @see org.objectweb.asm.commons.EmptyVisitor#visit(java.lang.String, java.lang.Object)
*/
public void visit(String arg0, Object arg1) {
if (arg0.equals("name")) {
m_name = arg1.toString();
return;
}
if (arg0.equals("value")) {
m_value = arg1.toString();
return;
}
if (arg0.equals("mandatory")) {
m_mandatory = arg1.toString();
return;
}
if (arg0.equals("id")) {
m_id = arg1.toString();
return;
}
}
/**
* End of the visit.
* Append the computed element to the parent element.
* @see org.objectweb.asm.commons.EmptyVisitor#visitEnd()
*/
public void visitEnd() {
// If neither name not id, try to extract the name
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) {
m_id = m_name;
} else if (m_id != null && m_name == null) {
m_name = m_id;
}
Element[] props = m_parent.getElements("Property");
Element prop = null;
for (int i = 0; props != null && prop == null && i < props.length; i++) {
String name = props[i].getAttribute("name");
if (name != null && name.equals(m_name)) {
prop = props[i];
}
}
if (prop == null) {
prop = new Element("property", "");
m_parent.addElement(prop);
if (m_name != null) {
prop.addAttribute(new Attribute("name", m_name));
}
}
if (m_value != null) {
prop.addAttribute(new Attribute("value", m_value));
}
if (m_mandatory != null) {
prop.addAttribute(new Attribute("mandatory", m_mandatory));
}
if (m_isParameterAnnotation) {
String t = Type.getArgumentTypes(m_descriptor)[m_index].getClassName();
prop.addAttribute(new Attribute("type", t));
prop.addAttribute(new Attribute("constructor-parameter", Integer.toString(m_index)));
} else {
prop.addAttribute(new Attribute("method", m_method));
}
}
}
}