Package org.mapstruct.ap.model.common

Source Code of org.mapstruct.ap.model.common.Type

/**
*  Copyright 2012-2014 Gunnar Morling (http://www.gunnarmorling.de/)
*  and/or other contributors as indicated by the @authors tag. See the
*  copyright.txt file in the distribution for a full listing of all
*  contributors.
*
*  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 org.mapstruct.ap.model.common;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;

import org.mapstruct.ap.util.Executables;
import org.mapstruct.ap.util.Filters;
import org.mapstruct.ap.util.Nouns;
import org.mapstruct.ap.util.SpecificCompilerWorkarounds;

/**
* Represents (a reference to) the type of a bean property, parameter etc. Types are managed per generated source file.
* Each type corresponds to a {@link TypeMirror}, i.e. there are different instances for e.g. {@code Set<String>} and
* {@code Set<Integer>}.
* <p>
* Allows for a unified handling of declared and primitive types and usage within templates. Instances are obtained
* through {@link TypeFactory}.
*
* @author Gunnar Morling
*/
public class Type extends ModelElement implements Comparable<Type> {

    private final Types typeUtils;
    private final Elements elementUtils;

    private final TypeMirror typeMirror;
    private final TypeElement typeElement;
    private final List<Type> typeParameters;

    private final Type implementationType;

    private final String packageName;
    private final String name;
    private final String qualifiedName;

    private final boolean isInterface;
    private final boolean isEnumType;
    private final boolean isIterableType;
    private final boolean isCollectionType;
    private final boolean isMapType;
    private final boolean isImported;
    private final boolean isVoid;

    private final List<String> enumConstants;

    private List<ExecutableElement> getters = null;
    private List<ExecutableElement> setters = null;
    private List<ExecutableElement> adders = null;
    private List<ExecutableElement> alternativeTargetAccessors = null;

    //CHECKSTYLE:OFF
    public Type(Types typeUtils, Elements elementUtils, TypeMirror typeMirror, TypeElement typeElement,
                List<Type> typeParameters, Type implementationType, String packageName, String name,
                String qualifiedName, boolean isInterface, boolean isEnumType, boolean isIterableType,
                boolean isCollectionType, boolean isMapType, boolean isImported) {

        this.typeUtils = typeUtils;
        this.elementUtils = elementUtils;

        this.typeMirror = typeMirror;
        this.typeElement = typeElement;
        this.typeParameters = typeParameters;
        this.implementationType = implementationType;

        this.packageName = packageName;
        this.name = name;
        this.qualifiedName = qualifiedName;

        this.isInterface = isInterface;
        this.isEnumType = isEnumType;
        this.isIterableType = isIterableType;
        this.isCollectionType = isCollectionType;
        this.isMapType = isMapType;
        this.isImported = isImported;
        this.isVoid = typeMirror.getKind() == TypeKind.VOID;

        if ( isEnumType ) {
            enumConstants = new ArrayList<String>();

            for ( Element element : typeElement.getEnclosedElements() ) {
                // #162: The check for visibility shouldn't be required, but the Eclipse compiler implementation
                // exposes non-enum members otherwise
                if ( element.getKind() == ElementKind.ENUM_CONSTANT &&
                    element.getModifiers().contains( Modifier.PUBLIC ) ) {
                    enumConstants.add( element.getSimpleName().toString() );
                }
            }
        }
        else {
            enumConstants = Collections.emptyList();
        }
    }
    //CHECKSTYLE:ON

    public TypeMirror getTypeMirror() {
        return typeMirror;
    }

    public TypeElement getTypeElement() {
        return typeElement;
    }

    public String getPackageName() {
        return packageName;
    }

    public String getName() {
        return name;
    }

    public List<Type> getTypeParameters() {
        return typeParameters;
    }

    public boolean isPrimitive() {
        return typeMirror.getKind().isPrimitive();
    }

    public boolean isInterface() {
        return isInterface;
    }

    public boolean isEnumType() {
        return isEnumType;
    }

    public boolean isVoid() {
        return isVoid;
    }

    /**
     * Returns this type's enum constants in case it is an enum, an empty list otherwise.
     */
    public List<String> getEnumConstants() {
        return enumConstants;
    }

    /**
     * Returns the implementation type to be instantiated in case this type is an interface iterable, collection or map
     * type. The type will have the correct type arguments, so if this type e.g. represents {@code Set<String>}, the
     * implementation type is {@code HashSet<String>}.
     *
     * @return The implementation type to be instantiated in case this type is an interface iterable, collection or map
     * type, {@code null} otherwise.
     */
    public Type getImplementationType() {
        return implementationType;
    }

    public boolean isIterableType() {
        return isIterableType;
    }

    public boolean isCollectionType() {
        return isCollectionType;
    }

    public boolean isMapType() {
        return isMapType;
    }

    public boolean isCollectionOrMapType() {
        return isCollectionType || isMapType;
    }

    public String getFullyQualifiedName() {
        return qualifiedName;
    }

    @Override
    public Set<Type> getImportTypes() {
        return implementationType != null ? Collections.singleton( implementationType ) : Collections.<Type>emptySet();
    }

    /**
     * Whether this type is imported by means of an import statement in the currently generated source file (meaning it
     * can be referenced in the generated source using its simple name) or not (meaning it has to be referenced using
     * the fully-qualified name).
     *
     * @return {@code true} if the type is imported, {@code false} otherwise.
     */
    public boolean isImported() {
        return isImported;
    }

    /**
     * @param annotationTypeName the fully qualified name of the annotation type
     *
     * @return true, if the type is annotated with an annotation of the specified type (super-types are not inspected)
     */
    public boolean isAnnotatedWith(String annotationTypeName) {
        List<? extends AnnotationMirror> annotationMirrors = typeElement.getAnnotationMirrors();

        for ( AnnotationMirror mirror : annotationMirrors ) {
            Name mirrorAnnotationName = ( (TypeElement) mirror.getAnnotationType().asElement() ).getQualifiedName();
            if ( mirrorAnnotationName.contentEquals( annotationTypeName ) ) {
                return true;
            }
        }

        return false;
    }

    public Type erasure() {
        return new Type(
            typeUtils,
            elementUtils,
            SpecificCompilerWorkarounds.erasure( typeUtils, typeMirror ),
            typeElement,
            typeParameters,
            implementationType,
            packageName,
            name,
            qualifiedName,
            isInterface,
            isEnumType,
            isIterableType,
            isCollectionType,
            isMapType,
            isImported
        );
    }

    /**
     * Whether this type is assignable to the given other type.
     *
     * @param other The other type.
     *
     * @return {@code true} if and only if this type is assignable to the given other type.
     */
    // TODO This doesn't yet take wild card types into account; e.g. ? extends Integer wouldn't be assignable to Number
    // atm.
    public boolean isAssignableTo(Type other) {
        if ( equals( other ) ) {
            return true;
        }

        return typeUtils.isAssignable( typeMirror, other.typeMirror );
    }

    /**
     * getGetters
     *
     * @return an unmodifiable list of all getters (including 'is' for booleans).
     */
    public List<ExecutableElement> getGetters() {
        if ( getters == null ) {
            List<? extends Element> members = elementUtils.getAllMembers( typeElement );
            getters = Collections.unmodifiableList( Filters.getterMethodsIn( members ) );
        }
        return getters;
    }

    /**
     * Tries to find an addMethod in this type for given collection property in this type.
     *
     * Matching occurs on:
     * <ol>
     * <li>The generic type parameter type of the collection should match the adder method argument</li>
     * <li>When there are more candidates, property name is made singular (as good as is possible). This routine
     * looks for a matching add method name.</li>
     * <li>The singularization rules of Dali are used to make a property name singular. This routine
     * looks for a matching add method name.</li>
     * </ol>
     *
     * @param collectionProperty property type (assumed collection) to find  the adder method for
     * @param pluralPropertyName the property name (assumed plural)
     *
     * @return corresponding adder method for getter when present
     */
    public ExecutableElement getAdderForType(Type collectionProperty, String pluralPropertyName) {

        List<ExecutableElement> candidates = new ArrayList<ExecutableElement>();
        if ( collectionProperty.isCollectionType ) {

            // this is a collection, so this can be done always
            if ( !collectionProperty.getTypeParameters().isEmpty() ) {
                // there's only one type arg to a collection
                TypeMirror typeArg = collectionProperty.getTypeParameters().get( 0 ).getTypeMirror();
                // now, look for a method that
                // 1) starts with add,
                // 2) and has typeArg as one and only arg
                List<ExecutableElement> adderList = getAdders();
                for ( ExecutableElement adder : adderList ) {
                    VariableElement arg = adder.getParameters().get( 0 );
                    if ( arg.asType().equals( typeArg ) ) {
                        candidates.add( adder );
                    }
                }
            }
        }
        if ( candidates.isEmpty() ) {
            return null;
        }
        else if ( candidates.size() == 1 ) {
            return candidates.get( 0 );
        }
        else {
            for ( ExecutableElement candidate : candidates ) {
                String elementName = Executables.getElementNameForAdder( candidate );
                if ( elementName.equals( Nouns.singularize( pluralPropertyName ) ) ) {
                    return candidate;
                }
            }
        }

        return null;
    }

    /**
     * getSetters
     *
     * @return an unmodifiable list of all setters
     */
    public List<ExecutableElement> getSetters() {
        if ( setters == null ) {
            List<? extends Element> members = elementUtils.getAllMembers( typeElement );
            setters = Collections.unmodifiableList( Filters.setterMethodsIn( members ) );
        }
        return setters;
    }

    /**
     * Alternative accessors could be a getter for a collection / map. By means of the
     * {@link Collection#addAll(Collection) } or {@link Map#putAll(Map)} this getter can still be used as
     * targetAccessor. JAXB XJC tool generates such constructs. This method can be extended when new cases come along.
     * getAdders
     *
     * @return an unmodifiable list of all adders
     */
    private List<ExecutableElement> getAdders() {
        if ( adders == null ) {
            List<? extends Element> members = elementUtils.getAllMembers( typeElement );
            adders = Collections.unmodifiableList( Filters.adderMethodsIn( members ) );
        }
        return adders;
    }

    /**
     * Alternative accessors could be a getter for a collection. By means of the
     * {@link java.util.Collection#addAll(java.util.Collection) } this getter can still
     * be used as targetAccessor. JAXB XJC tool generates such constructs.
     *
     * This method can be extended when new cases come along.
     *
     * @return an unmodifiable list of alternative target accessors.
     */
    public List<ExecutableElement> getAlternativeTargetAccessors() {
        if ( alternativeTargetAccessors == null ) {

            List<ExecutableElement> result = new ArrayList<ExecutableElement>();
            List<ExecutableElement> setterMethods = getSetters();
            List<ExecutableElement> getterMethods = getGetters();

            // there could be a getter method for a list/map that is not present as setter.
            // a getter could substitute the setter in that case and act as setter.
            // (assuming it is initialized)
            for ( ExecutableElement getterMethod : getterMethods ) {
                if ( isCollectionOrMap( getterMethod ) &&
                    !correspondingSetterMethodExists( getterMethod, setterMethods ) ) {
                    result.add( getterMethod );
                }
            }

            alternativeTargetAccessors = Collections.unmodifiableList( result );
        }
        return alternativeTargetAccessors;
    }

    private boolean correspondingSetterMethodExists(ExecutableElement getterMethod,
                                                    List<ExecutableElement> setterMethods) {
        String getterPropertyName = Executables.getPropertyName( getterMethod );

        for ( ExecutableElement setterMethod : setterMethods ) {
            String setterPropertyName = Executables.getPropertyName( setterMethod );
            if ( getterPropertyName.equals( setterPropertyName ) ) {
                return true;
            }
        }

        return false;
    }

    private boolean isCollectionOrMap(ExecutableElement getterMethod) {
        return isCollection( getterMethod.getReturnType() ) || isMap( getterMethod.getReturnType() );
    }

    private boolean isCollection(TypeMirror candidate) {
        return isSubType( candidate, Collection.class );
    }

    private boolean isMap(TypeMirror candidate) {
        return isSubType( candidate, Map.class );
    }

    private boolean isSubType(TypeMirror candidate, Class<?> clazz) {
        String className = clazz.getCanonicalName();
        TypeMirror classType =
            SpecificCompilerWorkarounds.erasure( typeUtils, elementUtils.getTypeElement( className ).asType() );
        return SpecificCompilerWorkarounds.isSubType( typeUtils, candidate, classType );
    }

    /**
     * Returns the length of the shortest path in the type hierarchy between this type and the specified other type.
     * Returns {@code -1} if this type is not assignable to the other type. Returns {@code 0} if this type is equal to
     * the other type. Returns {@code 1}, if the other type is a direct super type of this type, and so on.
     *
     * @param assignableOther the other type
     *
     * @return the length of the shortest path in the type hierarchy between this type and the specified other type
     */
    public int distanceTo(Type assignableOther) {
        return distanceTo( typeMirror, assignableOther.typeMirror );
    }

    private int distanceTo(TypeMirror base, TypeMirror targetType) {
        if ( typeUtils.isSameType( base, targetType ) ) {
            return 0;
        }

        if ( !typeUtils.isAssignable( base, targetType ) ) {
            return -1;
        }

        List<? extends TypeMirror> directSupertypes = typeUtils.directSupertypes( base );
        int minDistanceOfSuperToTargetType = Integer.MAX_VALUE;
        for ( TypeMirror type : directSupertypes ) {
            int distanceToTargetType = distanceTo( type, targetType );
            if ( distanceToTargetType >= 0 ) {
                minDistanceOfSuperToTargetType = Math.min( minDistanceOfSuperToTargetType, distanceToTargetType );
            }
        }

        return 1 + minDistanceOfSuperToTargetType;
    }

    /**
     * Whether this type can access the given method declared on the given type.
     */
    public boolean canAccess(Type type, ExecutableElement method) {
        if ( method.getModifiers().contains( Modifier.PRIVATE ) ) {
            return false;
        }
        else if ( method.getModifiers().contains( Modifier.PROTECTED ) ) {
            return isAssignableTo( type ) || getPackageName().equals( type.getPackageName() );
        }
        else if ( !method.getModifiers().contains( Modifier.PUBLIC ) ) {
            // default
            return getPackageName().equals( type.getPackageName() );
        }
        // public
        return true;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ( ( name == null ) ? 0 : name.hashCode() );
        result = prime * result + ( ( packageName == null ) ? 0 : packageName.hashCode() );
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if ( this == obj ) {
            return true;
        }
        if ( obj == null ) {
            return false;
        }
        if ( getClass() != obj.getClass() ) {
            return false;
        }
        Type other = (Type) obj;

        return typeUtils.isSameType( typeMirror, other.typeMirror );
    }

    @Override
    public int compareTo(Type o) {
        return getFullyQualifiedName().compareTo( o.getFullyQualifiedName() );
    }

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

Related Classes of org.mapstruct.ap.model.common.Type

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.