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