| /* |
| * Copyright (C) 2017 The Android Open Source Project |
| * |
| * Licensed 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 com.android.dx; |
| |
| import com.android.dx.dex.file.ClassDefItem; |
| import com.android.dx.rop.annotation.Annotation; |
| import com.android.dx.rop.annotation.AnnotationVisibility; |
| import com.android.dx.rop.annotation.Annotations; |
| import com.android.dx.rop.annotation.NameValuePair; |
| import com.android.dx.rop.cst.*; |
| |
| import java.lang.annotation.ElementType; |
| import java.util.HashMap; |
| |
| /** |
| * Identifies an annotation on a program element, see {@link java.lang.annotation.ElementType}. |
| * |
| * Currently it is only targeting Class, Method, Field and Parameter because those are supported by |
| * {@link com.android.dx.dex.file.AnnotationsDirectoryItem} so far. |
| * |
| * <p><strong>NOTE:</strong> |
| * So far it only supports adding method annotation. The annotation of class, field and parameter |
| * will be implemented later. |
| * |
| * <p><strong>WARNING:</strong> |
| * The declared element of an annotation type should either have a default value or be set a value via |
| * {@code AnnotationId.set(Element)}. Otherwise it will incur |
| * {@link java.lang.annotation.IncompleteAnnotationException} when accessing the annotation element |
| * through reflection. The example is as follows: |
| * <pre> |
| * {@code @Retention(RetentionPolicy.RUNTIME)} |
| * {@code @Target({ElementType.METHOD})} |
| * {@code @interface MethodAnnotation { |
| * boolean elementBoolean(); |
| * // boolean elementBoolean() default false; |
| * } |
| * |
| * TypeId<?> GENERATED = TypeId.get("LGenerated;"); |
| * MethodId<?, Void> methodId = GENERATED.getMethod(VOID, "call"); |
| * Code code = dexMaker.declare(methodId, PUBLIC); |
| * code.returnVoid(); |
| * |
| * TypeId<MethodAnnotation> annotationTypeId = TypeId.get(MethodAnnotation.class); |
| * AnnotationId<?, MethodAnnotation> annotationId = AnnotationId.get(GENERATED, |
| * annotationTypeId, ElementType.METHOD); |
| * |
| * AnnotationId.Element element = new AnnotationId.Element("elementBoolean", true); |
| * annotationId.set(element); |
| * annotationId.addToMethod(dexMaker, methodId); |
| * } |
| * </pre> |
| * |
| * @param <D> the type that declares the program element. |
| * @param <V> the annotation type. It should be a known type before compile. |
| */ |
| public final class AnnotationId<D, V> { |
| private final TypeId<D> declaringType; |
| private final TypeId<V> type; |
| /** The type of program element to be annotated */ |
| private final ElementType annotatedElement; |
| /** The elements this annotation holds */ |
| private final HashMap<String, NameValuePair> elements; |
| |
| private AnnotationId(TypeId<D> declaringType, TypeId<V> type, ElementType annotatedElement) { |
| this.declaringType = declaringType; |
| this.type = type; |
| this.annotatedElement = annotatedElement; |
| this.elements = new HashMap<>(); |
| } |
| |
| /** |
| * Construct an instance. It initially contains no elements. |
| * |
| * @param declaringType the type declaring the program element. |
| * @param type the annotation type. |
| * @param annotatedElement the program element type to be annotated. |
| * @return an annotation {@code AnnotationId<D,V>} instance. |
| */ |
| public static <D, V> AnnotationId<D, V> get(TypeId<D> declaringType, TypeId<V> type, |
| ElementType annotatedElement) { |
| if (annotatedElement != ElementType.TYPE && |
| annotatedElement != ElementType.METHOD && |
| annotatedElement != ElementType.FIELD && |
| annotatedElement != ElementType.PARAMETER) { |
| throw new IllegalArgumentException("element type is not supported to annotate yet."); |
| } |
| |
| return new AnnotationId<>(declaringType, type, annotatedElement); |
| } |
| |
| /** |
| * Set an annotation element of this instance. |
| * If there is a preexisting element with the same name, it will be |
| * replaced by this method. |
| * |
| * @param element {@code non-null;} the annotation element to be set. |
| */ |
| public void set(Element element) { |
| if (element == null) { |
| throw new NullPointerException("element == null"); |
| } |
| |
| CstString pairName = new CstString(element.getName()); |
| Constant pairValue = Element.toConstant(element.getValue()); |
| NameValuePair nameValuePair = new NameValuePair(pairName, pairValue); |
| elements.put(element.getName(), nameValuePair); |
| } |
| |
| /** |
| * Add this annotation to a method. |
| * |
| * @param dexMaker DexMaker instance. |
| * @param method Method to be added to. |
| */ |
| public void addToMethod(DexMaker dexMaker, MethodId<?, ?> method) { |
| if (annotatedElement != ElementType.METHOD) { |
| throw new IllegalStateException("This annotation is not for method"); |
| } |
| |
| if (!method.declaringType.equals(declaringType)) { |
| throw new IllegalArgumentException("Method" + method + "'s declaring type is inconsistent with" + this); |
| } |
| |
| ClassDefItem classDefItem = dexMaker.getTypeDeclaration(declaringType).toClassDefItem(); |
| |
| if (classDefItem == null) { |
| throw new NullPointerException("No class defined item is found"); |
| } else { |
| CstMethodRef cstMethodRef = method.constant; |
| |
| if (cstMethodRef == null) { |
| throw new NullPointerException("Method reference is NULL"); |
| } else { |
| // Generate CstType |
| CstType cstType = CstType.intern(type.ropType); |
| |
| // Generate Annotation |
| Annotation annotation = new Annotation(cstType, AnnotationVisibility.RUNTIME); |
| |
| // Add generated annotation |
| Annotations annotations = new Annotations(); |
| for (NameValuePair nvp : elements.values()) { |
| annotation.add(nvp); |
| } |
| annotations.add(annotation); |
| classDefItem.addMethodAnnotations(cstMethodRef, annotations, dexMaker.getDexFile()); |
| } |
| } |
| } |
| |
| /** |
| * A wrapper of <code>NameValuePair</code> represents a (name, value) pair used as the contents |
| * of an annotation. |
| * |
| * An {@code Element} instance is stored in {@code AnnotationId.elements} by calling {@code |
| * AnnotationId.set(Element)}. |
| * |
| * <p><strong>WARNING: </strong></p> |
| * the name should be exact same as the annotation element declared in the annotation type |
| * which is referred by field {@code AnnotationId.type},otherwise the annotation will fail |
| * to add and {@code java.lang.reflect.Method.getAnnotations()} will return nothing. |
| * |
| */ |
| public static final class Element { |
| /** {@code non-null;} the name */ |
| private final String name; |
| /** {@code non-null;} the value */ |
| private final Object value; |
| |
| /** |
| * Construct an instance. |
| * |
| * @param name {@code non-null;} the name |
| * @param value {@code non-null;} the value |
| */ |
| public Element(String name, Object value) { |
| if (name == null) { |
| throw new NullPointerException("name == null"); |
| } |
| |
| if (value == null) { |
| throw new NullPointerException("value == null"); |
| } |
| this.name = name; |
| this.value = value; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public Object getValue() { |
| return value; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public String toString() { |
| return "[" + name + ", " + value + "]"; |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public int hashCode() { |
| return name.hashCode() * 31 + value.hashCode(); |
| } |
| |
| /** {@inheritDoc} */ |
| @Override |
| public boolean equals(Object other) { |
| if (! (other instanceof Element)) { |
| return false; |
| } |
| |
| Element otherElement = (Element) other; |
| |
| return name.equals(otherElement.name) |
| && value.equals(otherElement.value); |
| } |
| |
| /** |
| * Convert a value of an element to a {@code Constant}. |
| * <p><strong>Warning:</strong> Array or TypeId value is not supported yet. |
| * |
| * @param value an annotation element value. |
| * @return a Constant |
| */ |
| static Constant toConstant(Object value) { |
| Class clazz = value.getClass(); |
| if (clazz.isEnum()) { |
| CstString descriptor = new CstString(TypeId.get(clazz).getName()); |
| CstString name = new CstString(((Enum)value).name()); |
| CstNat cstNat = new CstNat(name, descriptor); |
| return new CstEnumRef(cstNat); |
| } else if (clazz.isArray()) { |
| throw new UnsupportedOperationException("Array is not supported yet"); |
| } else if (value instanceof TypeId) { |
| throw new UnsupportedOperationException("TypeId is not supported yet"); |
| } else { |
| return Constants.getConstant(value); |
| } |
| } |
| } |
| } |