blob: 0735b68155f0ef61be5ebae70462d59657f89411 [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.bedstead.remoteframeworkclasses.processor;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.lang.model.element.Element;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
/**
* Represents a minimal representation of a method for comparison purposes
*/
public final class MethodSignature {
/** Create a {@link MethodSignature} for the given {@link ExecutableElement}. */
public static MethodSignature forMethod(ExecutableElement method, Elements elements) {
List<TypeMirror> parameters = method.getParameters().stream()
.map(Element::asType)
.map(m -> rawType(m, elements))
.collect(Collectors.toList());
Set<TypeMirror> exceptions = method.getThrownTypes()
.stream().map(m -> rawType(m, elements))
.collect(Collectors.toSet());
return new MethodSignature(Visibility.ofMethod(method),
rawType(method.getReturnType(), elements),
method.getSimpleName().toString(), parameters, exceptions);
}
private static TypeMirror rawType(TypeMirror type, Elements elements) {
if (type instanceof DeclaredType) {
DeclaredType t = (DeclaredType) type;
if (!t.getTypeArguments().isEmpty()) {
type = elements.getTypeElement(t.toString().split("<", 2)[0]).asType();
}
}
return type;
}
/**
* Create a {@link MethodSignature} for the given string from an API file.
*/
public static /* @Nullable */ MethodSignature forApiString(
String string, Types types, Elements elements) {
// Strip annotations
string = string.replaceAll("@\\w+?\\(.+?\\) ", "");
string = string.replaceAll("@.+? ", "");
String[] parts = string.split(" ", 2);
Visibility visibility;
try {
visibility = Visibility.valueOf(parts[0].toUpperCase());
} catch (IllegalArgumentException e) {
throw new IllegalStateException("Error finding visibility in string " + string);
}
string = parts[1];
parts = string.split(" ", 2);
TypeMirror returnType;
while (parts[0].equals("abstract") || parts[0].equals("final")
|| parts[0].equals("static")) {
// These don't affect the signature in ways we care about
string = parts[1];
parts = string.split(" ", 2);
}
if (string.startsWith("<")) {
// This includes type arguments, for now we ignore this method
return null;
}
returnType = typeForString(parts[0], types, elements);
string = parts[1];
parts = string.split("\\(", 2);
String methodName = parts[0];
string = parts[1];
parts = string.split("\\)", 2);
// Remove generic types as we don't need to care about them at this point
String parametersString = parts[0].replaceAll("<.*>", "");
// Remove varargs
parametersString = parametersString.replaceAll("\\.\\.\\.", "");
List<TypeMirror> parameters;
try {
parameters = Arrays.stream(parametersString.split(","))
.map(String::trim)
.filter(t -> !t.isEmpty())
.map(t -> typeForString(t, types, elements))
.collect(Collectors.toList());
} catch (IllegalStateException e) {
throw new IllegalStateException("Error parsing types from string " + parametersString);
}
string = parts[1];
Set<TypeMirror> exceptions = new HashSet<>();
if (string.contains("throws")) {
exceptions = Arrays.stream(string.split("throws ", 2)[1].split(","))
.map(t -> t.trim())
.filter(t -> !t.isEmpty())
.map(t -> typeForString(t, types, elements))
.collect(Collectors.toSet());
}
return new MethodSignature(visibility, returnType, methodName, parameters, exceptions);
}
private static TypeMirror typeForString(String typeName, Types types, Elements elements) {
if (typeName.equals("void")) {
return types.getNoType(TypeKind.VOID);
}
if (typeName.contains("<")) {
// Because of type erasure we can just drop the type argument
return typeForString(typeName.split("<", 2)[0], types, elements);
}
if (typeName.endsWith("[]")) {
return types.getArrayType(
typeForString(typeName.substring(0, typeName.length() - 2), types, elements));
}
try {
return types.getPrimitiveType(TypeKind.valueOf(typeName.toUpperCase()));
} catch (IllegalArgumentException e) {
// Not a primitive
}
TypeElement element = elements.getTypeElement(typeName);
if (element == null) {
// It could be java.lang
element = elements.getTypeElement("java.lang." + typeName);
}
if (element == null) {
throw new IllegalStateException("Unknown type: " + typeName);
}
return element.asType();
}
enum Visibility {
PUBLIC,
PROTECTED;
static Visibility ofMethod(ExecutableElement method) {
if (method.getModifiers().contains(Modifier.PUBLIC)) {
return PUBLIC;
} else if (method.getModifiers().contains(Modifier.PROTECTED)) {
return PROTECTED;
}
throw new IllegalArgumentException("Only public and protected are visible in APIs");
}
}
private final Visibility mVisibility;
private final String mReturnType;
private final String mName;
private final ImmutableList<String> mParameterTypes;
private final ImmutableSet<String> mExceptions;
public MethodSignature(
Visibility visibility, TypeMirror returnType, String name,
List<TypeMirror> parameterTypes, Set<TypeMirror> exceptions) {
mVisibility = visibility;
mReturnType = returnType.toString();
mName = name;
mParameterTypes = ImmutableList.copyOf(parameterTypes.stream()
.map(TypeMirror::toString)
.collect(Collectors.toList()));
mExceptions = ImmutableSet.copyOf(exceptions.stream().map(TypeMirror::toString).collect(
Collectors.toSet()));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof MethodSignature)) return false;
MethodSignature that = (MethodSignature) o;
return mVisibility == that.mVisibility && mReturnType.equals(that.mReturnType)
&& mName.equals(
that.mName) && mParameterTypes.equals(that.mParameterTypes) && mExceptions.equals(
that.mExceptions);
}
@Override
public int hashCode() {
return Objects.hash(mVisibility, mReturnType, mName, mParameterTypes, mExceptions);
}
@Override
public String toString() {
return "MethodSignature{"
+ "mVisibility="
+ mVisibility
+ ", mReturnType='" + mReturnType + '\''
+ ", mName='" + mName + '\''
+ ", mParameterTypes=" + mParameterTypes
+ ", mExceptions=" + mExceptions
+ '}';
}
}