• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
AnnotationId(TypeId<D> declaringType, TypeId<V> type, ElementType annotatedElement)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      */
get(TypeId<D> declaringType, TypeId<V> type, ElementType annotatedElement)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      */
set(Element element)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      */
addToMethod(DexMaker dexMaker, MethodId<?, ?> method)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.equals(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          */
Element(String name, Object value)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 
getName()201         public String getName() {
202             return name;
203         }
204 
getValue()205         public Object getValue() {
206             return value;
207         }
208 
209         /** {@inheritDoc} */
210         @Override
toString()211         public String toString() {
212             return "[" + name + ", " + value + "]";
213         }
214 
215         /** {@inheritDoc} */
216         @Override
hashCode()217         public int hashCode() {
218             return name.hashCode() * 31 + value.hashCode();
219         }
220 
221         /** {@inheritDoc} */
222         @Override
equals(Object other)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          */
toConstant(Object value)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