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.ALLOWLISTED_TEST_CLASSES;
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         // Strip annotations
79         string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
80         string = string.replaceAll("@.+? ", "");
81 
82         String[] parts = string.split(" ", 2);
83         Visibility visibility;
84         try {
85             visibility = Visibility.valueOf(parts[0].toUpperCase());
86         } catch (IllegalArgumentException e) {
87             throw new IllegalStateException("Error finding visibility in string " + string);
88         }
89         string = parts[1];
90         parts = string.split(" ", 2);
91 
92         TypeMirror returnType;
93         while (parts[0].equals("abstract") || parts[0].equals("final")
94                 || parts[0].equals("static")) {
95             // These don't affect the signature in ways we care about
96             string = parts[1];
97             parts = string.split(" ", 2);
98         }
99 
100         if (string.startsWith("<")) {
101             // This includes type arguments, for now we ignore this method
102             return null;
103         }
104 
105         returnType = typeForString(parts[0], types, elements);
106 
107         string = parts[1];
108         parts = string.split("\\(", 2);
109         String methodName = parts[0];
110         string = parts[1];
111         parts = string.split("\\)", 2);
112         // Remove generic types as we don't need to care about them at this point
113         String parametersString = parts[0].replaceAll("<.*>", "");
114         // Remove varargs
115         parametersString = parametersString.replaceAll("\\.\\.\\.", "");
116         List<TypeMirror> parameters;
117         try {
118             parameters = Arrays.stream(parametersString.split(","))
119                     .map(String::trim)
120                     .filter(t -> !t.isEmpty())
121                     .map(t -> typeForString(t, types, elements))
122                     .collect(Collectors.toList());
123         } catch (IllegalStateException e) {
124             throw new IllegalStateException("Error parsing types from string " + parametersString);
125         }
126         string = parts[1];
127         Set<TypeMirror> exceptions = new HashSet<>();
128         if (string.contains("throws")) {
129             exceptions = Arrays.stream(string.split("throws ", 2)[1].split(","))
130                     .map(t -> t.trim())
131                     .filter(t -> !t.isEmpty())
132                     .map(t -> typeForString(t, types, elements))
133                     .collect(Collectors.toSet());
134         }
135 
136         return new MethodSignature(visibility, returnType, methodName, parameters, exceptions);
137     }
138 
typeForString(String typeName, Types types, Elements elements)139     private static TypeMirror typeForString(String typeName, Types types, Elements elements) {
140         if (typeName.equals("void")) {
141             return types.getNoType(TypeKind.VOID);
142         }
143 
144         if (isTypeAnnotatedWithTestApi(typeName)) {
145             // Use the proxy type instead
146             typeName = proxyType(typeName);
147         }
148 
149         if (typeName.contains("<")) {
150             // Because of type erasure we can just drop the type argument
151             return typeForString(typeName.split("<", 2)[0], types, elements);
152         }
153 
154         if (typeName.endsWith("[]")) {
155             return types.getArrayType(
156                     typeForString(typeName.substring(0, typeName.length() - 2), types, elements));
157         }
158 
159         try {
160             return types.getPrimitiveType(TypeKind.valueOf(typeName.toUpperCase()));
161         } catch (IllegalArgumentException e) {
162             // Not a primitive
163         }
164 
165         TypeElement element = elements.getTypeElement(typeName);
166         if (element == null) {
167             // It could be java.lang
168             element = elements.getTypeElement("java.lang." + typeName);
169         }
170 
171         if (element == null) {
172             throw new IllegalStateException("Unknown type: " + typeName);
173         }
174 
175         return element.asType();
176     }
177 
isTypeAnnotatedWithTestApi(String typeName)178     private static boolean isTypeAnnotatedWithTestApi(String typeName) {
179         return ALLOWLISTED_TEST_CLASSES.contains(typeName);
180     }
181 
proxyType(String typeName)182     private static String proxyType(String typeName) {
183         return TEST_APIS_REFLECTION_PACKAGE + "." +
184                 typeName.substring(typeName.lastIndexOf(".") + 1) + "Proxy";
185     }
186 
187     enum Visibility {
188         PUBLIC,
189         PROTECTED;
190 
ofMethod(ExecutableElement method)191         static Visibility ofMethod(ExecutableElement method) {
192             if (method.getModifiers().contains(Modifier.PUBLIC)) {
193                 return PUBLIC;
194             } else if (method.getModifiers().contains(Modifier.PROTECTED)) {
195                 return PROTECTED;
196             }
197 
198             throw new IllegalArgumentException("Only public and protected are visible in APIs");
199         }
200     }
201 
202     private final Visibility mVisibility;
203     private final String mReturnType;
204     private final String mName;
205     public final ImmutableList<String> mParameterTypes;
206     public final ImmutableSet<String> mExceptions;
MethodSignature( Visibility visibility, TypeMirror returnType, String name, List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions)207     public MethodSignature(
208             Visibility visibility, TypeMirror returnType, String name,
209             List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions) {
210         mVisibility = visibility;
211         mReturnType = returnType.toString();
212         mName = name;
213         mParameterTypes = ImmutableList.copyOf(parameterTypes.stream()
214                 .map(TypeMirror::toString)
215                 .collect(Collectors.toList()));
216         mExceptions = ImmutableSet.copyOf(exceptions.stream().map(TypeMirror::toString).collect(
217                 Collectors.toSet()));
218     }
219 
getReturnType()220     public String getReturnType() {
221         return mReturnType;
222     }
223 
getName()224     public String getName() {
225         return mName;
226     }
227 
228     @Override
equals(Object o)229     public boolean equals(Object o) {
230         if (this == o) return true;
231         if (!(o instanceof MethodSignature)) return false;
232         MethodSignature that = (MethodSignature) o;
233         return mVisibility == that.mVisibility && mReturnType.equals(that.mReturnType)
234                 && mName.equals(
235                 that.mName) && mParameterTypes.equals(that.mParameterTypes) && mExceptions.equals(
236                 that.mExceptions);
237     }
238 
239     @Override
hashCode()240     public int hashCode() {
241         return Objects.hash(mVisibility, mReturnType, mName, mParameterTypes, mExceptions);
242     }
243 
244     @Override
toString()245     public String toString() {
246         return "MethodSignature{"
247                 + "mVisibility="
248                 + mVisibility
249                 + ", mReturnType='" + mReturnType + '\''
250                 + ", mName='" + mName + '\''
251                 + ", mParameterTypes=" + mParameterTypes
252                 + ", mExceptions=" + mExceptions
253                 + '}';
254     }
255 }
256