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