Package org.springframework.roo.classpath.operations

Source Code of org.springframework.roo.classpath.operations.FieldCommands

package org.springframework.roo.classpath.operations;

import static org.springframework.roo.model.JdkJavaType.LIST;
import static org.springframework.roo.model.JdkJavaType.SET;
import static org.springframework.roo.model.JpaJavaType.EMBEDDABLE;
import static org.springframework.roo.model.JpaJavaType.ENTITY;
import static org.springframework.roo.model.SpringJavaType.PERSISTENT;
import static org.springframework.roo.shell.OptionContexts.PROJECT;
import static org.springframework.roo.shell.OptionContexts.UPDATE_PROJECT;

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

import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.Validate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.osgi.service.component.ComponentContext;
import org.springframework.roo.classpath.PhysicalTypeCategory;
import org.springframework.roo.classpath.PhysicalTypeDetails;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.TypeLocationService;
import org.springframework.roo.classpath.TypeManagementService;
import org.springframework.roo.classpath.details.ClassOrInterfaceTypeDetails;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.MemberHoldingTypeDetails;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.details.comments.CommentFormatter;
import org.springframework.roo.classpath.operations.jsr303.BooleanField;
import org.springframework.roo.classpath.operations.jsr303.CollectionField;
import org.springframework.roo.classpath.operations.jsr303.DateField;
import org.springframework.roo.classpath.operations.jsr303.DateFieldPersistenceType;
import org.springframework.roo.classpath.operations.jsr303.EmbeddedField;
import org.springframework.roo.classpath.operations.jsr303.EnumField;
import org.springframework.roo.classpath.operations.jsr303.FieldDetails;
import org.springframework.roo.classpath.operations.jsr303.ListField;
import org.springframework.roo.classpath.operations.jsr303.NumericField;
import org.springframework.roo.classpath.operations.jsr303.ReferenceField;
import org.springframework.roo.classpath.operations.jsr303.SetField;
import org.springframework.roo.classpath.operations.jsr303.StringField;
import org.springframework.roo.classpath.operations.jsr303.UploadedFileContentType;
import org.springframework.roo.classpath.operations.jsr303.UploadedFileField;
import org.springframework.roo.classpath.scanner.MemberDetails;
import org.springframework.roo.classpath.scanner.MemberDetailsScanner;
import org.springframework.roo.metadata.MetadataService;
import org.springframework.roo.model.DataType;
import org.springframework.roo.model.JavaSymbolName;
import org.springframework.roo.model.JavaType;
import org.springframework.roo.model.JdkJavaType;
import org.springframework.roo.model.ReservedWords;
import org.springframework.roo.project.ProjectOperations;
import org.springframework.roo.shell.CliAvailabilityIndicator;
import org.springframework.roo.shell.CliCommand;
import org.springframework.roo.shell.CliOption;
import org.springframework.roo.shell.CommandMarker;
import org.springframework.roo.shell.converters.StaticFieldConverter;

/**
* Additional shell commands for the purpose of creating fields.
*
* @author Ben Alex
* @author Alan Stewart
* @since 1.0
*/
@Component
@Service
public class FieldCommands implements CommandMarker {

    @Reference private MemberDetailsScanner memberDetailsScanner;
    @Reference private MetadataService metadataService;
    @Reference private ProjectOperations projectOperations;
    @Reference private StaticFieldConverter staticFieldConverter;
    @Reference private TypeLocationService typeLocationService;
    @Reference private TypeManagementService typeManagementService;

    private final Set<String> legalNumericPrimitives = new HashSet<String>();

    protected void activate(final ComponentContext context) {
        legalNumericPrimitives.add(Short.class.getName());
        legalNumericPrimitives.add(Byte.class.getName());
        legalNumericPrimitives.add(Integer.class.getName());
        legalNumericPrimitives.add(Long.class.getName());
        legalNumericPrimitives.add(Float.class.getName());
        legalNumericPrimitives.add(Double.class.getName());
        staticFieldConverter.add(Cardinality.class);
        staticFieldConverter.add(Fetch.class);
        staticFieldConverter.add(EnumType.class);
        staticFieldConverter.add(DateTime.class);
    }

    @CliCommand(value = "field boolean", help = "Adds a private boolean field to an existing Java source file")
    public void addFieldBoolean(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "assertFalse", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must assert false") final boolean assertFalse,
            @CliOption(key = "assertTrue", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must assert true") final boolean assertTrue,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "primitive", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to use a primitive type") final boolean primitive,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final BooleanField fieldDetails = new BooleanField(
                physicalTypeIdentifier, primitive ? JavaType.BOOLEAN_PRIMITIVE
                        : JavaType.BOOLEAN_OBJECT, fieldName);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        fieldDetails.setAssertFalse(assertFalse);
        fieldDetails.setAssertTrue(assertTrue);
        if (column != null) {
            fieldDetails.setColumn(column);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }
        if (value != null) {
            fieldDetails.setValue(value);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field date", help = "Adds a private date field to an existing Java source file")
    public void addFieldDateJpa(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, optionContext = "java-date", help = "The Java type of the entity") final JavaType fieldType,
            @CliOption(key = "persistenceType", mandatory = false, help = "The type of persistent storage to be used") final DateFieldPersistenceType persistenceType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "future", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be in the future") final boolean future,
            @CliOption(key = "past", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be in the past") final boolean past,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords,
            @CliOption(key = "dateFormat", mandatory = false, unspecifiedDefaultValue = "MEDIUM", specifiedDefaultValue = "MEDIUM", help = "Indicates the style of the date format (ignored if dateTimeFormatPattern is specified)") final DateTime dateFormat,
            @CliOption(key = "timeFormat", mandatory = false, unspecifiedDefaultValue = "NONE", specifiedDefaultValue = "NONE", help = "Indicates the style of the time format (ignored if dateTimeFormatPattern is specified)") final DateTime timeFormat,
            @CliOption(key = "dateTimeFormatPattern", mandatory = false, help = "Indicates a DateTime format pattern such as yyyy-MM-dd hh:mm:ss a") final String pattern) {

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final DateField fieldDetails = new DateField(physicalTypeIdentifier,
                fieldType, fieldName);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        fieldDetails.setFuture(future);
        fieldDetails.setPast(past);
        if (JdkJavaType.isDateField(fieldType)) {
            fieldDetails
                    .setPersistenceType(persistenceType != null ? persistenceType
                            : DateFieldPersistenceType.JPA_TIMESTAMP);
        }
        if (column != null) {
            fieldDetails.setColumn(column);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }
        if (dateFormat != null) {
            fieldDetails.setDateFormat(dateFormat);
        }
        if (timeFormat != null) {
            fieldDetails.setTimeFormat(timeFormat);
        }
        if (pattern != null) {
            fieldDetails.setPattern(pattern);
        }
        if (value != null) {
            fieldDetails.setValue(value);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field embedded", help = "Adds a private @Embedded field to an existing Java source file ")
    public void addFieldEmbeddedJpa(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, optionContext = PROJECT, help = "The Java type of the @Embeddable class") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the @Entity class to receive this field") final JavaType typeName,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        // Check if the field type is a JPA @Embeddable class
        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(fieldType);
        Validate.notNull(
                cid,
                "The specified target '--type' does not exist or can not be found. Please create this type first.");
        Validate.notNull(cid.getAnnotation(EMBEDDABLE),
                "The field embedded command is only applicable to JPA @Embeddable field types.");

        // Check if the requested entity is a JPA @Entity
        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final PhysicalTypeMetadata targetTypeMetadata = (PhysicalTypeMetadata) metadataService
                .get(physicalTypeIdentifier);
        Validate.notNull(
                targetTypeMetadata,
                "The specified target '--class' does not exist or can not be found. Please create this type first.");
        final PhysicalTypeDetails targetPtd = targetTypeMetadata
                .getMemberHoldingTypeDetails();
        Validate.isInstanceOf(MemberHoldingTypeDetails.class, targetPtd);

        final ClassOrInterfaceTypeDetails targetTypeCid = (ClassOrInterfaceTypeDetails) targetPtd;
        final MemberDetails memberDetails = memberDetailsScanner
                .getMemberDetails(this.getClass().getName(), targetTypeCid);
        Validate.isTrue(
                memberDetails.getAnnotation(ENTITY) != null
                        || memberDetails.getAnnotation(PERSISTENT) != null,
                "The field embedded command is only applicable to JPA @Entity or Spring Data @Persistent target types.");

        final EmbeddedField fieldDetails = new EmbeddedField(
                physicalTypeIdentifier, fieldType, fieldName);

        insertField(fieldDetails, permitReservedWords, false);
    }

    @CliCommand(value = "field enum", help = "Adds a private enum field to an existing Java source file")
    public void addFieldEnum(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, help = "The enum type of this field") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "enumType", mandatory = false, help = "The fetch semantics at a JPA level") final EnumType enumType,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(cid, "The type specified, '%s', doesn't exist",
                typeName);

        final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
        final EnumField fieldDetails = new EnumField(physicalTypeIdentifier,
                fieldType, fieldName);
        if (column != null) {
            fieldDetails.setColumn(column);
        }
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (enumType != null) {
            fieldDetails.setEnumType(enumType);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field number", help = "Adds a private numeric field to an existing Java source file")
    public void addFieldNumber(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, optionContext = "java-number", help = "The Java type of the entity") JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "decimalMin", mandatory = false, help = "The BigDecimal string-based representation of the minimum value") final String decimalMin,
            @CliOption(key = "decimalMax", mandatory = false, help = "The BigDecimal string based representation of the maximum value") final String decimalMax,
            @CliOption(key = "digitsInteger", mandatory = false, help = "Maximum number of integral digits accepted for this number") final Integer digitsInteger,
            @CliOption(key = "digitsFraction", mandatory = false, help = "Maximum number of fractional digits accepted for this number") final Integer digitsFraction,
            @CliOption(key = "min", mandatory = false, help = "The minimum value") final Long min,
            @CliOption(key = "max", mandatory = false, help = "The maximum value") final Long max,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "primitive", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to use a primitive type if possible") final boolean primitive,
            @CliOption(key = "unique", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether to mark the field with a unique constraint") final boolean unique,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        if (primitive
                && legalNumericPrimitives.contains(fieldType
                        .getFullyQualifiedTypeName())) {
            fieldType = new JavaType(fieldType.getFullyQualifiedTypeName(), 0,
                    DataType.PRIMITIVE, null, null);
        }
        final NumericField fieldDetails = new NumericField(
                physicalTypeIdentifier, fieldType, fieldName);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (decimalMin != null) {
            fieldDetails.setDecimalMin(decimalMin);
        }
        if (decimalMax != null) {
            fieldDetails.setDecimalMax(decimalMax);
        }
        if (digitsInteger != null) {
            fieldDetails.setDigitsInteger(digitsInteger);
        }
        if (digitsFraction != null) {
            fieldDetails.setDigitsFraction(digitsFraction);
        }
        if (min != null) {
            fieldDetails.setMin(min);
        }
        if (max != null) {
            fieldDetails.setMax(max);
        }
        if (column != null) {
            fieldDetails.setColumn(column);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }
        if (unique) {
            fieldDetails.setUnique(true);
        }
        if (value != null) {
            fieldDetails.setValue(value);
        }

        Validate.isTrue(
                fieldDetails.isDigitsSetCorrectly(),
                "Must specify both --digitsInteger and --digitsFractional for @Digits to be added");

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field reference", help = "Adds a private reference field to an existing Java source file (eg the 'many' side of a many-to-one)")
    public void addFieldReferenceJpa(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, optionContext = PROJECT, help = "The Java type of the entity to reference") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "joinColumnName", mandatory = false, help = "The JPA @JoinColumn name") final String joinColumnName,
            @CliOption(key = "referencedColumnName", mandatory = false, help = "The JPA @JoinColumn referencedColumnName") final String referencedColumnName,
            @CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_ONE", specifiedDefaultValue = "MANY_TO_ONE", help = "The relationship cardinality at a JPA level") final Cardinality cardinality,
            @CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(fieldType);
        Validate.notNull(
                cid,
                "The specified target '--type' does not exist or can not be found. Please create this type first.");

        // Check if the requested entity is a JPA @Entity
        final MemberDetails memberDetails = memberDetailsScanner
                .getMemberDetails(this.getClass().getName(), cid);
        final AnnotationMetadata entityAnnotation = memberDetails
                .getAnnotation(ENTITY);
        final AnnotationMetadata persistentAnnotation = memberDetails
                .getAnnotation(PERSISTENT);
        Validate.isTrue(
                entityAnnotation != null || persistentAnnotation != null,
                "The field reference command is only applicable to JPA @Entity or Spring Data @Persistent target types.");

        Validate.isTrue(cardinality == Cardinality.MANY_TO_ONE
                || cardinality == Cardinality.ONE_TO_ONE,
                "Cardinality must be MANY_TO_ONE or ONE_TO_ONE for the field reference command");

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final ReferenceField fieldDetails = new ReferenceField(
                physicalTypeIdentifier, fieldType, fieldName, cardinality);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (joinColumnName != null) {
            fieldDetails.setJoinColumnName(joinColumnName);
        }
        if (referencedColumnName != null) {
            Validate.notNull(joinColumnName,
                    "@JoinColumn name is required if specifying a referencedColumnName");
            fieldDetails.setReferencedColumnName(referencedColumnName);
        }
        if (fetch != null) {
            fieldDetails.setFetch(fetch);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field set", help = "Adds a private Set field to an existing Java source file (eg the 'one' side of a many-to-one)")
    public void addFieldSetJpa(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, help = "The entity which will be contained within the Set") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "mappedBy", mandatory = false, help = "The field name on the referenced type which owns the relationship") final JavaSymbolName mappedBy,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "sizeMin", mandatory = false, help = "The minimum number of elements in the collection") final Integer sizeMin,
            @CliOption(key = "sizeMax", mandatory = false, help = "The maximum number of elements in the collection") final Integer sizeMax,
            @CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_MANY", specifiedDefaultValue = "MANY_TO_MANY", help = "The relationship cardinality at a JPA level") Cardinality cardinality,
            @CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(fieldType);
        Validate.notNull(
                cid,
                "The specified target '--type' does not exist or can not be found. Please create this type first.");

        // Check if the requested entity is a JPA @Entity
        final MemberDetails memberDetails = memberDetailsScanner
                .getMemberDetails(this.getClass().getName(), cid);
        final AnnotationMetadata entityAnnotation = memberDetails
                .getAnnotation(ENTITY);
        final AnnotationMetadata persistentAnnotation = memberDetails
                .getAnnotation(PERSISTENT);

        if (entityAnnotation != null) {
            Validate.isTrue(cardinality == Cardinality.ONE_TO_MANY
                    || cardinality == Cardinality.MANY_TO_MANY,
                    "Cardinality must be ONE_TO_MANY or MANY_TO_MANY for the field set command");
        }
        else if (cid.getPhysicalTypeCategory() == PhysicalTypeCategory.ENUMERATION) {
            cardinality = null;
        }
        else if (persistentAnnotation != null) {
            // Yes, we can deal with that
        }
        else {
            throw new IllegalStateException(
                    "The field set command is only applicable to enum, JPA @Entity or Spring Data @Persistence elements");
        }

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s', doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final SetField fieldDetails = new SetField(physicalTypeIdentifier,
                new JavaType(SET.getFullyQualifiedTypeName(), 0, DataType.TYPE,
                        null, Arrays.asList(fieldType)), fieldName, fieldType,
                cardinality);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (sizeMin != null) {
            fieldDetails.setSizeMin(sizeMin);
        }
        if (sizeMax != null) {
            fieldDetails.setSizeMax(sizeMax);
        }
        if (mappedBy != null) {
            fieldDetails.setMappedBy(mappedBy);
        }
        if (fetch != null) {
            fieldDetails.setFetch(fetch);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field list", help = "Adds a private List field to an existing Java source file (eg the 'one' side of a many-to-one)")
    public void addFieldListJpa(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, help = "The entity which will be contained within the Set") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = "update,project", help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "mappedBy", mandatory = false, help = "The field name on the referenced type which owns the relationship") final JavaSymbolName mappedBy,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "sizeMin", mandatory = false, help = "The minimum number of elements in the collection") final Integer sizeMin,
            @CliOption(key = "sizeMax", mandatory = false, help = "The maximum number of elements in the collection") final Integer sizeMax,
            @CliOption(key = "cardinality", mandatory = false, unspecifiedDefaultValue = "MANY_TO_MANY", specifiedDefaultValue = "MANY_TO_MANY", help = "The relationship cardinality at a JPA level") Cardinality cardinality,
            @CliOption(key = "fetch", mandatory = false, help = "The fetch semantics at a JPA level") final Fetch fetch,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(fieldType);
        Validate.notNull(
                cid,
                "The specified target '--type' does not exist or can not be found. Please create this type first.");

        // Check if the requested entity is a JPA @Entity
        final MemberDetails memberDetails = memberDetailsScanner
                .getMemberDetails(this.getClass().getName(), cid);
        final AnnotationMetadata entityAnnotation = memberDetails
                .getAnnotation(ENTITY);
        final AnnotationMetadata persistentAnnotation = memberDetails
                .getAnnotation(PERSISTENT);

        if (entityAnnotation != null) {
            Validate.isTrue(cardinality == Cardinality.ONE_TO_MANY
                    || cardinality == Cardinality.MANY_TO_MANY,
                    "Cardinality must be ONE_TO_MANY or MANY_TO_MANY for the field list command");
        }
        else if (cid.getPhysicalTypeCategory() == PhysicalTypeCategory.ENUMERATION) {
            cardinality = null;
        }
        else if (persistentAnnotation != null) {
            // Yes, we can deal with that
        }
        else {
            throw new IllegalStateException(
                    "The field list command is only applicable to enum, JPA @Entity or Spring Data @Persistence elements");
        }

        final ClassOrInterfaceTypeDetails javaTypeDetails = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(javaTypeDetails,
                "The type specified, '%s' doesn't exist", typeName);

        final String physicalTypeIdentifier = javaTypeDetails
                .getDeclaredByMetadataId();
        final ListField fieldDetails = new ListField(physicalTypeIdentifier,
                new JavaType(LIST.getFullyQualifiedTypeName(), 0,
                        DataType.TYPE, null, Arrays.asList(fieldType)),
                fieldName, fieldType, cardinality);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (sizeMin != null) {
            fieldDetails.setSizeMin(sizeMin);
        }
        if (sizeMax != null) {
            fieldDetails.setSizeMax(sizeMax);
        }
        if (mappedBy != null) {
            fieldDetails.setMappedBy(mappedBy);
        }
        if (fetch != null) {
            fieldDetails.setFetch(fetch);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field string", help = "Adds a private string field to an existing Java source file")
    public void addFieldString(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the field to add") final JavaSymbolName fieldName,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "decimalMin", mandatory = false, help = "The BigDecimal string-based representation of the minimum value") final String decimalMin,
            @CliOption(key = "decimalMax", mandatory = false, help = "The BigDecimal string based representation of the maximum value") final String decimalMax,
            @CliOption(key = "sizeMin", mandatory = false, help = "The minimum string length") final Integer sizeMin,
            @CliOption(key = "sizeMax", mandatory = false, help = "The maximum string length") final Integer sizeMax,
            @CliOption(key = "regexp", mandatory = false, help = "The required regular expression pattern") final String regexp,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "unique", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether to mark the field with a unique constraint") final boolean unique,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords,
          @CliOption(key = "lob", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates that this field is a Large Object") final boolean lob) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(cid, "The type specified, '%s', doesn't exist",
                typeName);

        final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
        final StringField fieldDetails = new StringField(
                physicalTypeIdentifier, fieldName);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (decimalMin != null) {
            fieldDetails.setDecimalMin(decimalMin);
        }
        if (decimalMax != null) {
            fieldDetails.setDecimalMax(decimalMax);
        }
        if (sizeMin != null) {
            fieldDetails.setSizeMin(sizeMin);
        }
        if (sizeMax != null) {
            fieldDetails.setSizeMax(sizeMax);
        }
        if (regexp != null) {
            fieldDetails.setRegexp(regexp.replace("\\", "\\\\"));
        }
        if (column != null) {
            fieldDetails.setColumn(column);
        }
        if (comment != null) {
            fieldDetails.setComment(comment);
        }
        if (unique) {
            fieldDetails.setUnique(true);
        }
        if (value != null) {
            fieldDetails.setValue(value);
        }

    if (lob) {
      fieldDetails.getInitedAnnotations().add(
          new AnnotationMetadataBuilder("javax.persistence.Lob"));
    }
        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliCommand(value = "field file", help = "Adds a byte array field for storing uploaded file contents (JSF-scaffolded UIs only)")
    public void addFileUploadField(
            @CliOption(key = { "", "fieldName" }, mandatory = true, help = "The name of the file upload field to add") final JavaSymbolName fieldName,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "contentType", mandatory = true, help = "The content type of the file") final UploadedFileContentType contentType,
            @CliOption(key = "autoUpload", mandatory = false, specifiedDefaultValue = "true", unspecifiedDefaultValue = "false", help = "Whether the file is uploaded automatically when selected") final boolean autoUpload,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(cid, "The type specified, '%s', doesn't exist",
                typeName);

        final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
        final UploadedFileField fieldDetails = new UploadedFileField(
                physicalTypeIdentifier, fieldName, contentType);
        fieldDetails.setAutoUpload(autoUpload);
        fieldDetails.setNotNull(notNull);
        if (column != null) {
            fieldDetails.setColumn(column);
        }

        insertField(fieldDetails, permitReservedWords, false);
    }

    protected void deactivate(final ComponentContext context) {
        staticFieldConverter.remove(Cardinality.class);
        staticFieldConverter.remove(Fetch.class);
        staticFieldConverter.remove(EnumType.class);
        staticFieldConverter.remove(DateTime.class);
    }

    private void insertField(final FieldDetails fieldDetails,
            final boolean permitReservedWords, final boolean transientModifier) {
        if (!permitReservedWords) {
            ReservedWords.verifyReservedWordsNotPresent(fieldDetails
                    .getFieldName());
            if (fieldDetails.getColumn() != null) {
                ReservedWords.verifyReservedWordsNotPresent(fieldDetails
                        .getColumn());
            }
        }

    final List<AnnotationMetadataBuilder> annotations = fieldDetails
        .getInitedAnnotations();
        fieldDetails.decorateAnnotationsList(annotations);
        fieldDetails.setAnnotations(annotations);

        String initializer = null;
        if (fieldDetails instanceof CollectionField) {
            final CollectionField collectionField = (CollectionField) fieldDetails;
            initializer = "new " + collectionField.getInitializer() + "()";
        }
        else if (fieldDetails instanceof DateField
                && fieldDetails.getFieldName().getSymbolName()
                        .equals("created")) {
            initializer = "new Date()";
        }
        int modifier = Modifier.PRIVATE;
        if (transientModifier) {
            modifier += Modifier.TRANSIENT;
        }
        fieldDetails.setModifiers(modifier);

        // Format the passed-in comment (if given)
        formatFieldComment(fieldDetails);

        final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(
                fieldDetails);
        fieldBuilder.setFieldInitializer(initializer);
        typeManagementService.addField(fieldBuilder.build());
    }

    private void formatFieldComment(FieldDetails fieldDetails) {
        // If a comment was defined, we need to format it
        if (fieldDetails.getComment() != null) {

            // First replace all "" with the proper escape sequence \"
            String unescapedMultiLineComment = fieldDetails.getComment()
                    .replaceAll("\"\"", "\\\\\"");

            // Then unescape all characters
            unescapedMultiLineComment = StringEscapeUtils
                    .unescapeJava(unescapedMultiLineComment);

            CommentFormatter commentFormatter = new CommentFormatter();
            String javadocComment = commentFormatter
                    .formatStringAsJavadoc(unescapedMultiLineComment);

            fieldDetails.setComment(commentFormatter.format(javadocComment, 1));
        }
    }

    @CliCommand(value = "field other", help = "Inserts a private field into the specified file")
    public void insertField(
            @CliOption(key = "fieldName", mandatory = true, help = "The name of the field") final JavaSymbolName fieldName,
            @CliOption(key = "type", mandatory = true, help = "The Java type of this field") final JavaType fieldType,
            @CliOption(key = "class", mandatory = false, unspecifiedDefaultValue = "*", optionContext = UPDATE_PROJECT, help = "The name of the class to receive this field") final JavaType typeName,
            @CliOption(key = "notNull", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value cannot be null") final boolean notNull,
            @CliOption(key = "nullRequired", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Whether this value must be null") final boolean nullRequired,
            @CliOption(key = "comment", mandatory = false, help = "An optional comment for JavaDocs") final String comment,
            @CliOption(key = "column", mandatory = false, help = "The JPA @Column name") final String column,
            @CliOption(key = "value", mandatory = false, help = "Inserts an optional Spring @Value annotation with the given content") final String value,
            @CliOption(key = "transient", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates to mark the field as transient") final boolean transientModifier,
            @CliOption(key = "permitReservedWords", mandatory = false, unspecifiedDefaultValue = "false", specifiedDefaultValue = "true", help = "Indicates whether reserved words are ignored by Roo") final boolean permitReservedWords) {

        final ClassOrInterfaceTypeDetails cid = typeLocationService
                .getTypeDetails(typeName);
        Validate.notNull(cid, "The type specified, '%s', doesn't exist",
                typeName);

        final String physicalTypeIdentifier = cid.getDeclaredByMetadataId();
        final FieldDetails fieldDetails = new FieldDetails(
                physicalTypeIdentifier, fieldType, fieldName);
        fieldDetails.setNotNull(notNull);
        fieldDetails.setNullRequired(nullRequired);
        if (comment != null) {
            fieldDetails.setComment(comment);
        }
        if (column != null) {
            fieldDetails.setColumn(column);
        }

        insertField(fieldDetails, permitReservedWords, transientModifier);
    }

    @CliAvailabilityIndicator({ "field other", "field number", "field string",
            "field date", "field boolean", "field enum", "field embedded",
            "field file" })
    public boolean isJdkFieldManagementAvailable() {
        return projectOperations.isFocusedProjectAvailable();
    }

    @CliAvailabilityIndicator({ "field reference", "field set", "field list" })
    public boolean isJpaFieldManagementAvailable() {
        // In a separate method in case we decide to check for JPA registration
        // in the future
        return projectOperations.isFocusedProjectAvailable();
    }
}
TOP

Related Classes of org.springframework.roo.classpath.operations.FieldCommands

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.