Home | History | Annotate | Download | only in dx
      1 /*
      2  * Copyright (C) 2017 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 package com.android.dx;
     17 
     18 import com.android.dx.dex.file.ClassDefItem;
     19 import com.android.dx.rop.annotation.Annotation;
     20 import com.android.dx.rop.annotation.AnnotationVisibility;
     21 import com.android.dx.rop.annotation.Annotations;
     22 import com.android.dx.rop.annotation.NameValuePair;
     23 import com.android.dx.rop.cst.*;
     24 
     25 import java.lang.annotation.ElementType;
     26 import java.util.HashMap;
     27 
     28 /**
     29  * Identifies an annotation on a program element, see {@link java.lang.annotation.ElementType}.
     30  *
     31  * Currently it is only targeting Class, Method, Field and Parameter because those are supported by
     32  * {@link com.android.dx.dex.file.AnnotationsDirectoryItem} so far.
     33  *
     34  * <p><strong>NOTE:</strong>
     35  * So far it only supports adding method annotation. The annotation of class, field and parameter
     36  * will be implemented later.
     37  *
     38  * <p><strong>WARNING:</strong>
     39  * The declared element of an annotation type should either have a default value or be set a value via
     40  * {@code AnnotationId.set(Element)}. Otherwise it will incur
     41  * {@link java.lang.annotation.IncompleteAnnotationException} when accessing the annotation element
     42  * through reflection. The example is as follows:
     43  * <pre>
     44  *     {@code @Retention(RetentionPolicy.RUNTIME)}
     45  *     {@code @Target({ElementType.METHOD})}
     46  *     {@code @interface MethodAnnotation {
     47  *                boolean elementBoolean();
     48  *                // boolean elementBoolean() default false;
     49  *            }
     50  *
     51  *            TypeId<?> GENERATED = TypeId.get("LGenerated;");
     52  *            MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call");
     53  *            Code code = dexMaker.declare(methodId, PUBLIC);
     54  *            code.returnVoid();
     55  *
     56  *            TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class);
     57  *            AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED,
     58  *              annotationTypeId, ElementType.METHOD);
     59  *
     60  *            AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true);
     61  *            annotationId.set(element);
     62  *            annotationId.addToMethod(dexMaker, methodId);
     63  *     }
     64  * </pre>
     65  *
     66  * @param <D> the type that declares the program element.
     67  * @param <V> the annotation type. It should be a known type before compile.
     68  */
     69 public final class AnnotationId<D, V> {
     70     private final TypeId<D> declaringType;
     71     private final TypeId<V> type;
     72     /** The type of program element to be annotated */
     73     private final ElementType annotatedElement;
     74     /** The elements this annotation holds */
     75     private final HashMap<String, NameValuePair> elements;
     76 
     77     private AnnotationId(TypeId<D> declaringType, TypeId<V> type, ElementType annotatedElement) {
     78         this.declaringType = declaringType;
     79         this.type = type;
     80         this.annotatedElement = annotatedElement;
     81         this.elements = new HashMap<>();
     82     }
     83 
     84     /**
     85      *  Construct an instance. It initially contains no elements.
     86      *
     87      * @param declaringType the type declaring the program element.
     88      * @param type the annotation type.
     89      * @param annotatedElement the program element type to be annotated.
     90      * @return an annotation {@code AnnotationId<D,V>} instance.
     91      */
     92     public static <D, V> AnnotationId<D, V> get(TypeId<D> declaringType, TypeId<V> type,
     93                                                 ElementType annotatedElement) {
     94         if (annotatedElement != ElementType.TYPE &&
     95                 annotatedElement != ElementType.METHOD &&
     96                 annotatedElement != ElementType.FIELD &&
     97                 annotatedElement != ElementType.PARAMETER) {
     98             throw new IllegalArgumentException("element type is not supported to annotate yet.");
     99         }
    100 
    101         return new AnnotationId<>(declaringType, type, annotatedElement);
    102     }
    103 
    104     /**
    105      * Set an annotation element of this instance.
    106      * If there is a preexisting element with the same name, it will be
    107      * replaced by this method.
    108      *
    109      * @param element {@code non-null;} the annotation element to be set.
    110      */
    111     public void set(Element element) {
    112         if (element == null) {
    113             throw new NullPointerException("element == null");
    114         }
    115 
    116         CstString pairName = new CstString(element.getName());
    117         Constant pairValue = Element.toConstant(element.getValue());
    118         NameValuePair nameValuePair = new NameValuePair(pairName, pairValue);
    119         elements.put(element.getName(), nameValuePair);
    120     }
    121 
    122     /**
    123      * Add this annotation to a method.
    124      *
    125      * @param dexMaker DexMaker instance.
    126      * @param method Method to be added to.
    127      */
    128     public void addToMethod(DexMaker dexMaker, MethodId<?, ?> method) {
    129         if (annotatedElement != ElementType.METHOD) {
    130             throw new IllegalStateException("This annotation is not for method");
    131         }
    132 
    133         if (method.declaringType != declaringType) {
    134             throw new IllegalArgumentException("Method" + method + "'s declaring type is inconsistent with" + this);
    135         }
    136 
    137         ClassDefItem classDefItem = dexMaker.getTypeDeclaration(declaringType).toClassDefItem();
    138 
    139         if (classDefItem == null) {
    140             throw new NullPointerException("No class defined item is found");
    141         } else {
    142             CstMethodRef cstMethodRef = method.constant;
    143 
    144             if (cstMethodRef == null) {
    145                 throw new NullPointerException("Method reference is NULL");
    146             } else {
    147                 // Generate CstType
    148                 CstType cstType = CstType.intern(type.ropType);
    149 
    150                 // Generate Annotation
    151                 Annotation annotation = new Annotation(cstType, AnnotationVisibility.RUNTIME);
    152 
    153                 // Add generated annotation
    154                 Annotations annotations = new Annotations();
    155                 for (NameValuePair nvp : elements.values()) {
    156                     annotation.add(nvp);
    157                 }
    158                 annotations.add(annotation);
    159                 classDefItem.addMethodAnnotations(cstMethodRef, annotations, dexMaker.getDexFile());
    160             }
    161         }
    162     }
    163 
    164     /**
    165      *  A wrapper of <code>NameValuePair</code> represents a (name, value) pair used as the contents
    166      *  of an annotation.
    167      *
    168      *  An {@code Element} instance is stored in {@code AnnotationId.elements} by calling {@code
    169      *  AnnotationId.set(Element)}.
    170      *
    171      *  <p><strong>WARNING: </strong></p>
    172      *  the name should be exact same as the annotation element declared in the annotation type
    173      *  which is referred by field {@code AnnotationId.type},otherwise the annotation will fail
    174      *  to add and {@code java.lang.reflect.Method.getAnnotations()} will return nothing.
    175      *
    176      */
    177     public static final class Element {
    178         /** {@code non-null;} the name */
    179         private final String name;
    180         /** {@code non-null;} the value */
    181         private final Object value;
    182 
    183         /**
    184          * Construct an instance.
    185          *
    186          * @param name {@code non-null;} the name
    187          * @param value {@code non-null;} the value
    188          */
    189         public Element(String name, Object value) {
    190             if (name == null) {
    191                 throw new NullPointerException("name == null");
    192             }
    193 
    194             if (value == null) {
    195                 throw new NullPointerException("value == null");
    196             }
    197             this.name = name;
    198             this.value = value;
    199         }
    200 
    201         public String getName() {
    202             return name;
    203         }
    204 
    205         public Object getValue() {
    206             return value;
    207         }
    208 
    209         /** {@inheritDoc} */
    210         @Override
    211         public String toString() {
    212             return "[" + name + ", " + value + "]";
    213         }
    214 
    215         /** {@inheritDoc} */
    216         @Override
    217         public int hashCode() {
    218             return name.hashCode() * 31 + value.hashCode();
    219         }
    220 
    221         /** {@inheritDoc} */
    222         @Override
    223         public boolean equals(Object other) {
    224             if (! (other instanceof Element)) {
    225                 return false;
    226             }
    227 
    228             Element otherElement = (Element) other;
    229 
    230             return name.equals(otherElement.name)
    231                     && value.equals(otherElement.value);
    232         }
    233 
    234         /**
    235          *  Convert a value of an element to a {@code Constant}.
    236          *  <p><strong>Warning:</strong> Array or TypeId value is not supported yet.
    237          *
    238          * @param value an annotation element value.
    239          * @return a Constant
    240          */
    241         static Constant toConstant(Object value) {
    242             Class clazz = value.getClass();
    243             if (clazz.isEnum()) {
    244                 CstString descriptor = new CstString(TypeId.get(clazz).getName());
    245                 CstString name = new CstString(((Enum)value).name());
    246                 CstNat cstNat = new CstNat(name, descriptor);
    247                 return new CstEnumRef(cstNat);
    248             } else if (clazz.isArray()) {
    249                 throw new UnsupportedOperationException("Array is not supported yet");
    250             } else if (value instanceof TypeId) {
    251                 throw new UnsupportedOperationException("TypeId is not supported yet");
    252             } else {
    253                 return  Constants.getConstant(value);
    254             }
    255         }
    256     }
    257 }
    258