• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 android.processor.compat.unsupportedappusage;
17 
18 import static javax.tools.Diagnostic.Kind.ERROR;
19 
20 import com.google.common.collect.ImmutableMap;
21 
22 import java.util.ArrayList;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.Map;
26 
27 import javax.annotation.processing.Messager;
28 import javax.lang.model.element.AnnotationMirror;
29 import javax.lang.model.element.Element;
30 import javax.lang.model.element.ExecutableElement;
31 import javax.lang.model.element.PackageElement;
32 import javax.lang.model.element.TypeElement;
33 import javax.lang.model.element.VariableElement;
34 import javax.lang.model.type.ArrayType;
35 import javax.lang.model.type.DeclaredType;
36 import javax.lang.model.type.TypeKind;
37 import javax.lang.model.type.TypeMirror;
38 import javax.lang.model.type.TypeVariable;
39 import javax.lang.model.util.Types;
40 
41 /**
42  * Builds a dex signature for a given method or field.
43  */
44 final class SignatureConverter {
45 
46     private static final Map<TypeKind, String> TYPE_MAP = ImmutableMap.<TypeKind, String>builder()
47             .put(TypeKind.BOOLEAN, "Z")
48             .put(TypeKind.BYTE, "B")
49             .put(TypeKind.CHAR, "C")
50             .put(TypeKind.DOUBLE, "D")
51             .put(TypeKind.FLOAT, "F")
52             .put(TypeKind.INT, "I")
53             .put(TypeKind.LONG, "J")
54             .put(TypeKind.SHORT, "S")
55             .put(TypeKind.VOID, "V")
56             .build();
57 
58     private final Messager messager;
59 
SignatureConverter(Messager messager)60     SignatureConverter(Messager messager) {
61         this.messager = messager;
62     }
63 
64     /**
65      * Creates the signature for an annotated element.
66      */
getSignature(Types types, TypeElement annotation, Element element)67     String getSignature(Types types, TypeElement annotation, Element element) {
68         try {
69             String signature;
70             switch (element.getKind()) {
71                 case METHOD:
72                     signature = buildMethodSignature((ExecutableElement) element);
73                     break;
74                 case CONSTRUCTOR:
75                     signature = buildConstructorSignature((ExecutableElement) element);
76                     break;
77                 case FIELD:
78                     signature = buildFieldSignature((VariableElement) element);
79                     break;
80                 default:
81                     return null;
82             }
83             return verifyExpectedSignature(types, signature, element, annotation);
84         } catch (SignatureConverterException problem) {
85             messager.printMessage(ERROR, problem.getMessage(), element);
86             return null;
87         }
88     }
89 
90     /**
91      * Returns a list of enclosing elements for the given element, with the package first, and
92      * excluding the element itself.
93      */
getEnclosingElements(Element element)94     private List<Element> getEnclosingElements(Element element) {
95         List<Element> enclosing = new ArrayList<>();
96         element = element.getEnclosingElement(); // don't include the element itself.
97         while (element != null) {
98             enclosing.add(element);
99             element = element.getEnclosingElement();
100         }
101         Collections.reverse(enclosing);
102         return enclosing;
103     }
104 
105     /**
106      * Get the dex signature for a clazz, in format "Lpackage/name/Outer$Inner;"
107      */
getClassSignature(TypeElement clazz)108     private String getClassSignature(TypeElement clazz) {
109         StringBuilder signature = new StringBuilder("L");
110         for (Element enclosing : getEnclosingElements(clazz)) {
111             switch (enclosing.getKind()) {
112                 case MODULE:
113                     // ignore
114                     break;
115                 case PACKAGE:
116                     signature.append(((PackageElement) enclosing)
117                             .getQualifiedName()
118                             .toString()
119                             .replace('.', '/'));
120                     signature.append('/');
121                     break;
122                 case CLASS:
123                 case INTERFACE:
124                     signature.append(enclosing.getSimpleName()).append('$');
125                     break;
126                 default:
127                     throw new IllegalStateException(
128                             "Unexpected enclosing element " + enclosing.getKind());
129             }
130         }
131         return signature
132                 .append(clazz.getSimpleName())
133                 .append(";")
134                 .toString();
135     }
136 
137     /**
138      * Returns the type signature for a given type.
139      *
140      * <p>For primitive types, a single character. For classes, the class signature. For arrays, a
141      * "[" preceding the component type.
142      */
getTypeSignature(TypeMirror type)143     private String getTypeSignature(TypeMirror type) throws SignatureConverterException {
144         TypeKind kind = type.getKind();
145         if (TYPE_MAP.containsKey(kind)) {
146             return TYPE_MAP.get(kind);
147         }
148         switch (kind) {
149             case ARRAY:
150                 return "[" + getTypeSignature(((ArrayType) type).getComponentType());
151             case DECLARED:
152                 Element declaring = ((DeclaredType) type).asElement();
153                 if (!(declaring instanceof TypeElement)) {
154                     throw new SignatureConverterException(
155                             "Can't handle declared type of kind " + declaring.getKind());
156                 }
157                 return getClassSignature((TypeElement) declaring);
158             case TYPEVAR:
159                 TypeVariable typeVar = (TypeVariable)type;
160                 if (typeVar.getLowerBound().getKind() != TypeKind.NULL) {
161                     return getTypeSignature(typeVar.getLowerBound());
162                 } else if (typeVar.getUpperBound().getKind() != TypeKind.NULL) {
163                     return getTypeSignature(typeVar.getUpperBound());
164                 } else {
165                     throw new SignatureConverterException("Can't handle typevar with no bound");
166                 }
167             default:
168                 throw new SignatureConverterException("Can't handle type of kind " + kind);
169         }
170     }
171 
172     /**
173      * Get the signature for an executable, either a method or a constructor.
174      *
175      * @param name   "<init>" for  constructor, else the method name
176      * @param method The executable element in question.
177      */
getExecutableSignature(CharSequence name, ExecutableElement method)178     private String getExecutableSignature(CharSequence name, ExecutableElement method)
179             throws SignatureConverterException {
180         StringBuilder signature = new StringBuilder();
181         signature.append(getClassSignature((TypeElement) method.getEnclosingElement()))
182                 .append("->")
183                 .append(name)
184                 .append("(");
185         for (VariableElement param : method.getParameters()) {
186             signature.append(getTypeSignature(param.asType()));
187         }
188         signature
189                 .append(")")
190                 .append(getTypeSignature(method.getReturnType()));
191         return signature.toString();
192     }
193 
buildMethodSignature(ExecutableElement method)194     private String buildMethodSignature(ExecutableElement method)
195             throws SignatureConverterException {
196         return getExecutableSignature(method.getSimpleName(), method);
197     }
198 
buildConstructorSignature(ExecutableElement constructor)199     private String buildConstructorSignature(ExecutableElement constructor)
200             throws SignatureConverterException {
201         return getExecutableSignature("<init>", constructor);
202     }
203 
buildFieldSignature(VariableElement field)204     private String buildFieldSignature(VariableElement field) throws SignatureConverterException {
205         return new StringBuilder()
206                 .append(getClassSignature((TypeElement) field.getEnclosingElement()))
207                 .append("->")
208                 .append(field.getSimpleName())
209                 .append(":")
210                 .append(getTypeSignature(field.asType()))
211                 .toString();
212     }
213 
214     /** If we have an expected signature on the annotation, warn if it doesn't match. */
verifyExpectedSignature(Types types, String signature, Element element, TypeElement annotation)215     private String verifyExpectedSignature(Types types, String signature, Element element,
216             TypeElement annotation) throws SignatureConverterException {
217         AnnotationMirror annotationMirror = null;
218         for (AnnotationMirror mirror : element.getAnnotationMirrors()) {
219             if (types.isSameType(annotation.asType(), mirror.getAnnotationType())) {
220                 annotationMirror = mirror;
221                 break;
222             }
223         }
224         if (annotationMirror == null) {
225             throw new SignatureConverterException(
226                     "Element doesn't have any UnsupportedAppUsage annotation");
227         }
228         String expectedSignature = annotationMirror.getElementValues().entrySet().stream()
229                 .filter(e -> e.getKey().getSimpleName().contentEquals("expectedSignature"))
230                 .map(e -> (String) e.getValue().getValue())
231                 .findAny()
232                 .orElse(signature);
233         if (!signature.equals(expectedSignature)) {
234             throw new SignatureConverterException(String.format(
235                     "Expected signature doesn't match generated signature.\n"
236                             + " Expected:  %s\n Generated: %s",
237                     expectedSignature,
238                     signature));
239         }
240         return signature;
241     }
242 
243     /**
244      * Exception used internally when we can't build a signature.
245      */
246     private static class SignatureConverterException extends Exception {
SignatureConverterException(String message)247         SignatureConverterException(String message) {
248             super(message);
249         }
250     }
251 }
252