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