Package org.springframework.roo.classpath.javaparser

Source Code of org.springframework.roo.classpath.javaparser.JavaParserUtils

package org.springframework.roo.classpath.javaparser;

import static org.springframework.roo.model.JavaType.OBJECT;
import japa.parser.ast.CompilationUnit;
import japa.parser.ast.ImportDeclaration;
import japa.parser.ast.TypeParameter;
import japa.parser.ast.body.ClassOrInterfaceDeclaration;
import japa.parser.ast.body.ModifierSet;
import japa.parser.ast.body.TypeDeclaration;
import japa.parser.ast.expr.AnnotationExpr;
import japa.parser.ast.expr.ClassExpr;
import japa.parser.ast.expr.Expression;
import japa.parser.ast.expr.FieldAccessExpr;
import japa.parser.ast.expr.MarkerAnnotationExpr;
import japa.parser.ast.expr.NameExpr;
import japa.parser.ast.expr.NormalAnnotationExpr;
import japa.parser.ast.expr.QualifiedNameExpr;
import japa.parser.ast.expr.SingleMemberAnnotationExpr;
import japa.parser.ast.type.ClassOrInterfaceType;
import japa.parser.ast.type.PrimitiveType;
import japa.parser.ast.type.PrimitiveType.Primitive;
import japa.parser.ast.type.ReferenceType;
import japa.parser.ast.type.Type;
import japa.parser.ast.type.VoidType;
import japa.parser.ast.type.WildcardType;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaPackage;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;

/**
* Assists with the usage of Java Parser.
* <p>
* This class is for internal use by the Java Parser module and should NOT be
* used by other code.
*
* @author Ben Alex
* @since 1.0
*/
public final class JavaParserUtils {

    /**
     * Converts the indicated {@link NameExpr} into a
     * {@link ClassOrInterfaceType}.
     * <p>
     * Note that no effort is made to manage imports etc.
     *
     * @param nameExpr to convert (required)
     * @return the corresponding {@link ClassOrInterfaceType} (never null)
     */
    public static ClassOrInterfaceType getClassOrInterfaceType(
            final NameExpr nameExpr) {
        Validate.notNull(nameExpr, "Java type required");
        if (nameExpr instanceof QualifiedNameExpr) {
            final QualifiedNameExpr qne = (QualifiedNameExpr) nameExpr;
            if (StringUtils.isNotBlank(qne.getQualifier().getName())) {
                return new ClassOrInterfaceType(qne.getQualifier().getName()
                        + "." + qne.getName());
            }
            return new ClassOrInterfaceType(qne.getName());
        }
        return new ClassOrInterfaceType(nameExpr.getName());
    }

    /**
     * Looks up the import declaration applicable to the presented name
     * expression.
     * <p>
     * If a fully-qualified name is passed to this method, the corresponding
     * import will be evaluated for a complete match. If a simple name is passed
     * to this method, the corresponding import will be evaluated if its simple
     * name matches. This therefore reflects the normal Java semantics for using
     * simple type names that have been imported.
     *
     * @param compilationUnitServices the types in the compilation unit
     *            (required)
     * @param nameExpr the expression to locate an import for (which would
     *            generally be a {@link NameExpr} and thus not have a package
     *            identifier; required)
     * @return the relevant import, or null if there is no import for the
     *         expression
     */
    private static ImportDeclaration getImportDeclarationFor(
            final CompilationUnitServices compilationUnitServices,
            final NameExpr nameExpr) {
        Validate.notNull(compilationUnitServices,
                "Compilation unit services required");
        Validate.notNull(nameExpr, "Name expression required");

        final List<ImportDeclaration> imports = compilationUnitServices
                .getImports();

        for (final ImportDeclaration candidate : imports) {
            final NameExpr candidateNameExpr = candidate.getName();
            if (!candidate.toString().contains("*")) {
                Validate.isInstanceOf(QualifiedNameExpr.class,
                        candidateNameExpr, "Expected import '" + candidate
                                + "' to use a fully-qualified type name");
            }
            if (nameExpr instanceof QualifiedNameExpr) {
                // User is asking for a fully-qualified name; let's see if there
                // is a full match
                if (isEqual(nameExpr, candidateNameExpr)) {
                    return candidate;
                }
            }
            else {
                // User is not asking for a fully-qualified name, so let's do a
                // simple name comparison that discards the import's
                // qualified-name package
                if (candidateNameExpr.getName().equals(nameExpr.getName())) {
                    return candidate;
                }
            }
        }
        return null;
    }

    /**
     * Converts a JDK {@link Modifier} integer into the equivalent Java Parser
     * modifier.
     *
     * @param modifiers the JDK int
     * @return the equivalent Java Parser int
     */
    public static int getJavaParserModifier(final int modifiers) {
        int result = 0;
        if (Modifier.isAbstract(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.ABSTRACT, result);
        }
        if (Modifier.isFinal(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.FINAL, result);
        }
        if (Modifier.isInterface(modifiers)) {
            // Unsupported by Java Parser ModifierSet
        }
        if (Modifier.isNative(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.NATIVE, result);
        }
        if (Modifier.isPrivate(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.PRIVATE, result);
        }
        if (Modifier.isProtected(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.PROTECTED, result);
        }
        if (Modifier.isPublic(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.PUBLIC, result);
        }
        if (Modifier.isStatic(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.STATIC, result);
        }
        if (Modifier.isStrict(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.STRICTFP, result);
        }
        if (Modifier.isSynchronized(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.SYNCHRONIZED, result);
        }
        if (Modifier.isTransient(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.TRANSIENT, result);
        }
        if (Modifier.isVolatile(modifiers)) {
            result = ModifierSet.addModifier(ModifierSet.VOLATILE, result);
        }
        return result;
    }

    /**
     * Resolves the effective {@link JavaType} a {@link NameExpr} represents.
     * <p>
     * You should use {@link #getJavaType(CompilationUnitServices, Type, Set)}
     * where possible so that type arguments are preserved (a {@link NameExpr}
     * does not contain type arguments).
     * <p>
     * A name expression can be either qualified or unqualified.
     * <p>
     * If a name expression is qualified and the qualification starts with a
     * lowercase letter, that represents the fully-qualified name. If the
     * qualification starts with an uppercase letter, the package name is
     * prepended to the qualifier.
     * <p>
     * If a name expression is unqualified, the imports are scanned. If the
     * unqualified name expression is found in the imports, that import
     * declaration represents the fully-qualified name. If the unqualified name
     * expression is not found in the imports, it indicates the name to find is
     * either in the same package as the qualified name expression, or the type
     * relates to a member of java.lang. If part of java.lang, the fully
     * qualified name is treated as part of java.lang. Otherwise the compilation
     * unit package plus unqualified name expression represents the fully
     * qualified name expression.
     *
     * @param compilationUnitServices for package management (required)
     * @param nameToFind to locate (required)
     * @param typeParameters names to consider type parameters (can be null if
     *            there are none)
     * @return the effective Java type (never null)
     */
    public static JavaType getJavaType(
            final CompilationUnitServices compilationUnitServices,
            final NameExpr nameToFind, final Set<JavaSymbolName> typeParameters) {
        Validate.notNull(compilationUnitServices,
                "Compilation unit services required");
        Validate.notNull(nameToFind, "Name to find is required");

        final JavaPackage compilationUnitPackage = compilationUnitServices
                .getCompilationUnitPackage();

        if (nameToFind instanceof QualifiedNameExpr) {
            final QualifiedNameExpr qne = (QualifiedNameExpr) nameToFind;

            // Handle qualified name expressions that are related to inner types
            // (eg Foo.Bar)
            final NameExpr qneQualifier = qne.getQualifier();
            final NameExpr enclosedBy = getNameExpr(compilationUnitServices
                    .getEnclosingTypeName().getSimpleTypeName());
            if (isEqual(qneQualifier, enclosedBy)) {
                // This qualified name expression is simply an inner type
                // reference
                final String name = compilationUnitServices
                        .getEnclosingTypeName().getFullyQualifiedTypeName()
                        + "." + nameToFind.getName();
                return new JavaType(name,
                        compilationUnitServices.getEnclosingTypeName());
            }

            // Refers to a different enclosing type, so calculate the package
            // name based on convention of an uppercase letter denotes same
            // package (ROO-1210)
            if (qne.toString().length() > 1
                    && Character.isUpperCase(qne.toString().charAt(0))) {
                // First letter is uppercase, so this likely requires prepending
                // of some package name
                final ImportDeclaration importDeclaration = getImportDeclarationFor(
                        compilationUnitServices, qne.getQualifier());
                if (importDeclaration == null) {
                    if (!compilationUnitPackage.getFullyQualifiedPackageName()
                            .equals("")) {
                        // It was not imported, so let's assume it's in the same
                        // package
                        return new JavaType(compilationUnitServices
                                .getCompilationUnitPackage()
                                .getFullyQualifiedPackageName()
                                + "." + qne.toString());
                    }
                }
                else {
                    return new JavaType(importDeclaration.getName() + "."
                            + qne.getName());
                }

                // This name expression (which contains a dot) had its qualifier
                // imported, so let's use the import
            }
            else {
                // First letter is lowercase, so the reference already includes
                // a package
                return new JavaType(qne.toString());
            }
        }

        if ("?".equals(nameToFind.getName())) {
            return new JavaType(OBJECT.getFullyQualifiedTypeName(), 0,
                    DataType.TYPE, JavaType.WILDCARD_NEITHER, null);
        }

        // Unqualified name detected, so check if it's in the type parameter
        // list
        if (typeParameters != null
                && typeParameters.contains(new JavaSymbolName(nameToFind
                        .getName()))) {
            return new JavaType(nameToFind.getName(), 0, DataType.VARIABLE,
                    null, null);
        }
       
        // Check if we are looking for the enclosingType itself
        final NameExpr enclosingTypeName = getNameExpr(compilationUnitServices
                .getEnclosingTypeName().getSimpleTypeName());
        if (isEqual(enclosingTypeName, nameToFind)) {
            return compilationUnitServices.getEnclosingTypeName();
        }

        // We are searching for a non-qualified name expression (nameToFind), so
        // check if the compilation unit itself declares that type
        for (final TypeDeclaration internalType : compilationUnitServices
                .getInnerTypes()) {
            final NameExpr nameExpr = getNameExpr(internalType.getName());
            if (isEqual(nameExpr, nameToFind)) {
                // Found, so now we need to convert the internalType to a proper
                // JavaType
                final String name = compilationUnitServices
                        .getEnclosingTypeName().getFullyQualifiedTypeName()
                        + "." + nameToFind.getName();
                return new JavaType(name);
            }
        }

        final ImportDeclaration importDeclaration = getImportDeclarationFor(
                compilationUnitServices, nameToFind);
        if (importDeclaration == null) {
            if (JdkJavaType.isPartOfJavaLang(nameToFind.getName())) {
                return new JavaType("java.lang." + nameToFind.getName());
            }
            final String name = compilationUnitPackage
                    .getFullyQualifiedPackageName().equals("") ? nameToFind
                    .getName() : compilationUnitPackage
                    .getFullyQualifiedPackageName()
                    + "."
                    + nameToFind.getName();
            return new JavaType(name);
        }

        return new JavaType(importDeclaration.getName().toString());
    }

    /**
     * Resolves the effective {@link JavaType} a {@link Type} represents. A
     * {@link Type} includes low-level types such as void, arrays and
     * primitives.
     *
     * @param compilationUnitServices to use for package resolution (required)
     * @param type to locate (required)
     * @param typeParameters names to consider type parameters (can be null if
     *            there are none)
     * @return the {@link JavaType}, with proper indication of primitive and
     *         array status (never null)
     */
    public static JavaType getJavaType(
            final CompilationUnitServices compilationUnitServices,
            final Type type, final Set<JavaSymbolName> typeParameters) {
        Validate.notNull(compilationUnitServices,
                "Compilation unit services required");
        Validate.notNull(type, "The reference type must be provided");

        if (type instanceof VoidType) {
            return JavaType.VOID_PRIMITIVE;
        }

        int array = 0;

        Type internalType = type;
        if (internalType instanceof ReferenceType) {
            array = ((ReferenceType) internalType).getArrayCount();
            if (array > 0) {
                internalType = ((ReferenceType) internalType).getType();
            }
        }

        if (internalType instanceof PrimitiveType) {
            final PrimitiveType pt = (PrimitiveType) internalType;
            if (pt.getType().equals(Primitive.Boolean)) {
                return new JavaType(Boolean.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Char)) {
                return new JavaType(Character.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Byte)) {
                return new JavaType(Byte.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Short)) {
                return new JavaType(Short.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Int)) {
                return new JavaType(Integer.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Long)) {
                return new JavaType(Long.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Float)) {
                return new JavaType(Float.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            if (pt.getType().equals(Primitive.Double)) {
                return new JavaType(Double.class.getName(), array,
                        DataType.PRIMITIVE, null, null);
            }
            throw new IllegalStateException("Unsupported primitive '"
                    + pt.getType() + "'");
        }

        if (internalType instanceof WildcardType) {
            // We only provide very primitive support for wildcard types; Roo
            // only needs metadata at the end of the day,
            // not complete binding support from an AST
            final WildcardType wt = (WildcardType) internalType;
            if (wt.getSuper() != null) {
                final ReferenceType rt = wt.getSuper();
                final ClassOrInterfaceType cit = (ClassOrInterfaceType) rt
                        .getType();
                final JavaType effectiveType = getJavaTypeNow(
                        compilationUnitServices, cit, typeParameters);
                return new JavaType(effectiveType.getFullyQualifiedTypeName(),
                        rt.getArrayCount(), effectiveType.getDataType(),
                        JavaType.WILDCARD_SUPER, effectiveType.getParameters());
            }
            else if (wt.getExtends() != null) {
                final ReferenceType rt = wt.getExtends();
                final ClassOrInterfaceType cit = (ClassOrInterfaceType) rt
                        .getType();
                final JavaType effectiveType = getJavaTypeNow(
                        compilationUnitServices, cit, typeParameters);
                return new JavaType(effectiveType.getFullyQualifiedTypeName(),
                        rt.getArrayCount(), effectiveType.getDataType(),
                        JavaType.WILDCARD_EXTENDS,
                        effectiveType.getParameters());
            }
            else {
                return new JavaType(OBJECT.getFullyQualifiedTypeName(), 0,
                        DataType.TYPE, JavaType.WILDCARD_NEITHER, null);
            }
        }

        ClassOrInterfaceType cit;
        if (internalType instanceof ClassOrInterfaceType) {
            cit = (ClassOrInterfaceType) internalType;
        }
        else if (internalType instanceof ReferenceType) {
            cit = (ClassOrInterfaceType) ((ReferenceType) type).getType();
        }
        else {
            throw new IllegalStateException("The presented type '"
                    + internalType.getClass() + "' with value '" + internalType
                    + "' is unsupported by JavaParserUtils");
        }

        final JavaType effectiveType = getJavaTypeNow(compilationUnitServices,
                cit, typeParameters);
        if (array > 0) {
            return new JavaType(effectiveType.getFullyQualifiedTypeName(),
                    array, effectiveType.getDataType(),
                    effectiveType.getArgName(), effectiveType.getParameters());
        }

        return effectiveType;
    }

    /**
     * Resolves the effective {@link JavaType} a
     * {@link ClassOrInterfaceDeclaration} represents, including any type
     * parameters.
     *
     * @param compilationUnitServices for package management (required)
     * @param typeDeclaration the type declaration to resolve (required)
     * @return the effective Java type (never null)
     */
    public static JavaType getJavaType(
            final CompilationUnitServices compilationUnitServices,
            final TypeDeclaration typeDeclaration) {
        Validate.notNull(compilationUnitServices,
                "Compilation unit services required");
        Validate.notNull(typeDeclaration, "Type declaration required");

        // Convert the ClassOrInterfaceDeclaration name into a JavaType
        final NameExpr nameExpr = getNameExpr(typeDeclaration.getName());
        final JavaType effectiveType = getJavaType(compilationUnitServices,
                nameExpr, null);

        final List<JavaType> parameterTypes = new ArrayList<JavaType>();
        if (typeDeclaration instanceof ClassOrInterfaceDeclaration) {
            final ClassOrInterfaceDeclaration clazz = (ClassOrInterfaceDeclaration) typeDeclaration;
            // Populate JavaType with type parameters
            final List<TypeParameter> typeParameters = clazz
                    .getTypeParameters();
            if (typeParameters != null) {
                final Set<JavaSymbolName> locatedTypeParameters = new HashSet<JavaSymbolName>();
                for (final TypeParameter candidate : typeParameters) {
                    final JavaSymbolName currentTypeParam = new JavaSymbolName(
                            candidate.getName());
                    locatedTypeParameters.add(currentTypeParam);
                    JavaType javaType = null;
                    if (candidate.getTypeBound() == null) {
                        javaType = new JavaType(
                                OBJECT.getFullyQualifiedTypeName(), 0,
                                DataType.TYPE, currentTypeParam, null);
                    }
                    else {
                        final ClassOrInterfaceType cit = candidate
                                .getTypeBound().get(0);
                        javaType = JavaParserUtils.getJavaTypeNow(
                                compilationUnitServices, cit,
                                locatedTypeParameters);
                        javaType = new JavaType(
                                javaType.getFullyQualifiedTypeName(),
                                javaType.getArray(), javaType.getDataType(),
                                currentTypeParam, javaType.getParameters());
                    }
                    parameterTypes.add(javaType);
                }
            }
        }

        return new JavaType(effectiveType.getFullyQualifiedTypeName(),
                effectiveType.getArray(), effectiveType.getDataType(), null,
                parameterTypes);
    }

    /**
     * Resolves the effective {@link JavaType} a {@link ClassOrInterfaceType}
     * represents, including any type arguments.
     *
     * @param compilationUnitServices for package management (required)
     * @param cit the class or interface type to resolve (required)
     * @return the effective Java type (never null)
     */
    public static JavaType getJavaTypeNow(
            final CompilationUnitServices compilationUnitServices,
            final ClassOrInterfaceType cit,
            final Set<JavaSymbolName> typeParameters) {
        Validate.notNull(compilationUnitServices,
                "Compilation unit services required");
        Validate.notNull(cit, "ClassOrInterfaceType required");

        final JavaPackage compilationUnitPackage = compilationUnitServices
                .getCompilationUnitPackage();
        Validate.notNull(compilationUnitPackage,
                "Compilation unit package required");

        String typeName = cit.getName();
        ClassOrInterfaceType scope = cit.getScope();
        while (scope != null) {
            typeName = scope.getName() + "." + typeName;
            scope = scope.getScope();
        }
        final NameExpr nameExpr = getNameExpr(typeName);

        final JavaType effectiveType = getJavaType(compilationUnitServices,
                nameExpr, typeParameters);

        // Handle any type arguments
        final List<JavaType> parameterTypes = new ArrayList<JavaType>();
        if (cit.getTypeArgs() != null) {
            for (final Type ta : cit.getTypeArgs()) {
                parameterTypes.add(getJavaType(compilationUnitServices, ta,
                        typeParameters));
            }
        }

        return new JavaType(effectiveType.getFullyQualifiedTypeName(),
                effectiveType.getArray(), effectiveType.getDataType(), null,
                parameterTypes);
    }

    /**
     * Converts a Java Parser modifier integer into a JDK {@link Modifier}
     * integer.
     *
     * @param modifiers the Java Parser int
     * @return the equivalent JDK int
     */
    public static int getJdkModifier(final int modifiers) {
        int result = 0;
        if (ModifierSet.isAbstract(modifiers)) {
            result |= Modifier.ABSTRACT;
        }
        if (ModifierSet.isFinal(modifiers)) {
            result |= Modifier.FINAL;
        }
        if (ModifierSet.isNative(modifiers)) {
            result |= Modifier.NATIVE;
        }
        if (ModifierSet.isPrivate(modifiers)) {
            result |= Modifier.PRIVATE;
        }
        if (ModifierSet.isProtected(modifiers)) {
            result |= Modifier.PROTECTED;
        }
        if (ModifierSet.isPublic(modifiers)) {
            result |= Modifier.PUBLIC;
        }
        if (ModifierSet.isStatic(modifiers)) {
            result |= Modifier.STATIC;
        }
        if (ModifierSet.isStrictfp(modifiers)) {
            result |= Modifier.STRICT;
        }
        if (ModifierSet.isSynchronized(modifiers)) {
            result |= Modifier.SYNCHRONIZED;
        }
        if (ModifierSet.isTransient(modifiers)) {
            result |= Modifier.TRANSIENT;
        }
        if (ModifierSet.isVolatile(modifiers)) {
            result |= Modifier.VOLATILE;
        }
        return result;
    }

    /**
     * Obtains the name expression ({@link NameExpr}) for the passed
     * {@link AnnotationExpr}, which is the annotation's type.
     *
     * @param annotationExpr to retrieve the type name from (required)
     * @return the name (never null)
     */
    public static NameExpr getNameExpr(final AnnotationExpr annotationExpr) {
        Validate.notNull(annotationExpr, "Annotation expression required");
        if (annotationExpr instanceof MarkerAnnotationExpr) {
            final MarkerAnnotationExpr a = (MarkerAnnotationExpr) annotationExpr;
            final NameExpr nameToFind = a.getName();
            Validate.notNull(nameToFind,
                    "Unable to determine annotation name from '"
                            + annotationExpr + "'");
            return nameToFind;
        }
        else if (annotationExpr instanceof SingleMemberAnnotationExpr) {
            final SingleMemberAnnotationExpr a = (SingleMemberAnnotationExpr) annotationExpr;
            final NameExpr nameToFind = a.getName();
            Validate.notNull(nameToFind,
                    "Unable to determine annotation name from '"
                            + annotationExpr + "'");
            return nameToFind;
        }
        else if (annotationExpr instanceof NormalAnnotationExpr) {
            final NormalAnnotationExpr a = (NormalAnnotationExpr) annotationExpr;
            final NameExpr nameToFind = a.getName();
            Validate.notNull(nameToFind,
                    "Unable to determine annotation name from '"
                            + annotationExpr + "'");
            return nameToFind;
        }
        throw new UnsupportedOperationException(
                "Unknown annotation expression type '"
                        + annotationExpr.getClass().getName() + "'");
    }

    /**
     * Converts the presented class name into a name expression (either a
     * {@link NameExpr} or {@link QualifiedNameExpr} depending on whether a
     * package was presented).
     *
     * @param className to convert (required; can be fully qualified or simple
     *            name only)
     * @return a compatible expression (never returns null)
     */
    public static NameExpr getNameExpr(final String className) {
        Validate.notBlank(className, "Class name required");
        if (className.contains(".")) {
            final int offset = className.lastIndexOf(".");
            final String packageName = className.substring(0, offset);
            final String typeName = className.substring(offset + 1);
            return new QualifiedNameExpr(new NameExpr(packageName), typeName);
        }
        return new NameExpr(className);
    }

    /**
     * Converts the indicated {@link JavaType} into a {@link ReferenceType}.
     * <p>
     * Note that no effort is made to manage imports etc.
     *
     * @param nameExpr to convert (required)
     * @return the corresponding {@link ReferenceType} (never null)
     */
    public static ReferenceType getReferenceType(final NameExpr nameExpr) {
        Validate.notNull(nameExpr, "Java type required");
        return new ReferenceType(getClassOrInterfaceType(nameExpr));
    }

    public static ClassOrInterfaceType getResolvedName(final JavaType target,
            final JavaType current, final CompilationUnit compilationUnit) {
        final NameExpr nameExpr = JavaParserUtils.importTypeIfRequired(target,
                compilationUnit.getImports(), current);
        final ClassOrInterfaceType resolvedName = JavaParserUtils
                .getClassOrInterfaceType(nameExpr);
        if (current.getParameters() != null
                && current.getParameters().size() > 0) {
            resolvedName.setTypeArgs(new ArrayList<Type>());
            for (final JavaType param : current.getParameters()) {
                resolvedName.getTypeArgs().add(
                        getResolvedName(target, param, compilationUnit));
            }
        }

        return resolvedName;
    }

    public static Type getResolvedName(final JavaType target,
            final JavaType current,
            final CompilationUnitServices compilationUnit) {
        final NameExpr nameExpr = JavaParserUtils.importTypeIfRequired(target,
                compilationUnit.getImports(), current);
        final ClassOrInterfaceType resolvedName = JavaParserUtils
                .getClassOrInterfaceType(nameExpr);
        if (current.getParameters() != null
                && current.getParameters().size() > 0) {
            resolvedName.setTypeArgs(new ArrayList<Type>());
            for (final JavaType param : current.getParameters()) {
                resolvedName.getTypeArgs().add(
                        getResolvedName(target, param, compilationUnit));
            }
        }
       
        if (current.getArray() > 0) {
          // Primitives includes array declaration in resolvedName
          if (!current.isPrimitive()){
            return new ReferenceType(resolvedName, current.getArray());
          }
        }

        return resolvedName;
    }

    /**
     * Given a primitive type, computes the corresponding Java Parser type.
     * <p>
     * Presenting a non-primitive type to this method will throw an exception.
     * If you have a non-primitive type, use
     * {@link #importTypeIfRequired(JavaType, List, JavaType)} and then present
     * the {@link NameExpr} it returns to
     * {@link #getClassOrInterfaceType(NameExpr)}.
     *
     * @param javaType a primitive type (required, and must be primitive)
     * @return the equivalent Java Parser {@link Type}
     */
    public static Type getType(final JavaType javaType) {
        Validate.notNull(javaType, "Java type required");
        Validate.isTrue(javaType.isPrimitive(),
                "Java type must be primitive to be presented to this method");
        if (javaType.equals(JavaType.VOID_PRIMITIVE)) {
            return new VoidType();
        }
        else if (javaType.equals(JavaType.BOOLEAN_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Boolean);
        }
        else if (javaType.equals(JavaType.BYTE_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Byte);
        }
        else if (javaType.equals(JavaType.CHAR_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Char);
        }
        else if (javaType.equals(JavaType.DOUBLE_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Double);
        }
        else if (javaType.equals(JavaType.FLOAT_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Float);
        }
        else if (javaType.equals(JavaType.INT_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Int);
        }
        else if (javaType.equals(JavaType.LONG_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Long);
        }
        else if (javaType.equals(JavaType.SHORT_PRIMITIVE)) {
            return new PrimitiveType(Primitive.Short);
        }
        throw new IllegalStateException("Unknown primitive " + javaType);
    }

    /**
     * Recognises {@link Expression}s of type {@link FieldAccessExpr} and
     * {@link ClassExpr} and automatically imports them if required, returning
     * the correct {@link Expression} that should subsequently be used.
     * <p>
     * Even if an {@link Expression} is not resolved by this method into a type
     * and/or imported, the method guarantees to always return an
     * {@link Expression} that the caller can subsequently use in place of the
     * passed {@link Expression}. In practical terms, the {@link Expression}
     * passed to this method will be returned unless the type was already
     * imported, just imported, or represented a java.lang type.
     *
     * @param targetType the compilation unit target type (required)
     * @param imports the existing imports (required)
     * @param value that expression, which need not necessarily be resolvable to
     *            a type (required)
     * @return the expression to now use, as appropriately resolved (never
     *         returns null)
     */
    public static Expression importExpressionIfRequired(
            final JavaType targetType, final List<ImportDeclaration> imports,
            final Expression value) {
        Validate.notNull(targetType, "Target type required");
        Validate.notNull(imports, "Imports required");
        Validate.notNull(value, "Expression value required");

        if (value instanceof FieldAccessExpr) {
            final Expression scope = ((FieldAccessExpr) value).getScope();
            final String field = ((FieldAccessExpr) value).getField();
            if (scope instanceof QualifiedNameExpr) {
                final String packageName = ((QualifiedNameExpr) scope)
                        .getQualifier().getName();
                final String simpleName = ((QualifiedNameExpr) scope).getName();
                final String fullyQualifiedName = packageName + "."
                        + simpleName;
                final JavaType javaType = new JavaType(fullyQualifiedName);
                final NameExpr nameToUse = importTypeIfRequired(targetType,
                        imports, javaType);
                if (!(nameToUse instanceof QualifiedNameExpr)) {
                    return new FieldAccessExpr(nameToUse, field);
                }
            }
        }
        else if (value instanceof ClassExpr) {
            final Type type = ((ClassExpr) value).getType();
            if (type instanceof ClassOrInterfaceType) {
                final JavaType javaType = new JavaType(
                        ((ClassOrInterfaceType) type).getName());
                final NameExpr nameToUse = importTypeIfRequired(targetType,
                        imports, javaType);
                if (!(nameToUse instanceof QualifiedNameExpr)) {
                    return new ClassExpr(new ClassOrInterfaceType(
                            javaType.getSimpleTypeName()));
                }
            }
            else if (type instanceof ReferenceType
                    && ((ReferenceType) type).getType() instanceof ClassOrInterfaceType) {
                final ClassOrInterfaceType cit = (ClassOrInterfaceType) ((ReferenceType) type)
                        .getType();
                final JavaType javaType = new JavaType(cit.getName());
                final NameExpr nameToUse = importTypeIfRequired(targetType,
                        imports, javaType);
                if (!(nameToUse instanceof QualifiedNameExpr)) {
                    return new ClassExpr(new ClassOrInterfaceType(
                            javaType.getSimpleTypeName()));
                }
            }
        }

        // Make no changes
        return value;
    }

    public static ReferenceType importParametersForType(
            final JavaType targetType, final List<ImportDeclaration> imports,
            final JavaType typeToImport) {
        Validate.notNull(targetType, "Target type is required");
        Validate.notNull(imports, "Compilation unit imports required");
        Validate.notNull(typeToImport, "Java type to import is required");

        final ClassOrInterfaceType cit = getClassOrInterfaceType(importTypeIfRequired(
                targetType, imports, typeToImport));
       
        // Add any type arguments presented for the return type
        if (typeToImport.getParameters().size() > 0) {
            final List<Type> typeArgs = new ArrayList<Type>();
            cit.setTypeArgs(typeArgs);
            for (final JavaType parameter : typeToImport
                    .getParameters()) {
                typeArgs.add(JavaParserUtils.importParametersForType(
                        targetType,
                        imports, parameter));
            }
        }
        return  new ReferenceType(cit);
    }

    /**
     * Attempts to import the presented {@link JavaType}.
     * <p>
     * Whether imported or not, the method returns a {@link NameExpr} suitable
     * for subsequent use when referring to that type.
     * <p>
     * If an attempt is made to import a java.lang type, it is ignored.
     * <p>
     * If an attempt is made to import a type without a package, it is ignored.
     * <p>
     * We import every type usage even if the type usage is within the same
     * package and would theoretically not require an import. This is undertaken
     * so that there is no requirement to separately parse every unqualified
     * type usage within the compilation unit so as to refrain from importing
     * subsequently conflicting types.
     *
     * @param targetType the compilation unit target type (required)
     * @param imports the compilation unit's imports (required)
     * @param typeToImport the type to be imported (required)
     * @return the name expression to be used when referring to that type (never
     *         null)
     */
    public static NameExpr importTypeIfRequired(final JavaType targetType,
            final List<ImportDeclaration> imports, final JavaType typeToImport) {
        Validate.notNull(targetType, "Target type is required");
        final JavaPackage compilationUnitPackage = targetType.getPackage();
        Validate.notNull(imports, "Compilation unit imports required");
        Validate.notNull(typeToImport, "Java type to import is required");

        // If it's a primitive, it's really easy
        if (typeToImport.isPrimitive()) {
            return new NameExpr(typeToImport.getNameIncludingTypeParameters());
        }

        // Handle if the type doesn't have a package at all
        if (typeToImport.isDefaultPackage()) {
            return new NameExpr(typeToImport.getSimpleTypeName());
        }

        final JavaPackage typeToImportPackage = typeToImport.getPackage();
        if (typeToImportPackage.equals(compilationUnitPackage)) {         
            return new NameExpr(typeToImport.getSimpleTypeName());
        }

        NameExpr typeToImportExpr;
        if (typeToImport.getEnclosingType() == null) {
            typeToImportExpr = new QualifiedNameExpr(new NameExpr(typeToImport
                    .getPackage().getFullyQualifiedPackageName()),
                    typeToImport.getSimpleTypeName());
        }
        else {
            typeToImportExpr = new QualifiedNameExpr(new NameExpr(typeToImport
                    .getEnclosingType().getFullyQualifiedTypeName()),
                    typeToImport.getSimpleTypeName());
        }

        final ImportDeclaration newImport = new ImportDeclaration(
                typeToImportExpr, false, false);

        boolean addImport = true;
        boolean useSimpleTypeName = false;
        for (final ImportDeclaration existingImport : imports) {
            if (existingImport.getName().getName()
                    .equals(newImport.getName().getName())) {
                // Do not import, as there is already an import with the simple
                // type name
                addImport = false;

                // If this is a complete match, it indicates we can use the
                // simple type name
                if (isEqual(existingImport.getName(), newImport.getName())) {
                    useSimpleTypeName = true;
                    break;
                }
            }
        }

        if (addImport
                && JdkJavaType.isPartOfJavaLang(typeToImport
                        .getSimpleTypeName())) {
            // This simple type name would be part of java.lang if left as the
            // simple name. We want a fully-qualified name.
            addImport = false;
            useSimpleTypeName = false;
        }

        if (JdkJavaType.isPartOfJavaLang(typeToImport)) {
            // So we would have imported, but we don't need to
            addImport = false;

            // The fact we could have imported means there was no other
            // conflicting simple type names
            useSimpleTypeName = true;
        }

        if (addImport
                && typeToImport.getPackage().equals(compilationUnitPackage)) {
            // It is not theoretically necessary to add an import for something
            // in the same package,
            // but we elect to explicitly perform an import so future
            // conflicting types are not imported
            // addImport = true;
            // useSimpleTypeName = false;
        }

        if (addImport
                && targetType.getSimpleTypeName().equals(
                        typeToImport.getSimpleTypeName())) {
            // So we would have imported it, but then it would conflict with the
            // simple name of the type
            addImport = false;
            useSimpleTypeName = false;
        }

        if (addImport) {
            imports.add(newImport);
            useSimpleTypeName = true;
        }

        // This is pretty crude, but at least it emits source code for people
        // (forget imports, though!)
        if (typeToImport.getArgName() != null) {
            return new NameExpr(typeToImport.toString());
        }

        if (useSimpleTypeName) {
            return new NameExpr(typeToImport.getSimpleTypeName());
        }
        return new QualifiedNameExpr(new NameExpr(typeToImport.getPackage()
                .getFullyQualifiedPackageName()),
                typeToImport.getSimpleTypeName());
    }
   
    /**
     * Indicates whether two {@link NameExpr} expressions are equal.
     * <p>
     * This method is necessary given {@link NameExpr} does not offer an equals
     * method.
     *
     * @param o1 the first entry to compare (null is acceptable)
     * @param o2 the second entry to compare (null is acceptable)
     * @return true if and only if both entries are identical
     */
    private static boolean isEqual(final NameExpr o1, final NameExpr o2) {
        if (o1 == null && o2 == null) {
            return true;
        }
        if (o1 == null && o2 != null) {
            return false;
        }
        if (o1 != null && o2 == null) {
            return false;
        }
        if (o1 != null && !o1.getName().equals(o2.getName())) {
            return false;
        }
        return o1 != null && o1.toString().equals(o2.toString());
    }

    /**
     * Searches a compilation unit and locates the declaration with the given
     * type's simple name.
     *
     * @param compilationUnit to scan (required)
     * @param javaType the target to locate (required)
     * @return the located type declaration or null if it could not be found
     */
    public static TypeDeclaration locateTypeDeclaration(
            final CompilationUnit compilationUnit, final JavaType javaType) {
        Validate.notNull(compilationUnit, "Compilation unit required");
        Validate.notNull(javaType, "Java type to search for required");
        if (compilationUnit.getTypes() == null) {
            return null;
        }
        for (final TypeDeclaration candidate : compilationUnit.getTypes()) {
            if (javaType.getSimpleTypeName().equals(candidate.getName())) {
                // We have the required type declaration
                return candidate;
            }
        }
        return null;
    }

    /**
     * Constructor is private to prevent instantiation
     */
    private JavaParserUtils() {
    }
   
    /**
     * Returns the final {@link ClassOrInterfaceType} from a {@link Type}
     *
     * @param initType
     * @return the final {@link ClassOrInterfaceType} or null if no {@link ClassOrInterfaceType} found
     *
     */
    public static ClassOrInterfaceType getClassOrInterfaceType(Type type) {
        Type tmp = type;
        while (tmp instanceof ReferenceType) {
               tmp = ((ReferenceType) tmp).getType();
        };
        if (tmp instanceof ClassOrInterfaceType){
          return (ClassOrInterfaceType) tmp;
        }
        return null;
    }   
}
TOP

Related Classes of org.springframework.roo.classpath.javaparser.JavaParserUtils

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.