View Javadoc

    1   /***************************************************************************************
    2    * Copyright (c) Jonas Bonr, Alexandre Vasseur. All rights reserved.                 *
    3    * http://aspectwerkz.codehaus.org                                                    *
    4    * ---------------------------------------------------------------------------------- *
    5    * The software in this package is published under the terms of the LGPL license      *
    6    * a copy of which has been included with this distribution in the license.txt file.  *
    7    **************************************************************************************/
    8   package org.codehaus.aspectwerkz.annotation.expression;
    9
    10  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTAnnotation;
    11  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTArray;
    12  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTBoolean;
    13  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTChar;
    14  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTFloat;
    15  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTHex;
    16  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTIdentifier;
    17  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTInteger;
    18  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTKeyValuePair;
    19  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTOct;
    20  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTRoot;
    21  import org.codehaus.aspectwerkz.annotation.expression.ast.ASTString;
    22  import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParserVisitor;
    23  import org.codehaus.aspectwerkz.annotation.expression.ast.SimpleNode;
    24  import org.codehaus.aspectwerkz.annotation.expression.ast.AnnotationParser;
    25  import org.codehaus.aspectwerkz.annotation.expression.ast.ParseException;
    26  import org.codehaus.aspectwerkz.annotation.AnnotationElement;
    27  import org.codehaus.aspectwerkz.annotation.AnnotationManager;
    28  import org.codehaus.aspectwerkz.annotation.Annotation;
    29  import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
    30  import org.codehaus.aspectwerkz.util.Strings;
    31  import org.objectweb.asm.Type;
    32
    33  import java.lang.reflect.Field;
    34  import java.lang.reflect.Method;
    35  import java.lang.reflect.Array;
    36  import java.util.Map;
    37  import java.util.HashMap;
    38
    39  /***
    40   * Parse a source-like annotation representation to feed a map of AnnotationElement which
    41   * contain holder to actual values. Class and type referenced are holded behind lazy
    42   * wrapper that won't load them unless used.
    43   * <p/>
    44   * Note that this parser will trigger class loading to ensure type consistency
    45   * [change to ASMClassInfo instead of reflect if embedded parsing needed]
    46   * <p/>
    47   * Note: the loader used here is the one from the annotation class and not the one from annotated element
    48   * That does not matter since parse time is a build time operation for now.
    49   *
    50   * @author <a href="mailto:jboner@codehaus.org">Jonas Bonér </a>
    51   * @author <a href="mailto:alex AT gnilux DOT com">Alexandre Vasseur</a>
    52   */
    53  public class AnnotationVisitor implements AnnotationParserVisitor {
    54
    55      /***
    56       * The one and only annotation parser.
    57       */
    58      protected static final AnnotationParser PARSER = new AnnotationParser(System.in);
    59
    60      protected Map m_annotationElementValueHoldersByName;
    61
    62      /***
    63       * We reference class at parse time. We don't need to avoid reflection.
    64       */
    65      protected Class m_annotationClass;
    66
    67      /***
    68       * Creates a new visitor.
    69       */
    70      public AnnotationVisitor(final Map annotationElementValueHoldersByName, final Class annotationClass) {
    71          m_annotationElementValueHoldersByName = annotationElementValueHoldersByName;
    72          m_annotationClass = annotationClass;
    73      }
    74
    75      /***
    76       * Parse the given annotationRepresentation (@XXX(...)) to feed the given annotationElements map,
    77       * based on the annotationClass annotation interface.
    78       *
    79       * @param annotationElements
    80       * @param annotationRepresentation
    81       * @param annotationClass
    82       */
    83      public static void parse(final Map annotationElements, final String annotationRepresentation,
    84                               final Class annotationClass) {
    85          try {
    86              ASTRoot root = PARSER.parse(annotationRepresentation);
    87              new AnnotationVisitor(annotationElements, annotationClass).visit(root, null);
    88          } catch (ParseException e) {
    89              throw new WrappedRuntimeException("cannot parse annotation [" + annotationRepresentation + "]", e);
    90          }
    91      }
    92
    93      public Object visit(SimpleNode node, Object data) {
    94          return node.jjtGetChild(0).jjtAccept(this, data);
    95      }
    96
    97      public Object visit(ASTRoot node, Object data) {
    98          return node.jjtGetChild(0).jjtAccept(this, data);
    99      }
    100
    101     public Object visit(ASTAnnotation node, Object data) {
    102         int nr = node.jjtGetNumChildren();
    103
    104         if (nr == 1 && !(node.jjtGetChild(0) instanceof ASTKeyValuePair)) {
    105             // single "value" default
    106         		Object value = node.jjtGetChild(0).jjtAccept(this, data);
    107
    108         		if(!(node.jjtGetChild(0) instanceof ASTAnnotation)) { // child already set the value
    109                m_annotationElementValueHoldersByName.put("value",
    110                      new AnnotationElement("value", value));
    111         		}
    112         } else {
    113             for (int i = 0; i < nr; i++) {
    114                 node.jjtGetChild(i).jjtAccept(this, data);
    115             }
    116         }
    117         return null;
    118     }
    119
    120     public Object visit(ASTKeyValuePair node, Object data) {
    121         String elementName = node.getKey();
    122
    123         // get the methodInfo for this elementName to access its type from its name
    124         MethodInfo elementMethod = getMethodInfo(elementName);
    125
    126         // nested annotation
    127         if (node.jjtGetChild(0) instanceof ASTAnnotation) {
    128             Map nestedAnnotationElementValueHoldersByName = new HashMap();
    129             AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
    130                     nestedAnnotationElementValueHoldersByName,
    131                     elementMethod.elementType
    132                     );
    133             nestedAnnotationVisitor.visit((ASTAnnotation)node.jjtGetChild(0), data);
    134             m_annotationElementValueHoldersByName.put(elementName,
    135                     new AnnotationElement(elementName,
    136                             AnnotationManager.instantiateNestedAnnotation(elementMethod.elementType, nestedAnnotationElementValueHoldersByName)));
    137         } else {
    138             Object typedValue = node.jjtGetChild(0).jjtAccept(this, elementMethod);
    139             m_annotationElementValueHoldersByName.put(elementName,
    140                     new AnnotationElement(elementName, typedValue));
    141         }
    142         return null;
    143     }
    144
    145     public Object visit(ASTArray node, Object data) {
    146         MethodInfo methodInfo = (MethodInfo) data;
    147         Class elementType = methodInfo.elementType;
    148         if (!elementType.isArray()) {
    149             throw new RuntimeException(
    150                     "type for element ["
    151                     + methodInfo.elementMethod.getName()
    152                     + "] is not of type array"
    153             );
    154         }
    155         Class componentType = elementType.getComponentType();
    156         if (componentType.isArray()) {
    157             throw new UnsupportedOperationException(
    158                     "multidimensional arrays are not supported for element type, was required method ["
    159                     + methodInfo.elementMethod.getName()
    160                     + "]"
    161             );
    162         }
    163         return createTypedArray(node, data, node.jjtGetNumChildren(), componentType);
    164     }
    165
    166     public Object visit(ASTIdentifier node, Object data) {
    167         String identifier = node.getValue();
    168         if (identifier.endsWith(".class")) {
    169             return handleClassIdentifier(identifier);
    170         } else if (isJavaReferenceType(identifier)) {
    171             return handleReferenceIdentifier(identifier);
    172         } else {
    173             throw new RuntimeException("unsupported format for java type or reference [" + identifier + "]");
    174         }
    175     }
    176
    177     public Object visit(ASTBoolean node, Object data) {
    178         return Boolean.valueOf(node.getValue());
    179     }
    180
    181     public Object visit(ASTChar node, Object data) {
    182         return new Character(node.getValue().charAt(0));
    183     }
    184
    185     public Object visit(ASTString node, Object data) {
    186         // the node contains the  \" string escapes
    187         if (node.getValue().length() >= 2) {
    188             String escaped = node.getValue().substring(1, node.getValue().length() - 1);
    189             return Strings.replaceSubString(escaped, "//\"", "\"");
    190         } else {
    191             return node.getValue();
    192         }
    193     }
    194
    195     public Object visit(ASTInteger node, Object data) {
    196         String value = node.getValue();
    197         char lastChar = value.charAt(value.length() - 1);
    198         if ((lastChar == 'L') || (lastChar == 'l')) {
    199             return new Long(value.substring(0, value.length() - 1));
    200         } else if (value.length() > 9) {
    201             return new Long(value);
    202         } else {
    203             return new Integer(value);
    204         }
    205     }
    206
    207     public Object visit(ASTFloat node, Object data) {
    208         String value = node.getValue();
    209         char lastChar = value.charAt(value.length() - 1);
    210         if ((lastChar == 'D') || (lastChar == 'd')) {
    211             return new Double(value.substring(0, value.length() - 1));
    212         } else if ((lastChar == 'F') || (lastChar == 'f')) {
    213             return new Float(value.substring(0, value.length() - 1));
    214         } else {
    215             return new Double(value);
    216         }
    217     }
    218
    219     public Object visit(ASTHex node, Object data) {
    220         throw new UnsupportedOperationException("hex numbers not yet supported");
    221     }
    222
    223     public Object visit(ASTOct node, Object data) {
    224         throw new UnsupportedOperationException("octal numbers not yet supported");
    225     }
    226
    227     /***
    228      * For a typed annotation, there should be
    229      * - a setter method setx or setX
    230      * - a getter method x or getx or getX
    231      *
    232      * @param elementName
    233      * @return
    234      */
    235     private MethodInfo getMethodInfo(final String elementName) {
    236         StringBuffer javaBeanMethodPostfix = new StringBuffer();
    237         javaBeanMethodPostfix.append(elementName.substring(0, 1).toUpperCase());
    238         if (elementName.length() > 1) {
    239             javaBeanMethodPostfix.append(elementName.substring(1));
    240         }
    241
    242         MethodInfo methodInfo = new MethodInfo();
    243         Method[] methods = m_annotationClass.getDeclaredMethods();
    244         // look for element methods
    245         for (int i = 0; i < methods.length; i++) {
    246             Method elementMethod = methods[i];
    247             if (elementMethod.getName().equals(elementName)) {
    248                 methodInfo.elementMethod = elementMethod;
    249                 methodInfo.elementType = elementMethod.getReturnType();
    250                 break;
    251             }
    252         }
    253         if (methodInfo.elementMethod == null) {
    254             throw new RuntimeException(
    255                     "method for the annotation element ["
    256                     + elementName
    257                     + "] can not be found in annotation interface ["
    258                     + m_annotationClass.getName()
    259                     + "]"
    260             );
    261         }
    262         return methodInfo;
    263     }
    264
    265     private boolean isJavaReferenceType(final String valueAsString) {
    266         int first = valueAsString.indexOf('.');
    267         int last = valueAsString.lastIndexOf('.');
    268         int comma = valueAsString.indexOf(',');
    269         if ((first > 0) && (last > 0) && (first != last) && (comma < 0)) {
    270             return true;
    271         } else {
    272             return false;
    273         }
    274     }
    275
    276     private Object createTypedArray(final ASTArray node,
    277                                     final Object data,
    278                                     final int nrOfElements,
    279                                     final Class componentType) {
    280         if (componentType.equals(String.class)) {
    281             String[] array = new String[nrOfElements];
    282             for (int i = 0; i < nrOfElements; i++) {
    283                 String value = (String) node.jjtGetChild(i).jjtAccept(this, data);
    284                 array[i] = value;
    285             }
    286             return array;
    287         } else if (componentType.equals(long.class)) {
    288             long[] array = new long[nrOfElements];
    289             for (int i = 0; i < nrOfElements; i++) {
    290                 array[i] = ((Long) node.jjtGetChild(i).jjtAccept(this, data)).longValue();
    291             }
    292             return array;
    293         } else if (componentType.equals(int.class)) {
    294             int[] array = new int[nrOfElements];
    295             for (int i = 0; i < nrOfElements; i++) {
    296                 array[i] = ((Integer) node.jjtGetChild(i).jjtAccept(this, data)).intValue();
    297             }
    298             return array;
    299         } else if (componentType.equals(short.class)) {
    300             short[] array = new short[nrOfElements];
    301             for (int i = 0; i < nrOfElements; i++) {
    302                 array[i] = ((Short) node.jjtGetChild(i).jjtAccept(this, data)).shortValue();
    303             }
    304             return array;
    305         } else if (componentType.equals(double.class)) {
    306             double[] array = new double[nrOfElements];
    307             for (int i = 0; i < nrOfElements; i++) {
    308                 array[i] = ((Double) node.jjtGetChild(i).jjtAccept(this, data)).doubleValue();
    309             }
    310             return array;
    311         } else if (componentType.equals(float.class)) {
    312             float[] array = new float[nrOfElements];
    313             for (int i = 0; i < nrOfElements; i++) {
    314                 array[i] = ((Float) node.jjtGetChild(i).jjtAccept(this, data)).floatValue();
    315             }
    316             return array;
    317         } else if (componentType.equals(byte.class)) {
    318             byte[] array = new byte[nrOfElements];
    319             for (int i = 0; i < nrOfElements; i++) {
    320                 array[i] = ((Byte) node.jjtGetChild(i).jjtAccept(this, data)).byteValue();
    321             }
    322             return array;
    323         } else if (componentType.equals(char.class)) {
    324             char[] array = new char[nrOfElements];
    325             for (int i = 0; i < nrOfElements; i++) {
    326                 array[i] = ((Character) node.jjtGetChild(i).jjtAccept(this, data)).charValue();
    327             }
    328             return array;
    329         } else if (componentType.equals(boolean.class)) {
    330             boolean[] array = new boolean[nrOfElements];
    331             for (int i = 0; i < nrOfElements; i++) {
    332                 array[i] = ((Boolean) node.jjtGetChild(i).jjtAccept(this, data)).booleanValue();
    333             }
    334             return array;
    335         } else if (componentType.equals(Class.class)) {
    336             AnnotationElement.LazyClass[] array = new AnnotationElement.LazyClass[nrOfElements];
    337             for (int i = 0; i < nrOfElements; i++) {
    338                 array[i] = (AnnotationElement.LazyClass) node.jjtGetChild(i).jjtAccept(this, data);
    339             }
    340             return array;
    341         } else {
    342             if (nrOfElements > 1 && node.jjtGetChild(0) instanceof ASTAnnotation) {
    343                 // nested array of annotation
    344                 Object[] nestedTyped = (Object[])Array.newInstance(componentType, nrOfElements);
    345                 for (int i = 0; i < nrOfElements; i++) {
    346                     Map nestedAnnotationElementValueHoldersByName = new HashMap();
    347                     AnnotationVisitor nestedAnnotationVisitor = new AnnotationVisitor(
    348                             nestedAnnotationElementValueHoldersByName,
    349                             componentType
    350                             );
    351                     nestedAnnotationVisitor.visit((ASTAnnotation)node.jjtGetChild(i), data);
    352                     nestedTyped[i] = AnnotationManager.instantiateNestedAnnotation(componentType, nestedAnnotationElementValueHoldersByName);
    353                 }
    354                 return nestedTyped;
    355             } else {
    356                 // reference type
    357                 Object[] array = new Object[nrOfElements];
    358                 for (int i = 0; i < nrOfElements; i++) {
    359                     array[i] = node.jjtGetChild(i).jjtAccept(this, data);
    360                 }
    361                 return array;
    362             }
    363         }
    364     }
    365
    366     private Object handleClassIdentifier(String identifier) {
    367         int index = identifier.lastIndexOf('.');
    368         String className = identifier.substring(0, index);
    369
    370         int dimension = 0;
    371         String componentClassName = className;
    372         while (componentClassName.endsWith("[]")) {
    373             dimension++;
    374             componentClassName = componentClassName.substring(0, componentClassName.length()-2);
    375         }
    376
    377         Class componentClass = null;
    378         boolean isComponentPrimitive = true;
    379         if (componentClassName.equals("long")) {
    380             componentClass = long.class;
    381         } else if (componentClassName.equals("int")) {
    382             componentClass = int.class;
    383         } else if (componentClassName.equals("short")) {
    384             componentClass = short.class;
    385         } else if (componentClassName.equals("double")) {
    386             componentClass = double.class;
    387         } else if (componentClassName.equals("float")) {
    388             componentClass = float.class;
    389         } else if (componentClassName.equals("byte")) {
    390             componentClass = byte.class;
    391         } else if (componentClassName.equals("char")) {
    392             componentClass = char.class;
    393         } else if (componentClassName.equals("boolean")) {
    394             componentClass = boolean.class;
    395         } else {
    396             isComponentPrimitive = false;
    397             try {
    398                 componentClass = Class.forName(componentClassName, false, m_annotationClass.getClassLoader());
    399             } catch (ClassNotFoundException e) {
    400                 throw new RuntimeException("could not load class [" + className + "] due to: " + e.toString());
    401             }
    402         }
    403
    404         // primitive types are not wrapped in a LazyClass
    405         if (isComponentPrimitive) {
    406             if (dimension <= 0) {
    407                 return componentClass;
    408             } else {
    409                 return Array.newInstance(componentClass, dimension);
    410             }
    411         } else {
    412             String componentType = Type.getType(componentClass).getDescriptor();
    413             for (int i = 0; i < dimension; i++) {
    414                 componentType = "[" + componentType;
    415             }
    416             Type type = Type.getType(componentType);
    417             return new AnnotationElement.LazyClass(type.getClassName());
    418         }
    419     }
    420
    421     private Object handleReferenceIdentifier(String identifier) {
    422         int index = identifier.lastIndexOf('.');
    423         String className = identifier.substring(0, index);
    424         String fieldName = identifier.substring(index + 1, identifier.length());
    425         try {
    426             // TODO m_annotationClass might be higher in the CL than a referenced identifier
    427             Class clazz = Class.forName(className, false, m_annotationClass.getClassLoader());
    428             Field field = clazz.getDeclaredField(fieldName);
    429             return field.get(null);
    430         } catch (Exception e) {
    431             throw new RuntimeException(
    432                     "could not access reference field [" + identifier + "] due to: " + e.toString()
    433             );
    434         }
    435     }
    436
    437     /***
    438      * Holds the element method and type.
    439      */
    440     private static class MethodInfo {
    441
    442         public Method elementMethod;
    443
    444         public Class elementType;
    445     }
    446 }