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 com.google.common.collect.ImmutableList;
20 import com.google.common.collect.ImmutableSet;
21 
22 import java.util.Arrays;
23 import java.util.HashSet;
24 import java.util.List;
25 import java.util.Objects;
26 import java.util.Set;
27 import java.util.stream.Collectors;
28 
29 import javax.lang.model.element.Element;
30 import javax.lang.model.element.ExecutableElement;
31 import javax.lang.model.element.Modifier;
32 import javax.lang.model.element.TypeElement;
33 import javax.lang.model.type.DeclaredType;
34 import javax.lang.model.type.TypeKind;
35 import javax.lang.model.type.TypeMirror;
36 import javax.lang.model.util.Elements;
37 import javax.lang.model.util.Types;
38 
39 /**
40  * Represents a minimal representation of a method for comparison purposes
41  */
42 public final class MethodSignature {
43 
44     /** Create a {@link MethodSignature} for the given {@link ExecutableElement}. */
forMethod(ExecutableElement method, Elements elements)45     public static MethodSignature forMethod(ExecutableElement method, Elements elements) {
46         List<TypeMirror> parameters = method.getParameters().stream()
47                 .map(Element::asType)
48                 .map(m -> rawType(m, elements))
49                 .collect(Collectors.toList());
50 
51         Set<TypeMirror> exceptions = method.getThrownTypes()
52                 .stream().map(m -> rawType(m, elements))
53                 .collect(Collectors.toSet());
54 
55         return new MethodSignature(Visibility.ofMethod(method),
56                 rawType(method.getReturnType(), elements),
57                 method.getSimpleName().toString(), parameters, exceptions);
58     }
59 
rawType(TypeMirror type, Elements elements)60     private static TypeMirror rawType(TypeMirror type, Elements elements) {
61         if (type instanceof DeclaredType) {
62             DeclaredType t = (DeclaredType) type;
63             if (!t.getTypeArguments().isEmpty()) {
64                 type = elements.getTypeElement(t.toString().split("<", 2)[0]).asType();
65             }
66         }
67         return type;
68     }
69 
70     /**
71      * Create a {@link MethodSignature} for the given string from an API file.
72      */
forApiString(String string, Types types, Elements elements)73     public static MethodSignature forApiString(String string, Types types, Elements elements) {
74         // Strip annotations
75         string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
76         string = string.replaceAll("@.+? ", "");
77 
78         String[] parts = string.split(" ", 2);
79         Visibility visibility;
80         try {
81             visibility = Visibility.valueOf(parts[0].toUpperCase());
82         } catch (IllegalArgumentException e) {
83             throw new IllegalStateException("Error finding visibility in string " + string);
84         }
85         string = parts[1];
86         parts = string.split(" ", 2);
87 
88         TypeMirror returnType;
89         while (parts[0].equals("abstract") || parts[0].equals("final")
90                 || parts[0].equals("static")) {
91             // These don't affect the signature in ways we care about
92             string = parts[1];
93             parts = string.split(" ", 2);
94         }
95 
96         returnType = typeForString(parts[0], types, elements);
97 
98         string = parts[1];
99         parts = string.split("\\(", 2);
100         String methodName = parts[0];
101         string = parts[1];
102         parts = string.split("\\)", 2);
103         // Remove generic types as we don't need to care about them at this point
104         String parametersString = parts[0].replaceAll("<.*>", "");
105         // Remove varargs
106         parametersString = parametersString.replaceAll("\\.\\.\\.", "");
107         List<TypeMirror> parameters;
108         try {
109             parameters = Arrays.stream(parametersString.split(","))
110                     .map(String::trim)
111                     .filter(t -> !t.isEmpty())
112                     .map(t -> typeForString(t, types, elements))
113                     .collect(Collectors.toList());
114         } catch (IllegalStateException e) {
115             throw new IllegalStateException("Error parsing types from string " + parametersString);
116         }
117         string = parts[1];
118         Set<TypeMirror> exceptions = new HashSet<>();
119         if (string.contains("throws")) {
120             exceptions = Arrays.stream(string.split("throws ", 2)[1].split(","))
121                     .map(t -> t.trim())
122                     .filter(t -> !t.isEmpty())
123                     .map(t -> typeForString(t, types, elements))
124                     .collect(Collectors.toSet());
125         }
126 
127         return new MethodSignature(visibility, returnType, methodName, parameters, exceptions);
128     }
129 
typeForString(String typeName, Types types, Elements elements)130     private static TypeMirror typeForString(String typeName, Types types, Elements elements) {
131         if (typeName.equals("void")) {
132             return types.getNoType(TypeKind.VOID);
133         }
134 
135         if (typeName.contains("<")) {
136             // Because of type erasure we can just drop the type argument
137             return typeForString(typeName.split("<", 2)[0], types, elements);
138         }
139 
140         if (typeName.endsWith("[]")) {
141             return types.getArrayType(
142                     typeForString(typeName.substring(0, typeName.length() - 2), types, elements));
143         }
144 
145         try {
146             return types.getPrimitiveType(TypeKind.valueOf(typeName.toUpperCase()));
147         } catch (IllegalArgumentException e) {
148             // Not a primitive
149         }
150 
151         TypeElement element = elements.getTypeElement(typeName);
152         if (element == null) {
153             // It could be java.lang
154             element = elements.getTypeElement("java.lang." + typeName);
155         }
156 
157         if (element == null) {
158             throw new IllegalStateException("Unknown type: " + typeName);
159         }
160 
161         return element.asType();
162     }
163 
164     enum Visibility {
165         PUBLIC,
166         PROTECTED;
167 
ofMethod(ExecutableElement method)168         static Visibility ofMethod(ExecutableElement method) {
169             if (method.getModifiers().contains(Modifier.PUBLIC)) {
170                 return PUBLIC;
171             } else if (method.getModifiers().contains(Modifier.PROTECTED)) {
172                 return PROTECTED;
173             }
174 
175             throw new IllegalArgumentException("Only public and protected are visible in APIs");
176         }
177     }
178 
179     private final Visibility mVisibility;
180     private final String mReturnType;
181     private final String mName;
182     private final ImmutableList<String> mParameterTypes;
183     private final ImmutableSet<String> mExceptions;
MethodSignature( Visibility visibility, TypeMirror returnType, String name, List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions)184     public MethodSignature(
185             Visibility visibility, TypeMirror returnType, String name,
186             List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions) {
187         mVisibility = visibility;
188         mReturnType = returnType.toString();
189         mName = name;
190         mParameterTypes = ImmutableList.copyOf(parameterTypes.stream()
191                 .map(TypeMirror::toString)
192                 .collect(Collectors.toList()));
193         mExceptions = ImmutableSet.copyOf(exceptions.stream().map(TypeMirror::toString).collect(
194                 Collectors.toSet()));
195     }
196 
197     @Override
equals(Object o)198     public boolean equals(Object o) {
199         if (this == o) return true;
200         if (!(o instanceof MethodSignature)) return false;
201         MethodSignature that = (MethodSignature) o;
202         return mVisibility == that.mVisibility && mReturnType.equals(that.mReturnType)
203                 && mName.equals(
204                 that.mName) && mParameterTypes.equals(that.mParameterTypes) && mExceptions.equals(
205                 that.mExceptions);
206     }
207 
208     @Override
hashCode()209     public int hashCode() {
210         return Objects.hash(mVisibility, mReturnType, mName, mParameterTypes, mExceptions);
211     }
212 
213     @Override
toString()214     public String toString() {
215         return "MethodSignature{"
216                 + "mVisibility="
217                 + mVisibility
218                 + ", mReturnType='" + mReturnType + '\''
219                 + ", mName='" + mName + '\''
220                 + ", mParameterTypes=" + mParameterTypes
221                 + ", mExceptions=" + mExceptions
222                 + '}';
223     }
224 }
225