Package org.springframework.roo.classpath.itd

Source Code of org.springframework.roo.classpath.itd.ItdSourceFileComposer

package org.springframework.roo.classpath.itd;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.details.AnnotationMetadataUtils;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.ConstructorMetadata;
import org.springframework.roo.classpath.details.DeclaredFieldAnnotationDetails;
import org.springframework.roo.classpath.details.DeclaredMethodAnnotationDetails;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.ItdTypeDetails;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.model.ImportRegistrationResolver;
import org.springframework.roo.model.ImportRegistrationResolverImpl;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;

/**
* A simple way of producing an inter-type declaration source file.
*
* @author Ben Alex
* @author Stefan Schmidt
* @author Alan Stewart
* @since 1.0
*/
public class ItdSourceFileComposer {

    private final JavaType aspect;
    private boolean content;
    private int indentLevel = 0;
    private final JavaType introductionTo;
    private final ItdTypeDetails itdTypeDetails;
    private StringBuilder pw = new StringBuilder();
    private final ImportRegistrationResolver resolver;

    /**
     * Constructs an {@link ItdSourceFileComposer} containing the members that
     * were requested in the passed object.
     *
     * @param itdTypeDetails to construct (required)
     */
    public ItdSourceFileComposer(final ItdTypeDetails itdTypeDetails) {
        Validate.notNull(itdTypeDetails, "ITD type details required");
        Validate.notNull(itdTypeDetails.getName(),
                "Introduction to is required");

        this.itdTypeDetails = itdTypeDetails;
        introductionTo = itdTypeDetails.getName();
        aspect = itdTypeDetails.getAspect();

        // Create my own resolver, so we can add items to it as we process
        resolver = new ImportRegistrationResolverImpl(itdTypeDetails
                .getAspect().getPackage());
        resolver.addImport(introductionTo); // ROO-2932

        for (final JavaType registeredImport : itdTypeDetails
                .getRegisteredImports()) {
            // Do a sanity check in case the user misused it
            if (resolver.isAdditionLegal(registeredImport)) {
                resolver.addImport(registeredImport);
            }
        }

        appendTypeDeclaration();
        appendDeclarePrecedence();
        appendExtendsTypes();
        appendImplementsTypes();
        appendTypeAnnotations();
        appendFieldAnnotations();
        appendMethodAnnotations();
        appendFields();
        appendConstructors();
        appendMethods(itdTypeDetails.getGovernor().getPhysicalTypeCategory()
                .equals(PhysicalTypeCategory.INTERFACE));
        appendInnerTypes();
        appendTerminator();

        // Now prepend the package declaration and any imports
        // We need to do this ** at the end ** so we can ensure our compilation
        // unit imports are correct, as they're built as we traverse over the
        // other members
        prependCompilationUnitDetails();
    }

    /**
     * Prints the message, WITHOUT ANY INDENTATION.
     */
    private ItdSourceFileComposer append(final String message) {
        if (message != null && !"".equals(message)) {
            pw.append(message);
            content = true;
        }
        return this;
    }

    private void appendConstructors() {
        final List<? extends ConstructorMetadata> constructors = itdTypeDetails
                .getDeclaredConstructors();
        if (constructors == null || constructors.isEmpty()) {
            return;
        }

        content = true;

        for (final ConstructorMetadata constructor : constructors) {
            Validate.isTrue(
                    constructor.getParameterTypes().size() == constructor
                            .getParameterNames().size(),
                    "Mismatched parameter names against parameter types");

            // Append annotations
            for (final AnnotationMetadata annotation : constructor
                    .getAnnotations()) {
                appendIndent();
                outputAnnotation(annotation);
                this.newLine(false);
            }

            // Append "<modifier> <TargetOfIntroduction>.new" portion
            appendIndent();
            if (constructor.getModifier() != 0) {
                append(Modifier.toString(constructor.getModifier()));
                append(" ");
            }
            append(introductionTo.getSimpleTypeName());
            append(".");
            append("new");

            // Append parameter types and names
            append("(");
            final List<AnnotatedJavaType> parameterTypes = constructor
                    .getParameterTypes();
            final List<JavaSymbolName> parameterNames = constructor
                    .getParameterNames();
            for (int i = 0; i < parameterTypes.size(); i++) {
                final AnnotatedJavaType paramType = parameterTypes.get(i);
                final JavaSymbolName paramName = parameterNames.get(i);
                for (final AnnotationMetadata methodParameterAnnotation : paramType
                        .getAnnotations()) {
                    append(AnnotationMetadataUtils
                            .toSourceForm(methodParameterAnnotation));
                    append(" ");
                }
                append(paramType.getJavaType().getNameIncludingTypeParameters(
                        false, resolver));
                append(" ");
                append(paramName.getSymbolName());
                if (i < parameterTypes.size() - 1) {
                    append(", ");
                }
            }
            append(") {");
            this.newLine(false);
            indent();

            // Add body
            append(constructor.getBody());
            indentRemove();
            appendFormalLine("}");
            this.newLine(false);
        }
    }

    private void appendExtendsTypes() {
      final List<JavaType> extendsTypes = itdTypeDetails.getExtendsTypes();
      if (extendsTypes == null || extendsTypes.isEmpty()) {
        return;
      }
     
      content = true;
     
      for (final JavaType extendsType : extendsTypes) {
        appendIndent();
        append("declare parents: ");
        append(introductionTo.getSimpleTypeName());
        append(" extends ");
        if (resolver
            .isFullyQualifiedFormRequiredAfterAutoImport(extendsType)) {
          append(extendsType.getNameIncludingTypeParameters());
        }
        else {
          append(extendsType.getNameIncludingTypeParameters(false,
              resolver));
        }
        append(";");
        this.newLine(false);
        this.newLine();
      }
    }
   
    private void appendDeclarePrecedence() {
        final Set<JavaType> aspects = itdTypeDetails.getDeclarePrecedence();
        if (aspects == null || aspects.isEmpty()) {
            return;
        }

        content = true;
       
        appendIndent();
        append("declare precedence: ");
       
        List<String> aspectNames = new ArrayList<String>(aspects.size());
       
        for (final JavaType aspect : aspects) {
            if (resolver
                    .isFullyQualifiedFormRequiredAfterAutoImport(aspect)) {
                aspectNames.add(aspect.getNameIncludingTypeParameters());
            }
            else {
              aspectNames.add(aspect.getNameIncludingTypeParameters(false,
                        resolver));
            }
        }
        append(StringUtils.join(aspectNames, ", "));
        append(";");
        this.newLine(false);
        this.newLine();
    }

    private void appendFieldAnnotations() {
        final List<DeclaredFieldAnnotationDetails> fieldAnnotations = itdTypeDetails
                .getFieldAnnotations();
        if (fieldAnnotations == null || fieldAnnotations.isEmpty()) {
            return;
        }

        content = true;

        for (final DeclaredFieldAnnotationDetails fieldDetails : fieldAnnotations) {
            appendIndent();
            append("declare @field: * ");
            append(introductionTo.getSimpleTypeName());
            append(".");
            append(fieldDetails.getField().getFieldName().getSymbolName());
            append(": ");
            if (fieldDetails.isRemoveAnnotation()) {
                append("-");
            }
            outputAnnotation(fieldDetails.getFieldAnnotation());
            append(";");
            this.newLine(false);
            this.newLine();
        }
    }

    private void appendFields() {
        final List<? extends FieldMetadata> fields = itdTypeDetails
                .getDeclaredFields();
        if (fields == null || fields.isEmpty()) {
            return;
        }

        content = true;
        for (final FieldMetadata field : fields) {
            // Append annotations
            for (final AnnotationMetadata annotation : field.getAnnotations()) {
                appendIndent();
                outputAnnotation(annotation);
                this.newLine(false);
            }

            // Append "<modifier> <fieldType> <fieldName>" portion
            appendIndent();
            if (field.getModifier() != 0) {
                append(Modifier.toString(field.getModifier()));
                append(" ");
            }
            append(field.getFieldType().getNameIncludingTypeParameters(false,
                    resolver));
            append(" ");
            append(introductionTo.getSimpleTypeName());
            append(".");
            append(field.getFieldName().getSymbolName());

            // Append initializer, if present
            if (field.getFieldInitializer() != null) {
                append(" = ");
                append(field.getFieldInitializer());
            }

            // Complete the field declaration
            append(";");
            this.newLine(false);
            this.newLine();
        }
    }

    /**
     * Prints the message, after adding indents and returns to a new line. This
     * is the most commonly used method.
     */
    private ItdSourceFileComposer appendFormalLine(final String message) {
        appendIndent();
        if (message != null && !"".equals(message)) {
            pw.append(message);
            content = true;
        }
        return newLine(false);
    }

    private void appendImplementsTypes() {
        final List<JavaType> implementsTypes = itdTypeDetails
                .getImplementsTypes();
        if (implementsTypes == null || implementsTypes.isEmpty()) {
            return;
        }

        content = true;

        for (final JavaType extendsType : implementsTypes) {
            appendIndent();
            append("declare parents: ");
            append(introductionTo.getSimpleTypeName());
            append(" implements ");
            if (resolver
                    .isFullyQualifiedFormRequiredAfterAutoImport(extendsType)) {
                append(extendsType.getNameIncludingTypeParameters());
            }
            else {
                append(extendsType.getNameIncludingTypeParameters(false,
                        resolver));
            }
            append(";");
            this.newLine(false);
            this.newLine();
        }
    }

    /**
     * Prints the relevant number of indents.
     */
    private ItdSourceFileComposer appendIndent() {
        for (int i = 0; i < indentLevel; i++) {
            pw.append("    ");
        }
        return this;
    }

    /**
     * supports static inner types with static field definitions only at this
     * point
     */
    private void appendInnerTypes() {
        final List<ClassOrInterfaceTypeDetails> innerTypes = itdTypeDetails
                .getInnerTypes();

        for (final ClassOrInterfaceTypeDetails innerType : innerTypes) {
            content = true;
            appendIndent();
            if (innerType.getModifier() != 0) {
                append(Modifier.toString(innerType.getModifier()));
                append(" ");
            }
            append("class ");
            append(introductionTo.getNameIncludingTypeParameters());
            append(".");
            append(innerType.getName().getSimpleTypeName());
            if (innerType.getExtendsTypes().size() > 0) {
                append(" extends ");
                // There should only be one extends type for inner classes
                final JavaType extendsType = innerType.getExtendsTypes().get(0);
                if (resolver
                        .isFullyQualifiedFormRequiredAfterAutoImport(extendsType)) {
                    append(extendsType.getNameIncludingTypeParameters());
                }
                else {
                    append(extendsType.getNameIncludingTypeParameters(false,
                            resolver));
                }
                append(" ");
            }
            final List<JavaType> implementsTypes = innerType
                    .getImplementsTypes();
            if (implementsTypes.size() > 0) {
                append(" implements ");
                for (int i = 0; i < implementsTypes.size(); i++) {
                    final JavaType implementsType = implementsTypes.get(i);
                    if (resolver
                            .isFullyQualifiedFormRequiredAfterAutoImport(implementsType)) {
                        append(implementsType.getNameIncludingTypeParameters());
                    }
                    else {
                        append(implementsType.getNameIncludingTypeParameters(
                                false, resolver));
                    }
                    if (i != implementsTypes.size() - 1) {
                        append(", ");
                    }
                    else {
                        append(" ");
                    }
                }
            }
            append("{");
            this.newLine(false);

            // Write out fields
            for (final FieldMetadata field : innerType.getDeclaredFields()) {
                indent();
                this.newLine(false);

                // Append annotations
                for (final AnnotationMetadata annotation : field
                        .getAnnotations()) {
                    appendIndent();
                    outputAnnotation(annotation);
                    this.newLine(false);
                }
                appendIndent();
                if (field.getModifier() != 0) {
                    append(Modifier.toString(field.getModifier()));
                    append(" ");
                }
                append(field.getFieldType().getNameIncludingTypeParameters(
                        false, resolver));
                append(" ");
                append(field.getFieldName().getSymbolName());

                // Append initializer, if present
                if (field.getFieldInitializer() != null) {
                    append(" = ");
                    append(field.getFieldInitializer());
                }

                // Complete the field declaration
                append(";");
                this.newLine(false);
                indentRemove();
            }
            this.newLine(false);

            // Write out methods
            indent();
            writeMethods(innerType.getDeclaredMethods(), false, false);
            indentRemove();

            appendIndent();
            append("}");
            this.newLine(false);
            this.newLine();
        }
    }

    private void appendMethodAnnotations() {
        final List<DeclaredMethodAnnotationDetails> methodAnnotations = itdTypeDetails
                .getMethodAnnotations();
        if (methodAnnotations == null || methodAnnotations.isEmpty()) {
            return;
        }

        content = true;

        for (final DeclaredMethodAnnotationDetails methodDetails : methodAnnotations) {
            appendIndent();
            append("declare @method: ");
            append(Modifier.toString(methodDetails.getMethodMetadata()
                    .getModifier()));
            append(" ");
            append(methodDetails.getMethodMetadata().getReturnType()
                    .getNameIncludingTypeParameters());
            append(" ");
            append(introductionTo.getSimpleTypeName());
            append(".");
            append(methodDetails.getMethodMetadata().getMethodName()
                    .getSymbolName());
            append("(");
            for (int i = 0; i < methodDetails.getMethodMetadata()
                    .getParameterTypes().size(); i++) {
                append(methodDetails.getMethodMetadata().getParameterTypes()
                        .get(i).getJavaType()
                        .getNameIncludingTypeParameters(false, resolver));
                if (i != methodDetails.getMethodMetadata().getParameterTypes()
                        .size() - 1) {
                    append(",");
                }
            }
            append("): ");
            outputAnnotation(methodDetails.getMethodAnnotation());
            append(";");
            this.newLine(false);
            this.newLine();
        }
    }

    private void appendMethods(final boolean interfaceMethod) {
        final List<? extends MethodMetadata> methods = itdTypeDetails
                .getDeclaredMethods();
        if (methods == null || methods.isEmpty()) {
            return;
        }

        content = true;
        writeMethods(methods, true, interfaceMethod);
    }

    private void appendTerminator() {
        Validate.isTrue(indentLevel == 1,
                "Indent level must be 1 (not %d) to conclude", indentLevel);
        indentRemove();

        // Ensure we present the content flag, as it will be set true during the
        // formal line append
        final boolean contentBefore = content;
        appendFormalLine("}");
        content = contentBefore;

    }

    private void appendTypeAnnotations() {
        final List<? extends AnnotationMetadata> typeAnnotations = itdTypeDetails
                .getAnnotations();
        if (typeAnnotations == null || typeAnnotations.isEmpty()) {
            return;
        }

        content = true;

        for (final AnnotationMetadata typeAnnotation : typeAnnotations) {
            appendIndent();
            append("declare @type: ");
            append(introductionTo.getSimpleTypeName());
            append(": ");
            outputAnnotation(typeAnnotation);
            append(";");
            this.newLine(false);
            this.newLine();
        }
    }

    private void appendTypeDeclaration() {
        Validate.isTrue(
                introductionTo.getPackage().equals(aspect.getPackage()),
                "Aspect and introduction must be in identical packages");

        appendIndent();
        if (itdTypeDetails.isPrivilegedAspect()) {
            append("privileged ");
        }
        append("aspect " + aspect.getSimpleTypeName() + " {");
        this.newLine(false);
        indent();
        this.newLine();

        // Set to false, as it was set true during the above operations
        content = false;
    }

    private String getNewLine() {
        // We use \n for consistency with JavaParser's DumpVisitor, which always
        // uses \n
        return "\n";
    }

    public String getOutput() {
        return pw.toString();
    }

    /**
     * Increases the indent by one level.
     */
    private ItdSourceFileComposer indent() {
        indentLevel++;
        return this;
    }

    /**
     * Decreases the indent by one level.
     */
    private ItdSourceFileComposer indentRemove() {
        indentLevel--;
        return this;
    }

    /**
     * Indicates whether any content was added to the ITD, aside from the formal
     * ITD declaration.
     *
     * @return true if there is actual content in the ITD, false otherwise
     */
    public boolean isContent() {
        return content;
    }

    /**
     * Prints a blank line, ensuring any indent is included before doing so.
     */
    private ItdSourceFileComposer newLine() {
        return newLine(true);
    }

    /**
     * Prints a blank line, ensuring any indent is included before doing so.
     */
    private ItdSourceFileComposer newLine(final boolean indent) {
        if (indent) {
            appendIndent();
        }
        // We use \n for consistency with JavaParser's DumpVisitor, which always
        // uses \n
        pw.append(getNewLine());
        // pw.append(StringUtils.LINE_SEPARATOR);
        return this;
    }

    private void outputAnnotation(final AnnotationMetadata annotation) {
        append(AnnotationMetadataUtils.toSourceForm(annotation, resolver));
    }

    private void prependCompilationUnitDetails() {
        final StringBuilder topOfFile = new StringBuilder();

        topOfFile
                .append("// WARNING: DO NOT EDIT THIS FILE. THIS FILE IS MANAGED BY SPRING ROO.")
                .append(getNewLine());
        topOfFile
                .append("// You may push code into the target .java compilation unit if you wish to edit any member(s).")
                .append(getNewLine()).append(getNewLine());

        // Note we're directly interacting with the top of file string builder
        if (!aspect.isDefaultPackage()) {
            topOfFile.append("package ")
                    .append(aspect.getPackage().getFullyQualifiedPackageName())
                    .append(";").append(getNewLine());
            topOfFile.append(getNewLine());
        }

        // Ordered to ensure consistency of output
        final SortedSet<JavaType> types = new TreeSet<JavaType>();
        types.addAll(resolver.getRegisteredImports());
        if (!types.isEmpty()) {
            for (final JavaType importType : types) {
                if (introductionTo.equals(importType.getEnclosingType())) {
                    // We don't "import" types defined within governor, as they
                    // already have scope and this causes AJDT warnings (see
                    // ROO-1686)
                    continue;
                }
                topOfFile.append("import ")
                        .append(importType.getFullyQualifiedTypeName())
                        .append(";").append(getNewLine());
            }

            topOfFile.append(getNewLine());
        }

        // Now append the normal file to the bottom
        topOfFile.append(pw.toString());

        // Replace the old writer with out new writer
        pw = topOfFile;
    }

    private void writeMethods(final List<? extends MethodMetadata> methods,
            final boolean defineTarget, final boolean isInterfaceMethod) {
        for (final MethodMetadata method : methods) {
            Validate.isTrue(
                    method.getParameterTypes().size() == method
                            .getParameterNames().size(),
                    "Method %s has mismatched parameter names against parameter types",
                    method.getMethodName().getSymbolName());

            // Append annotations
            for (final AnnotationMetadata annotation : method.getAnnotations()) {
                appendIndent();
                outputAnnotation(annotation);
                this.newLine(false);
            }

            // Append "<modifier> <returnType> <methodName>" portion
            appendIndent();
            if (method.getModifier() != 0) {
                append(Modifier.toString(method.getModifier()));
                append(" ");
            }

            // return type
            final boolean staticMethod = Modifier
                    .isStatic(method.getModifier());
            append(method.getReturnType().getNameIncludingTypeParameters(
                    staticMethod, resolver));
            append(" ");
            if (defineTarget) {
                append(introductionTo.getSimpleTypeName());
                append(".");
            }
            append(method.getMethodName().getSymbolName());

            // Append parameter types and names
            append("(");
            final List<AnnotatedJavaType> parameterTypes = method
                    .getParameterTypes();
            final List<JavaSymbolName> parameterNames = method
                    .getParameterNames();
            for (int i = 0; i < parameterTypes.size(); i++) {
                final AnnotatedJavaType paramType = parameterTypes.get(i);
                final JavaSymbolName paramName = parameterNames.get(i);
                for (final AnnotationMetadata methodParameterAnnotation : paramType
                        .getAnnotations()) {
                    outputAnnotation(methodParameterAnnotation);
                    append(" ");
                }
                append(paramType.getJavaType().getNameIncludingTypeParameters(
                        false, resolver));
                append(" ");
                append(paramName.getSymbolName());
                if (i < parameterTypes.size() - 1) {
                    append(", ");
                }
            }

            // Add exceptions to be thrown
            final List<JavaType> throwsTypes = method.getThrowsTypes();
            if (throwsTypes.size() > 0) {
                append(") throws ");
                for (int i = 0; i < throwsTypes.size(); i++) {
                    append(throwsTypes.get(i).getNameIncludingTypeParameters(
                            false, resolver));
                    if (throwsTypes.size() > i + 1) {
                        append(", ");
                    }
                }
            }
            else {
                append(")");
            }

            if (isInterfaceMethod) {
                append(";");
            }
            else {
                append(" {");
                this.newLine(false);

                // Add body
                indent();
                append(method.getBody());
                indentRemove();

                appendFormalLine("}");
            }
            this.newLine();
        }
    }
}
TOP

Related Classes of org.springframework.roo.classpath.itd.ItdSourceFileComposer

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.