TypeElementImpl.java

package net.florianschoppmann.java.reflect;

import javax.annotation.Nullable;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ElementVisitor;
import javax.lang.model.element.Name;
import javax.lang.model.element.NestingKind;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

final class TypeElementImpl extends ElementImpl implements TypeElement, ReflectionParameterizable {
    private final Class<?> clazz;
    private final ImmutableList<TypeParameterElementImpl> typeParameters;
    @Nullable private ReflectionElement enclosingElement;
    @Nullable private ReflectionTypeMirror superClass;
    @Nullable private List<ReflectionTypeMirror> interfaces;
    @Nullable private List<ElementImpl> enclosedElements;

    /**
     * Cache the type returned by {@link #asType()}.
     *
     * <p>Similarly to {@link String#hashCode()}, caching is not synchronized. This means that different threads may
     * (at least theoretically) see different values for this field. However, this is not a problem because all such
     * values would compare equal. Moreover, ยง17.7 JLS specifies that "Writes to and reads of references are always
     * atomic, regardless of whether they are implemented as 32-bit or 64-bit values". Hence, even if caches were
     * updated, every access to this field would yield a well-defined result.
     */
    @Nullable private DeclaredTypeImpl type;

    TypeElementImpl(Class<?> clazz) {
        this.clazz = Objects.requireNonNull(clazz);

        List<TypeParameterElementImpl> newTypeParameters = new ArrayList<>();
        for (TypeVariable<?> parameter: clazz.getTypeParameters()) {
            newTypeParameters.add(new TypeParameterElementImpl(parameter, this));
        }
        typeParameters = ImmutableList.copyOf(newTypeParameters);
    }

    @Override
    public boolean equals(@Nullable Object otherObject) {
        if (this == otherObject) {
            return true;
        } else if (otherObject == null || getClass() != otherObject.getClass()) {
            return false;
        }

        return clazz.equals(((TypeElementImpl) otherObject).clazz);
    }

    @Override
    public int hashCode() {
        return clazz.hashCode();
    }

    @Override
    public String toString() {
        return clazz.toString();
    }

    @Override
    public <R, P> R accept(ElementVisitor<R, P> visitor, @Nullable P parameter) {
        return visitor.visitType(this, parameter);
    }

    @Override
    public List<ElementImpl> getEnclosedElements() {
        requireFinished();
        assert enclosedElements != null : "must be non-null when finished";
        return enclosedElements;
    }

    @Override
    public NestingKind getNestingKind() {
        throw new UnsupportedOperationException(String.format(
            "Nesting kind not currently supported by %s.", ReflectionTypes.class
        ));
    }

    @Override
    public NameImpl getQualifiedName() {
        return new NameImpl(clazz.getCanonicalName());
    }

    @Override
    public Name getSimpleName() {
        return new NameImpl(clazz.getSimpleName());
    }

    @Override
    public TypeMirror getSuperclass() {
        requireFinished();
        assert superClass != null : "must be non-null when finished";
        return superClass;
    }

    @Override
    public List<? extends TypeMirror> getInterfaces() {
        requireFinished();
        assert interfaces != null : "must be non-null when finished";
        return interfaces;
    }

    @Override
    public List<TypeParameterElementImpl> getTypeParameters() {
        return typeParameters;
    }

    @Override
    public ReflectionElement getEnclosingElement() {
        requireFinished();

        if (enclosingElement == null) {
            throw new UnsupportedOperationException("getEnclosingElement() not supported for top-level classes.");
        } else {
            return enclosingElement;
        }
    }

    @Override
    public DeclaredTypeImpl asType() {
        requireFinished();

        @Nullable DeclaredTypeImpl localType = type;
        if (localType == null) {
            List<TypeVariableImpl> prototypicalTypeArguments = new ArrayList<>(typeParameters.size());
            for (TypeParameterElementImpl typeParameter: typeParameters) {
                prototypicalTypeArguments.add(typeParameter.asType());
            }

            ReflectionTypeMirror enclosingType = enclosingElement == null
                ? NoTypeImpl.NONE
                : enclosingElement.asType();
            localType = new DeclaredTypeImpl(enclosingType, this, prototypicalTypeArguments);
            type = localType;
        }
        return localType;
    }

    @Override
    public ElementKind getKind() {
        if (clazz.isEnum()) {
            return ElementKind.ENUM;
        } else if (clazz.isAnnotation()) {
            return ElementKind.ANNOTATION_TYPE;
        } else if (clazz.isInterface()) {
            return ElementKind.INTERFACE;
        } else {
            return ElementKind.CLASS;
        }
    }

    @Override
    protected void finishDerivedFromElement(MirrorContext mirrorContext) {
        @Nullable Class<?> enclosingClass = clazz.getEnclosingClass();
        enclosingElement = enclosingClass == null
            ? null
            : mirrorContext.typeDeclaration(enclosingClass);

        Class<?>[] declaredClasses = clazz.getDeclaredClasses();
        List<ElementImpl> newEnclosedElements = new ArrayList<>(typeParameters.size() + declaredClasses.length);
        newEnclosedElements.addAll(typeParameters);
        for (Class<?> declaredClass: declaredClasses) {
            newEnclosedElements.add(mirrorContext.typeDeclaration(declaredClass));
        }
        enclosedElements = ImmutableList.copyOf(newEnclosedElements);

        @Nullable Type genericSuperClass = clazz.getGenericSuperclass();
        superClass = genericSuperClass == null
            ? NoTypeImpl.NONE
            : mirrorContext.mirror(genericSuperClass);
        interfaces = mirrorContext.mirror(clazz.getGenericInterfaces());
        for (TypeParameterElementImpl typeParameter: typeParameters) {
            typeParameter.finish(mirrorContext);
        }

        // Field 'type' is lazily initialized in order to break a dependency chain: Constructing type requires
        // enclosingElement.asType(), which at this point may not yet be available.
    }
}