• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 
17 package com.android.bedstead.remoteframeworkclasses.processor;
18 
19 import static com.android.bedstead.remoteframeworkclasses.processor.Processor.CLASSES_LISTED_IN_TEST_CURRENT_FILE;
20 import static com.android.bedstead.remoteframeworkclasses.processor.Processor.TEST_APIS_REFLECTION_PACKAGE;
21 
22 import com.google.common.collect.ImmutableList;
23 import com.google.common.collect.ImmutableSet;
24 
25 import java.util.Arrays;
26 import java.util.HashSet;
27 import java.util.List;
28 import java.util.Objects;
29 import java.util.Set;
30 import java.util.stream.Collectors;
31 
32 import javax.lang.model.element.Element;
33 import javax.lang.model.element.ExecutableElement;
34 import javax.lang.model.element.Modifier;
35 import javax.lang.model.element.TypeElement;
36 import javax.lang.model.type.DeclaredType;
37 import javax.lang.model.type.TypeKind;
38 import javax.lang.model.type.TypeMirror;
39 import javax.lang.model.util.Elements;
40 import javax.lang.model.util.Types;
41 
42 /**
43  * Represents a minimal representation of a method for comparison purposes
44  */
45 public final class MethodSignature {
46 
47     /** Create a {@link MethodSignature} for the given {@link ExecutableElement}. */
forMethod(ExecutableElement method, Elements elements)48     public static MethodSignature forMethod(ExecutableElement method, Elements elements) {
49         List<TypeMirror> parameters = method.getParameters().stream()
50                 .map(Element::asType)
51                 .map(m -> rawType(m, elements))
52                 .collect(Collectors.toList());
53 
54         Set<TypeMirror> exceptions = method.getThrownTypes()
55                 .stream().map(m -> rawType(m, elements))
56                 .collect(Collectors.toSet());
57 
58         return new MethodSignature(Visibility.ofMethod(method),
59                 rawType(method.getReturnType(), elements),
60                 method.getSimpleName().toString(), parameters, exceptions);
61     }
62 
rawType(TypeMirror type, Elements elements)63     private static TypeMirror rawType(TypeMirror type, Elements elements) {
64         if (type instanceof DeclaredType) {
65             DeclaredType t = (DeclaredType) type;
66             if (!t.getTypeArguments().isEmpty()) {
67                 type = elements.getTypeElement(t.toString().split("<", 2)[0]).asType();
68             }
69         }
70         return type;
71     }
72 
73     /**
74      * Create a {@link MethodSignature} for the given string from an API file.
75      */
forApiString( String string, Types types, Elements elements)76     public static /* @Nullable */ MethodSignature forApiString(
77             String string, Types types, Elements elements) {
78         try {
79             // Strip annotations
80             string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
81             string = string.replaceAll("@.+? ", "");
82 
83             String[] parts = string.split(" ", 2);
84             Visibility visibility;
85             try {
86                 visibility = Visibility.valueOf(parts[0].toUpperCase());
87             } catch (IllegalArgumentException e) {
88                 throw new IllegalStateException("Error finding visibility in string " + string);
89             }
90             string = parts[1];
91             parts = string.split(" ", 2);
92 
93             TypeMirror returnType;
94             while (parts[0].equals("abstract") || parts[0].equals("final")
95                     || parts[0].equals("static")) {
96                 // These don't affect the signature in ways we care about
97                 string = parts[1];
98                 parts = string.split(" ", 2);
99             }
100 
101             if (string.startsWith("<")) {
102                 // This includes type arguments, for now we ignore this method
103                 return null;
104             }
105 
106             returnType = typeForString(parts[0], types, elements);
107 
108             string = parts[1];
109             parts = string.split("\\(", 2);
110             String methodName = parts[0];
111             string = parts[1];
112             parts = string.split("\\)", 2);
113             // Remove generic types as we don't need to care about them at this point
114             String parametersString = parts[0].replaceAll("<.*>", "");
115             // Remove varargs
116             parametersString = parametersString.replaceAll("\\.\\.\\.", "");
117             List<TypeMirror> parameters;
118             try {
119                 parameters = Arrays.stream(parametersString.split(", "))
120                         .map(String::trim)
121                         .filter(t -> !t.isEmpty())
122                         .map(t -> typeForString(t, types, elements))
123                         .collect(Collectors.toList());
124             } catch (IllegalStateException e) {
125                 throw new IllegalStateException(
126                         "Error parsing types from string " + parametersString, e);
127             }
128             string = parts[1];
129             Set<TypeMirror> exceptions = new HashSet<>();
130             if (string.contains("throws")) {
131                 exceptions = Arrays.stream(string.split("throws ", 2)[1].split(","))
132                         .map(t -> t.trim())
133                         .filter(t -> !t.isEmpty())
134                         .map(t -> typeForString(t, types, elements))
135                         .collect(Collectors.toSet());
136             }
137 
138             return new MethodSignature(visibility, returnType, methodName, parameters, exceptions);
139         } catch (Exception e) {
140             throw new RuntimeException("TestApisReflection: unable to parse method: " + string, e);
141         }
142     }
143 
typeForString(String typeName, Types types, Elements elements)144     private static TypeMirror typeForString(String typeName, Types types, Elements elements) {
145         if (typeName.equals("void")) {
146             return types.getNoType(TypeKind.VOID);
147         }
148 
149         if (isTestClass(typeName, elements)) {
150             // Use the proxy type instead
151             typeName = proxyType(typeName);
152         }
153 
154         if (typeName.contains("<")) {
155             // Because of type erasure we can just drop the type argument
156             return typeForString(typeName.split("<", 2)[0], types, elements);
157         }
158 
159         if (typeName.endsWith("[]")) {
160             return types.getArrayType(
161                     typeForString(typeName.substring(0, typeName.length() - 2), types, elements));
162         }
163 
164         try {
165             return types.getPrimitiveType(TypeKind.valueOf(typeName.toUpperCase()));
166         } catch (IllegalArgumentException e) {
167             // Not a primitive
168         }
169 
170         TypeElement element = elements.getTypeElement(typeName);
171         if (element == null) {
172             // It could be java.lang
173             element = elements.getTypeElement("java.lang." + typeName);
174         }
175 
176         if (element == null) {
177             throw new IllegalStateException("Unknown type: " + typeName);
178         }
179 
180         return element.asType();
181     }
182 
183     /**
184      * A "Test Class" is a class marked as @TestApi at class level.
185      *
186      * <p>Since such classes are not available on environments where test sdk is disabled. We
187      * identify them so that we can replace them with proxy classes generated by the
188      * TestApisReflection module.
189      */
isTestClass(String typeName, Elements elements)190     private static boolean isTestClass(String typeName, Elements elements) {
191         boolean isListedInTestCurrentFile = CLASSES_LISTED_IN_TEST_CURRENT_FILE.stream()
192                         .anyMatch(c -> c.getName().equals(typeName));
193         // wouldn't be accessible when test sdk is disabled.
194         boolean isNotAccessible = elements.getTypeElement(typeName) == null;
195         return isListedInTestCurrentFile && isNotAccessible;
196     }
197 
proxyType(String typeName)198     private static String proxyType(String typeName) {
199         String[] parts = typeName.split("\\.");
200         StringBuilder simpleName = new StringBuilder();
201         for (String p : parts) {
202             if (Character.isUpperCase(p.charAt(0))) {
203                 simpleName.append(p);
204             }
205         }
206 
207         return TEST_APIS_REFLECTION_PACKAGE + "." + simpleName + "Proxy";
208     }
209 
210     enum Visibility {
211         PUBLIC,
212         PROTECTED;
213 
ofMethod(ExecutableElement method)214         static Visibility ofMethod(ExecutableElement method) {
215             if (method.getModifiers().contains(Modifier.PUBLIC)) {
216                 return PUBLIC;
217             } else if (method.getModifiers().contains(Modifier.PROTECTED)) {
218                 return PROTECTED;
219             }
220 
221             throw new IllegalArgumentException("Only public and protected are visible in APIs");
222         }
223     }
224 
225     private final Visibility mVisibility;
226     private final String mReturnType;
227     private final String mName;
228     public final ImmutableList<String> mParameterTypes;
229     public final ImmutableSet<String> mExceptions;
MethodSignature( Visibility visibility, TypeMirror returnType, String name, List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions)230     public MethodSignature(
231             Visibility visibility, TypeMirror returnType, String name,
232             List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions) {
233         mVisibility = visibility;
234         mReturnType = returnType.toString();
235         mName = name;
236         mParameterTypes = ImmutableList.copyOf(parameterTypes.stream()
237                 .map(TypeMirror::toString)
238                 .collect(Collectors.toList()));
239         mExceptions = ImmutableSet.copyOf(exceptions.stream().map(TypeMirror::toString).collect(
240                 Collectors.toSet()));
241     }
242 
getReturnType()243     public String getReturnType() {
244         return mReturnType;
245     }
246 
getName()247     public String getName() {
248         return mName;
249     }
250 
251     @Override
equals(Object o)252     public boolean equals(Object o) {
253         if (this == o) return true;
254         if (!(o instanceof MethodSignature)) return false;
255         MethodSignature that = (MethodSignature) o;
256         return mVisibility == that.mVisibility && mReturnType.equals(that.mReturnType)
257                 && mName.equals(
258                 that.mName) && mParameterTypes.equals(that.mParameterTypes) && mExceptions.equals(
259                 that.mExceptions);
260     }
261 
262     @Override
hashCode()263     public int hashCode() {
264         return Objects.hash(mVisibility, mReturnType, mName, mParameterTypes, mExceptions);
265     }
266 
267     @Override
toString()268     public String toString() {
269         return "MethodSignature{"
270                 + "mVisibility="
271                 + mVisibility
272                 + ", mReturnType='" + mReturnType + '\''
273                 + ", mName='" + mName + '\''
274                 + ", mParameterTypes=" + mParameterTypes
275                 + ", mExceptions=" + mExceptions
276                 + '}';
277     }
278 }
279