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