/*
* Copyright 2017-present Facebook, Inc.
*
* 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.facebook.buck.jvm.java.abi;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.NoType;
import javax.lang.model.type.PrimitiveType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.type.TypeVariable;
import javax.lang.model.type.TypeVisitor;
import javax.lang.model.type.WildcardType;
import javax.lang.model.util.SimpleElementVisitor8;
import javax.lang.model.util.SimpleTypeVisitor8;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;
/** Computes type signatures for {@link Element}s that require them. */
class SignatureFactory {
private final DescriptorFactory descriptorFactory;
/**
* Adapts a SignatureVisitor to the ElementVisitor contract, so we can drive it from an Element.
*/
private final ElementVisitor<Void, SignatureVisitor> elementVisitorAdapter =
new SimpleElementVisitor8<Void, SignatureVisitor>() {
@Override
protected Void defaultAction(Element e, SignatureVisitor signatureVisitor) {
throw new IllegalArgumentException(
String.format("Unexpected element kind: %s", e.getKind()));
}
@Override
public Void visitType(TypeElement element, SignatureVisitor visitor) {
if (!signatureRequired(element)) {
return null;
}
for (TypeParameterElement typeParameterElement : element.getTypeParameters()) {
typeParameterElement.accept(this, visitor);
}
TypeMirror superclass = element.getSuperclass();
if (superclass.getKind() != TypeKind.NONE) {
superclass.accept(typeVisitorAdapter, visitor.visitSuperclass());
} else {
// Interface type; implicit superclass of Object
SignatureVisitor superclassVisitor = visitor.visitSuperclass();
superclassVisitor.visitClassType("java/lang/Object");
superclassVisitor.visitEnd();
}
for (TypeMirror interfaceType : element.getInterfaces()) {
interfaceType.accept(typeVisitorAdapter, visitor.visitInterface());
}
return null;
}
@Override
public Void visitTypeParameter(TypeParameterElement element, SignatureVisitor visitor) {
visitor.visitFormalTypeParameter(element.getSimpleName().toString());
for (TypeMirror boundType : element.getBounds()) {
if (isClassType(boundType)) {
boundType.accept(typeVisitorAdapter, visitor.visitClassBound());
} else {
boundType.accept(typeVisitorAdapter, visitor.visitInterfaceBound());
}
}
return null;
}
private boolean isClassType(TypeMirror type) {
if (type.getKind() == TypeKind.TYPEVAR) {
// For the purposes of signatures, typevar bounds are considered class bounds
return true;
}
if (type.getKind() != TypeKind.DECLARED) {
return false;
}
DeclaredType declaredType = (DeclaredType) type;
return declaredType.asElement().getKind().isClass();
}
@Override
public Void visitExecutable(ExecutableElement element, SignatureVisitor visitor) {
if (!signatureRequired(element)) {
return null;
}
for (TypeParameterElement typeParameterElement : element.getTypeParameters()) {
typeParameterElement.accept(this, visitor);
}
for (VariableElement parameter : element.getParameters()) {
parameter.asType().accept(typeVisitorAdapter, visitor.visitParameterType());
}
element.getReturnType().accept(typeVisitorAdapter, visitor.visitReturnType());
if (throwsATypeVar(element)) {
for (TypeMirror thrownType : element.getThrownTypes()) {
thrownType.accept(typeVisitorAdapter, visitor.visitExceptionType());
}
}
return null;
}
@Override
public Void visitVariable(VariableElement element, SignatureVisitor visitor) {
if (!signatureRequired(element)) {
return null;
}
element.asType().accept(typeVisitorAdapter, visitor);
return null;
}
};
/**
* Adapts a SignatureVisitor to the TypeVisitor contract, so we can drive it from a TypeMirror.
*/
private final TypeVisitor<Void, SignatureVisitor> typeVisitorAdapter =
new SimpleTypeVisitor8<Void, SignatureVisitor>() {
@Override
protected Void defaultAction(TypeMirror e, SignatureVisitor signatureVisitor) {
throw new IllegalArgumentException(
String.format("Unexpected type kind: %s", e.getKind()));
}
@Override
public Void visitNoType(NoType t, SignatureVisitor visitor) {
if (t.getKind() == TypeKind.VOID) {
visitor.visitBaseType(descriptorFactory.getDescriptor(t).charAt(0));
return null;
}
return defaultAction(t, visitor);
}
@Override
public Void visitPrimitive(PrimitiveType t, SignatureVisitor visitor) {
visitor.visitBaseType(descriptorFactory.getDescriptor(t).charAt(0));
return null;
}
@Override
public Void visitTypeVariable(TypeVariable t, SignatureVisitor visitor) {
visitor.visitTypeVariable(t.asElement().getSimpleName().toString());
return null;
}
@Override
public Void visitArray(ArrayType t, SignatureVisitor visitor) {
t.getComponentType().accept(this, visitor.visitArrayType());
return null;
}
@Override
public Void visitWildcard(WildcardType t, SignatureVisitor visitor) {
TypeMirror bound = t.getExtendsBound();
if (bound != null) {
bound.accept(this, visitor.visitTypeArgument(SignatureVisitor.EXTENDS));
return null;
}
bound = t.getSuperBound();
if (bound != null) {
bound.accept(this, visitor.visitTypeArgument(SignatureVisitor.SUPER));
return null;
}
visitor.visitTypeArgument();
return null;
}
@Override
public Void visitDeclared(DeclaredType t, SignatureVisitor visitor) {
List<DeclaredType> enclosingTypes = findEnclosingTypes(t);
// Signatures are weird with inner classes. They use $ as the separator until you
// encounter a class with type arguments, then . as the separator thereafter.
//
// What this means for visiting a class type with a SignatureVisitor is that we don't
// start the visiting (by calling visitClassType) at the top-level class; we start at the
// outermost class with type arguments. Then we visit the type arguments, and then
// every inner class from there to the type for which we're generating a signature.
boolean calledVisitClassType = false;
int numTypes = enclosingTypes.size();
for (int i = 0; i < numTypes; i++) {
DeclaredType type = enclosingTypes.get(i);
List<? extends TypeMirror> typeArguments = type.getTypeArguments();
if (calledVisitClassType) {
visitor.visitInnerClassType(type.asElement().getSimpleName().toString());
} else if (!typeArguments.isEmpty() || i == numTypes - 1) {
visitor.visitClassType(descriptorFactory.getInternalName(type));
calledVisitClassType = true;
} else {
// Keep looking for the outermost enclosing generic type
continue;
}
for (TypeMirror typeArgument : typeArguments) {
typeArgument.accept(this, visitor.visitTypeArgument(SignatureVisitor.INSTANCEOF));
}
}
visitor.visitEnd();
return null;
}
private List<DeclaredType> findEnclosingTypes(DeclaredType t) {
List<DeclaredType> result = new ArrayList<>();
TypeMirror walker = t;
while (walker.getKind() != TypeKind.NONE) {
result.add((DeclaredType) walker);
walker = ((DeclaredType) walker).getEnclosingType();
}
Collections.reverse(result);
return result;
}
};
public SignatureFactory(DescriptorFactory descriptorFactory) {
this.descriptorFactory = descriptorFactory;
}
/**
* Returns the type signature of the given element. If none is required by the VM spec, returns
* null.
*/
@Nullable
public String getSignature(Element element) {
SignatureWriter writer = new SignatureWriter();
element.accept(elementVisitorAdapter, writer);
String result = writer.toString();
return result.isEmpty() ? null : result;
}
/**
* Returns true if the JVM spec requires a signature to be emitted for this type. See JVMS8
* 4.7.9.1
*/
private static boolean signatureRequired(TypeElement element) {
if (!element.getTypeParameters().isEmpty()) {
return true;
}
if (usesGenerics(element.getSuperclass())) {
return true;
}
for (TypeMirror interfaceType : element.getInterfaces()) {
if (usesGenerics(interfaceType)) {
return true;
}
}
return false;
}
/**
* Returns true if the JVM spec requires a signature to be emitted for this method. See JVMS8
* 4.7.9.1
*/
private static boolean signatureRequired(ExecutableElement element) {
if (!element.getTypeParameters().isEmpty()) {
return true;
}
if (usesGenerics(element.getReturnType())) {
return true;
}
for (VariableElement parameter : element.getParameters()) {
if (usesGenerics(parameter.asType())) {
return true;
}
}
if (throwsATypeVar(element)) {
return true;
}
return false;
}
private static boolean throwsATypeVar(ExecutableElement element) {
for (TypeMirror thrownType : element.getThrownTypes()) {
if (thrownType.getKind() == TypeKind.TYPEVAR) {
return true;
}
}
return false;
}
/**
* Returns true if the JVM spec requires a signature to be emitted for this field. See JVMS8
* 4.7.9.1
*/
private static boolean signatureRequired(VariableElement element) {
return usesGenerics(element.asType());
}
private static boolean usesGenerics(TypeMirror type) {
return type.accept(
new SimpleTypeVisitor8<Boolean, Void>() {
@Override
protected Boolean defaultAction(TypeMirror e, Void aVoid) {
throw new IllegalArgumentException(
String.format("Unexpected type kind: %s", e.getKind()));
}
@Override
public Boolean visitPrimitive(PrimitiveType t, Void aVoid) {
return false;
}
@Override
public Boolean visitTypeVariable(TypeVariable t, Void aVoid) {
return true;
}
@Override
public Boolean visitNoType(NoType t, Void aVoid) {
return false;
}
@Override
public Boolean visitArray(ArrayType t, Void aVoid) {
return usesGenerics(t.getComponentType());
}
@Override
public Boolean visitWildcard(WildcardType t, Void aVoid) {
return true;
}
@Override
public Boolean visitDeclared(DeclaredType t, Void aVoid) {
return !t.getTypeArguments().isEmpty() || usesGenerics(t.getEnclosingType());
}
},
null);
}
}