com.facebook.buck.jvm.java.abi.SignatureFactory.java Source code

Java tutorial

Introduction

Here is the source code for com.facebook.buck.jvm.java.abi.SignatureFactory.java

Source

/*
 * 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.ErrorType;
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 visitError(ErrorType t, SignatureVisitor visitor) {
            // We don't really know, but if there's an error type the compilation is going to fail
            // anyway, so just pretend it's Object.
            visitor.visitClassType("java/lang/Object");
            visitor.visitEnd();
            return null;
        }

        @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 visitError(ErrorType t, Void aVoid) {
                // We don't really know, but if there's an ErrorType it means compilation is going to
                // fail anyway so it doesn't matter.
                return false;
            }

            @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);
    }
}