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