Package org.springframework.roo.addon.dod

Source Code of org.springframework.roo.addon.dod.DataOnDemandMetadata

package org.springframework.roo.addon.dod;

import static org.springframework.roo.model.HibernateJavaType.VALIDATOR_CONSTRAINTS_EMAIL;
import static org.springframework.roo.model.JavaType.STRING;
import static org.springframework.roo.model.JdkJavaType.ARRAY_LIST;
import static org.springframework.roo.model.JdkJavaType.BIG_DECIMAL;
import static org.springframework.roo.model.JdkJavaType.BIG_INTEGER;
import static org.springframework.roo.model.JdkJavaType.CALENDAR;
import static org.springframework.roo.model.JdkJavaType.DATE;
import static org.springframework.roo.model.JdkJavaType.GREGORIAN_CALENDAR;
import static org.springframework.roo.model.JdkJavaType.ITERATOR;
import static org.springframework.roo.model.JdkJavaType.LIST;
import static org.springframework.roo.model.JdkJavaType.RANDOM;
import static org.springframework.roo.model.JdkJavaType.SECURE_RANDOM;
import static org.springframework.roo.model.JdkJavaType.TIMESTAMP;
import static org.springframework.roo.model.JpaJavaType.JOIN_COLUMN;
import static org.springframework.roo.model.Jsr303JavaType.CONSTRAINT_VIOLATION;
import static org.springframework.roo.model.Jsr303JavaType.CONSTRAINT_VIOLATION_EXCEPTION;
import static org.springframework.roo.model.Jsr303JavaType.DECIMAL_MAX;
import static org.springframework.roo.model.Jsr303JavaType.DECIMAL_MIN;
import static org.springframework.roo.model.Jsr303JavaType.DIGITS;
import static org.springframework.roo.model.Jsr303JavaType.FUTURE;
import static org.springframework.roo.model.Jsr303JavaType.MAX;
import static org.springframework.roo.model.Jsr303JavaType.MIN;
import static org.springframework.roo.model.Jsr303JavaType.NOT_NULL;
import static org.springframework.roo.model.Jsr303JavaType.PAST;
import static org.springframework.roo.model.Jsr303JavaType.SIZE;
import static org.springframework.roo.model.SpringJavaType.AUTOWIRED;
import static org.springframework.roo.model.SpringJavaType.COMPONENT;

import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.math.NumberUtils;
import org.springframework.roo.classpath.PhysicalTypeIdentifierNamingUtils;
import org.springframework.roo.classpath.PhysicalTypeMetadata;
import org.springframework.roo.classpath.customdata.CustomDataKeys;
import org.springframework.roo.classpath.details.BeanInfoUtils;
import org.springframework.roo.classpath.details.FieldMetadata;
import org.springframework.roo.classpath.details.FieldMetadataBuilder;
import org.springframework.roo.classpath.details.MemberFindingUtils;
import org.springframework.roo.classpath.details.MethodMetadata;
import org.springframework.roo.classpath.details.MethodMetadataBuilder;
import org.springframework.roo.classpath.details.annotations.AnnotatedJavaType;
import org.springframework.roo.classpath.details.annotations.AnnotationAttributeValue;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadata;
import org.springframework.roo.classpath.details.annotations.AnnotationMetadataBuilder;
import org.springframework.roo.classpath.itd.AbstractItdTypeDetailsProvidingMetadataItem;
import org.springframework.roo.classpath.itd.InvocableMemberBodyBuilder;
import org.springframework.roo.classpath.layers.MemberTypeAdditions;
import org.springframework.roo.metadata.MetadataIdentificationUtils;
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.project.LogicalPath;

/**
* Metadata for {@link RooDataOnDemand}.
*
* @author Ben Alex
* @author Stefan Schmidt
* @author Alan Stewart
* @author Greg Turnquist
* @author Andrew Swan
* @since 1.0
*/
public class DataOnDemandMetadata extends
        AbstractItdTypeDetailsProvidingMetadataItem {

    private static final String INDEX_VAR = "index";
    private static final JavaSymbolName INDEX_SYMBOL = new JavaSymbolName(
            INDEX_VAR);
    private static final JavaSymbolName MAX_SYMBOL = new JavaSymbolName("max");
    private static final JavaSymbolName MIN_SYMBOL = new JavaSymbolName("min");
    private static final String OBJ_VAR = "obj";
    private static final JavaSymbolName OBJ_SYMBOL = new JavaSymbolName(OBJ_VAR);
    private static final String PROVIDES_TYPE_STRING = DataOnDemandMetadata.class
            .getName();
    private static final String PROVIDES_TYPE = MetadataIdentificationUtils
            .create(PROVIDES_TYPE_STRING);
    private static final JavaSymbolName VALUE = new JavaSymbolName("value");

    public static String createIdentifier(final JavaType javaType,
            final LogicalPath path) {
        return PhysicalTypeIdentifierNamingUtils.createIdentifier(
                PROVIDES_TYPE_STRING, javaType, path);
    }

    public static JavaType getJavaType(final String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getJavaType(
                PROVIDES_TYPE_STRING, metadataIdentificationString);
    }

    public static String getMetadataIdentiferType() {
        return PROVIDES_TYPE;
    }

    public static LogicalPath getPath(final String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.getPath(PROVIDES_TYPE_STRING,
                metadataIdentificationString);
    }

    public static boolean isValid(final String metadataIdentificationString) {
        return PhysicalTypeIdentifierNamingUtils.isValid(PROVIDES_TYPE_STRING,
                metadataIdentificationString);
    }

    private JavaSymbolName dataFieldName;
    private final Map<FieldMetadata, Map<FieldMetadata, String>> embeddedFieldInitializers = new LinkedHashMap<FieldMetadata, Map<FieldMetadata, String>>();
    private List<EmbeddedHolder> embeddedHolders;
    private EmbeddedIdHolder embeddedIdHolder;
    private JavaType entity;
    private final Map<FieldMetadata, String> fieldInitializers = new LinkedHashMap<FieldMetadata, String>();
    private MemberTypeAdditions findMethod;
    private MethodMetadata identifierAccessor;
    private JavaType identifierType;
    private MethodMetadata modifyMethod;
    private MethodMetadata newTransientEntityMethod;
    private MethodMetadata randomPersistentEntityMethod;
    private final List<JavaType> requiredDataOnDemandCollaborators = new ArrayList<JavaType>();
    private JavaSymbolName rndFieldName;
    private MethodMetadata specificPersistentEntityMethod;

    /**
     * Constructor
     *
     * @param identifier
     * @param aspectName
     * @param governorPhysicalTypeMetadata
     * @param annotationValues
     * @param identifierAccessor
     * @param findMethod
     * @param findEntriesMethod
     * @param persistMethod
     * @param flushMethod
     * @param locatedFields
     * @param entity
     * @param embeddedIdHolder
     * @param embeddedHolders
     */
    public DataOnDemandMetadata(final String identifier,
            final JavaType aspectName,
            final PhysicalTypeMetadata governorPhysicalTypeMetadata,
            final DataOnDemandAnnotationValues annotationValues,
            final MethodMetadata identifierAccessor,
            final MemberTypeAdditions findMethod,
            final MemberTypeAdditions findEntriesMethod,
            final MemberTypeAdditions persistMethod,
            final MemberTypeAdditions flushMethod,
            final Map<FieldMetadata, DataOnDemandMetadata> locatedFields,
            final JavaType identifierType,
            final EmbeddedIdHolder embeddedIdHolder,
            final List<EmbeddedHolder> embeddedHolders) {
        super(identifier, aspectName, governorPhysicalTypeMetadata);
        Validate.isTrue(
                isValid(identifier),
                "Metadata identification string '%s' does not appear to be a valid",
                identifier);
        Validate.notNull(annotationValues, "Annotation values required");
        Validate.notNull(identifierAccessor,
                "Identifier accessor method required");
        Validate.notNull(locatedFields, "Located fields map required");
        Validate.notNull(embeddedHolders, "Embedded holders list required");

        if (!isValid()) {
            return;
        }

        if (findEntriesMethod == null || persistMethod == null
                || findMethod == null) {
            valid = false;
            return;
        }

        this.embeddedIdHolder = embeddedIdHolder;
        this.embeddedHolders = embeddedHolders;
        this.identifierAccessor = identifierAccessor;
        this.findMethod = findMethod;
        this.identifierType = identifierType;
        entity = annotationValues.getEntity();

        // Calculate and store field initializers
        for (final Map.Entry<FieldMetadata, DataOnDemandMetadata> entry : locatedFields
                .entrySet()) {
            final FieldMetadata field = entry.getKey();
            final String initializer = getFieldInitializer(field,
                    entry.getValue());
            if (!StringUtils.isBlank(initializer)) {
                fieldInitializers.put(field, initializer);
            }
        }

        for (final EmbeddedHolder embeddedHolder : embeddedHolders) {
            final Map<FieldMetadata, String> initializers = new LinkedHashMap<FieldMetadata, String>();
            for (final FieldMetadata field : embeddedHolder.getFields()) {
                initializers.put(field, getFieldInitializer(field, null));
            }
            embeddedFieldInitializers.put(embeddedHolder.getEmbeddedField(),
                    initializers);
        }

        builder.addAnnotation(getComponentAnnotation());
        builder.addField(getRndField());
        builder.addField(getDataField());

        addCollaboratingDoDFieldsToBuilder();
        setNewTransientEntityMethod();

        builder.addMethod(getEmbeddedIdMutatorMethod());

        for (final EmbeddedHolder embeddedHolder : embeddedHolders) {
            builder.addMethod(getEmbeddedClassMutatorMethod(embeddedHolder));
            addEmbeddedClassFieldMutatorMethodsToBuilder(embeddedHolder);
        }

        for (final MethodMetadataBuilder fieldInitializerMethod : getFieldMutatorMethods()) {
            builder.addMethod(fieldInitializerMethod);
        }

        setSpecificPersistentEntityMethod();
        setRandomPersistentEntityMethod();
        setModifyMethod();
        builder.addMethod(getInitMethod(annotationValues.getQuantity(),
                findEntriesMethod, persistMethod, flushMethod));

        itdTypeDetails = builder.build();
    }

    private void addCollaboratingDoDFieldsToBuilder() {
        final Set<JavaSymbolName> fields = new LinkedHashSet<JavaSymbolName>();
        for (final JavaType entityNeedingCollaborator : requiredDataOnDemandCollaborators) {
            final JavaType collaboratorType = getCollaboratingType(entityNeedingCollaborator);
            final String collaboratingFieldName = getCollaboratingFieldName(
                    entityNeedingCollaborator).getSymbolName();

            final JavaSymbolName fieldSymbolName = new JavaSymbolName(
                    collaboratingFieldName);
            final FieldMetadata candidate = governorTypeDetails
                    .getField(fieldSymbolName);
            if (candidate != null) {
                // We really expect the field to be correct if we're going to
                // rely on it
                Validate.isTrue(
                        candidate.getFieldType().equals(collaboratorType),
                        "Field '%s' on '%s' must be of type '%s'",
                        collaboratingFieldName,
                        destination.getFullyQualifiedTypeName(),
                        collaboratorType.getFullyQualifiedTypeName());
                Validate.isTrue(Modifier.isPrivate(candidate.getModifier()),
                        "Field '%s' on '%s' must be private",
                        collaboratingFieldName,
                        destination.getFullyQualifiedTypeName());
                Validate.notNull(
                        MemberFindingUtils.getAnnotationOfType(
                                candidate.getAnnotations(), AUTOWIRED),
                        "Field '%s' on '%s' must be @Autowired",
                        collaboratingFieldName,
                        destination.getFullyQualifiedTypeName());
                // It's ok, so we can move onto the new field
                continue;
            }

            // Create field and add it to the ITD, if it hasn't already been
            if (!fields.contains(fieldSymbolName)) {
                // Must make the field
                final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
                annotations.add(new AnnotationMetadataBuilder(AUTOWIRED));
                builder.addField(new FieldMetadataBuilder(getId(), 0,
                        annotations, fieldSymbolName, collaboratorType));
                fields.add(fieldSymbolName);
            }
        }
    }

    private void addEmbeddedClassFieldMutatorMethodsToBuilder(
            final EmbeddedHolder embeddedHolder) {
        final JavaType embeddedFieldType = embeddedHolder.getEmbeddedField()
                .getFieldType();
        final JavaType[] parameterTypes = { embeddedFieldType,
                JavaType.INT_PRIMITIVE };
        final List<JavaSymbolName> parameterNames = Arrays.asList(OBJ_SYMBOL,
                INDEX_SYMBOL);

        for (final FieldMetadata field : embeddedHolder.getFields()) {
            final String initializer = getFieldInitializer(field, null);
            final JavaSymbolName fieldMutatorMethodName = BeanInfoUtils
                    .getMutatorMethodName(field.getFieldName());

            final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
            bodyBuilder.append(getFieldValidationBody(field, initializer,
                    fieldMutatorMethodName, false));

            final JavaSymbolName embeddedClassMethodName = getEmbeddedFieldMutatorMethodName(
                    embeddedHolder.getEmbeddedField().getFieldName(),
                    field.getFieldName());
            if (governorHasMethod(embeddedClassMethodName, parameterTypes)) {
                // Method found in governor so do not create method in ITD
                continue;
            }

            builder.addMethod(new MethodMetadataBuilder(getId(),
                    Modifier.PUBLIC, embeddedClassMethodName,
                    JavaType.VOID_PRIMITIVE, AnnotatedJavaType
                            .convertFromJavaTypes(parameterTypes),
                    parameterNames, bodyBuilder));
        }
    }

    private JavaSymbolName getCollaboratingFieldName(final JavaType entity) {
        return new JavaSymbolName(
                StringUtils.uncapitalize(getCollaboratingType(entity)
                        .getSimpleTypeName()));
    }

    private JavaType getCollaboratingType(final JavaType entity) {
        return new JavaType(entity.getFullyQualifiedTypeName() + "DataOnDemand");
    }

    private String getColumnPrecisionAndScaleBody(final FieldMetadata field,
            final Map<String, Object> values, final String suffix) {
        if (values == null || !values.containsKey("precision")) {
            return InvocableMemberBodyBuilder.getInstance().getOutput();
        }

        final String fieldName = field.getFieldName().getSymbolName();
        final JavaType fieldType = field.getFieldType();

        Integer precision = (Integer) values.get("precision");
        Integer scale = (Integer) values.get("scale");
        if (precision != null && scale != null && precision < scale) {
            scale = 0;
        }

        final BigDecimal maxValue;
        if (scale == null || scale == 0) {
            maxValue = new BigDecimal(StringUtils.rightPad("9", precision, '9'));
        } else {
            maxValue = new BigDecimal(StringUtils.rightPad("9",
                    precision - scale, '9')
                    + "."
                    + StringUtils.rightPad("9", scale, '9'));
        }

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        if (fieldType.equals(BIG_DECIMAL)) {
            bodyBuilder.appendFormalLine("if (" + fieldName + ".compareTo(new "
                    + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                    + "\")) == 1) {");
            bodyBuilder.indent();
            bodyBuilder.appendFormalLine(fieldName + " = new "
                    + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                    + "\");");
        }
        else {
            bodyBuilder.appendFormalLine("if (" + fieldName + " > "
                    + maxValue.doubleValue() + suffix + ") {");
            bodyBuilder.indent();
            bodyBuilder.appendFormalLine(fieldName + " = "
                    + maxValue.doubleValue() + suffix + ";");
        }

        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");

        return bodyBuilder.getOutput();
    }

    /**
     * Adds the @org.springframework.stereotype.Component annotation to the
     * type, unless it already exists.
     *
     * @return the annotation is already exists or will be created, or null if
     *         it will not be created (required)
     */
    public AnnotationMetadata getComponentAnnotation() {
        if (governorTypeDetails.getAnnotation(COMPONENT) != null) {
            return null;
        }
        final AnnotationMetadataBuilder annotationBuilder = new AnnotationMetadataBuilder(
                COMPONENT);
        return annotationBuilder.build();
    }

    /**
     * @return the "data" field to use, which is either provided by the user or
     *         produced on demand (never returns null)
     */
    private FieldMetadataBuilder getDataField() {
        final List<JavaType> parameterTypes = Arrays.asList(entity);
        final JavaType listType = new JavaType(
                LIST.getFullyQualifiedTypeName(), 0, DataType.TYPE, null,
                parameterTypes);

        int index = -1;
        while (true) {
            // Compute the required field name
            index++;

            // The type parameters to be used by the field type
            final JavaSymbolName fieldName = new JavaSymbolName("data"
                    + StringUtils.repeat("_", index));
            dataFieldName = fieldName;
            final FieldMetadata candidate = governorTypeDetails
                    .getField(fieldName);
            if (candidate != null) {
                // Verify if candidate is suitable
                if (!Modifier.isPrivate(candidate.getModifier())) {
                    // Candidate is not private, so we might run into naming
                    // clashes if someone subclasses this (therefore go onto the
                    // next possible name)
                    continue;
                }

                if (!candidate.getFieldType().equals(listType)) {
                    // Candidate isn't a java.util.List<theEntity>, so it isn't
                    // suitable
                    // The equals method also verifies type params are present
                    continue;
                }

                // If we got this far, we found a valid candidate
                // We don't check if there is a corresponding initializer, but
                // we assume the user knows what they're doing and have made one
                return new FieldMetadataBuilder(candidate);
            }

            // Candidate not found, so let's create one
            return new FieldMetadataBuilder(getId(), Modifier.PRIVATE,
                    new ArrayList<AnnotationMetadataBuilder>(), fieldName,
                    listType);
        }
    }

    private JavaSymbolName getDataFieldName() {
        return dataFieldName;
    }

    private String getDecimalMinAndDecimalMaxBody(final FieldMetadata field,
            final AnnotationMetadata decimalMinAnnotation,
            final AnnotationMetadata decimalMaxAnnotation, final String suffix) {
        final String fieldName = field.getFieldName().getSymbolName();
        final JavaType fieldType = field.getFieldType();

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        if (decimalMinAnnotation != null && decimalMaxAnnotation == null) {
            final String minValue = (String) decimalMinAnnotation.getAttribute(
                    VALUE).getValue();

            if (fieldType.equals(BIG_DECIMAL)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_DECIMAL.getSimpleTypeName()
                        + "(\"" + minValue + "\")) == -1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_DECIMAL.getSimpleTypeName() + "(\"" + minValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " < "
                        + minValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + minValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }
        else if (decimalMinAnnotation == null && decimalMaxAnnotation != null) {
            final String maxValue = (String) decimalMaxAnnotation.getAttribute(
                    VALUE).getValue();

            if (fieldType.equals(BIG_DECIMAL)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_DECIMAL.getSimpleTypeName()
                        + "(\"" + maxValue + "\")) == 1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " > "
                        + maxValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + maxValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }
        else if (decimalMinAnnotation != null && decimalMaxAnnotation != null) {
            final String minValue = (String) decimalMinAnnotation.getAttribute(
                    VALUE).getValue();
            final String maxValue = (String) decimalMaxAnnotation.getAttribute(
                    VALUE).getValue();
            Validate.isTrue(
                    Double.parseDouble(maxValue) >= Double
                            .parseDouble(minValue),
                    "The value of @DecimalMax must be greater or equal to the value of @DecimalMin for field %s",
                    fieldName);

            if (fieldType.equals(BIG_DECIMAL)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_DECIMAL.getSimpleTypeName()
                        + "(\"" + minValue + "\")) == -1 || " + fieldName
                        + ".compareTo(new " + BIG_DECIMAL.getSimpleTypeName()
                        + "(\"" + maxValue + "\")) == 1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " < "
                        + minValue + suffix + " || " + fieldName + " > "
                        + maxValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + maxValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }

        return bodyBuilder.getOutput();
    }

    private String getDigitsBody(final FieldMetadata field,
            final AnnotationMetadata digitsAnnotation, final String suffix) {
        final String fieldName = field.getFieldName().getSymbolName();
        final JavaType fieldType = field.getFieldType();

        final Integer integerValue = (Integer) digitsAnnotation.getAttribute(
                new JavaSymbolName("integer")).getValue();
        final Integer fractionValue = (Integer) digitsAnnotation.getAttribute(
                new JavaSymbolName("fraction")).getValue();
        final BigDecimal maxValue = new BigDecimal(StringUtils.rightPad("9",
                integerValue, '9')
                + "."
                + StringUtils.rightPad("9", fractionValue, '9'));

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        if (fieldType.equals(BIG_DECIMAL)) {
            bodyBuilder.appendFormalLine("if (" + fieldName + ".compareTo(new "
                    + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                    + "\")) == 1) {");
            bodyBuilder.indent();
            bodyBuilder.appendFormalLine(fieldName + " = new "
                    + BIG_DECIMAL.getSimpleTypeName() + "(\"" + maxValue
                    + "\");");
        }
        else {
            bodyBuilder.appendFormalLine("if (" + fieldName + " > "
                    + maxValue.doubleValue() + suffix + ") {");
            bodyBuilder.indent();
            bodyBuilder.appendFormalLine(fieldName + " = "
                    + maxValue.doubleValue() + suffix + ";");
        }

        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");

        return bodyBuilder.getOutput();
    }

    private MethodMetadataBuilder getEmbeddedClassMutatorMethod(
            final EmbeddedHolder embeddedHolder) {
        final JavaSymbolName methodName = getEmbeddedFieldMutatorMethodName(embeddedHolder
                .getEmbeddedField().getFieldName());
        final JavaType[] parameterTypes = { entity, JavaType.INT_PRIMITIVE };

        // Locate user-defined method
        if (governorHasMethod(methodName, parameterTypes)) {
            // Method found in governor so do not create method in ITD
            return null;
        }

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        // Create constructor for embedded class
        final JavaType embeddedFieldType = embeddedHolder.getEmbeddedField()
                .getFieldType();
        builder.getImportRegistrationResolver().addImport(embeddedFieldType);
        bodyBuilder.appendFormalLine(embeddedFieldType.getSimpleTypeName()
                + " embeddedClass = new "
                + embeddedFieldType.getSimpleTypeName() + "();");
        for (final FieldMetadata field : embeddedHolder.getFields()) {
            bodyBuilder.appendFormalLine(getEmbeddedFieldMutatorMethodName(
                    embeddedHolder.getEmbeddedField().getFieldName(),
                    field.getFieldName()).getSymbolName()
                    + "(embeddedClass, " + INDEX_VAR + ");");
        }
        bodyBuilder.appendFormalLine(OBJ_VAR + "."
                + embeddedHolder.getEmbeddedMutatorMethodName()
                + "(embeddedClass);");

        final List<JavaSymbolName> parameterNames = Arrays.asList(OBJ_SYMBOL,
                INDEX_SYMBOL);

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName,
                JavaType.VOID_PRIMITIVE,
                AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                parameterNames, bodyBuilder);
    }

    private JavaSymbolName getEmbeddedFieldMutatorMethodName(
            final JavaSymbolName fieldName) {
        return BeanInfoUtils.getMutatorMethodName(fieldName);
    }

    private JavaSymbolName getEmbeddedFieldMutatorMethodName(
            final JavaSymbolName embeddedFieldName,
            final JavaSymbolName fieldName) {
        return getEmbeddedFieldMutatorMethodName(new JavaSymbolName(
                embeddedFieldName.getSymbolName()
                        + StringUtils.capitalize(fieldName.getSymbolName())));
    }

    private MethodMetadataBuilder getEmbeddedIdMutatorMethod() {
        if (!hasEmbeddedIdentifier()) {
            return null;
        }

        final JavaSymbolName embeddedIdMutator = embeddedIdHolder
                .getEmbeddedIdMutator();
        final JavaSymbolName methodName = getEmbeddedIdMutatorMethodName();
        final JavaType[] parameterTypes = { entity, JavaType.INT_PRIMITIVE };

        // Locate user-defined method
        if (governorHasMethod(methodName, parameterTypes)) {
            // Method found in governor so do not create method in ITD
            return null;
        }

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        // Create constructor for embedded id class
        final JavaType embeddedIdFieldType = embeddedIdHolder
                .getEmbeddedIdField().getFieldType();
        builder.getImportRegistrationResolver().addImport(embeddedIdFieldType);

        final StringBuilder sb = new StringBuilder();
        final List<FieldMetadata> identifierFields = embeddedIdHolder
                .getIdFields();
        for (int i = 0, n = identifierFields.size(); i < n; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            final FieldMetadata field = identifierFields.get(i);
            final String fieldName = field.getFieldName().getSymbolName();
            final JavaType fieldType = field.getFieldType();
            builder.getImportRegistrationResolver().addImport(fieldType);
            final String initializer = getFieldInitializer(field, null);
            bodyBuilder.append(getFieldValidationBody(field, initializer, null,
                    true));
            sb.append(fieldName);
        }
        bodyBuilder.appendFormalLine("");
        bodyBuilder.appendFormalLine(embeddedIdFieldType.getSimpleTypeName()
                + " embeddedIdClass = new "
                + embeddedIdFieldType.getSimpleTypeName() + "(" + sb.toString()
                + ");");
        bodyBuilder.appendFormalLine(OBJ_VAR + "." + embeddedIdMutator
                + "(embeddedIdClass);");

        final List<JavaSymbolName> parameterNames = Arrays.asList(OBJ_SYMBOL,
                INDEX_SYMBOL);

        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName,
                JavaType.VOID_PRIMITIVE,
                AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                parameterNames, bodyBuilder);
    }

    private JavaSymbolName getEmbeddedIdMutatorMethodName() {
        final List<JavaSymbolName> fieldNames = new ArrayList<JavaSymbolName>();
        for (final FieldMetadata field : fieldInitializers.keySet()) {
            fieldNames.add(field.getFieldName());
        }

        int index = -1;
        JavaSymbolName embeddedIdField;
        while (true) {
            // Compute the required field name
            index++;
            embeddedIdField = new JavaSymbolName("embeddedIdClass"
                    + StringUtils.repeat("_", index));
            if (!fieldNames.contains(embeddedIdField)) {
                // Found a usable name
                break;
            }
        }
        return BeanInfoUtils.getMutatorMethodName(embeddedIdField);
    }

    public JavaType getEntityType() {
        return entity;
    }

    private String getFieldInitializer(final FieldMetadata field,
            final DataOnDemandMetadata collaboratingMetadata) {
        final JavaType fieldType = field.getFieldType();
        final String fieldName = field.getFieldName().getSymbolName();
        String initializer = "null";
        final String fieldInitializer = field.getFieldInitializer();
        final Set<Object> fieldCustomDataKeys = field.getCustomData().keySet();

        // Date fields included for DataNucleus (
        if (fieldType.equals(DATE)) {
            if (MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    PAST) != null) {
                builder.getImportRegistrationResolver().addImport(DATE);
                initializer = "new Date(new Date().getTime() - 10000000L)";
            }
            else if (MemberFindingUtils.getAnnotationOfType(
                    field.getAnnotations(), FUTURE) != null) {
                builder.getImportRegistrationResolver().addImport(DATE);
                initializer = "new Date(new Date().getTime() + 10000000L)";
            }
            else {
                builder.getImportRegistrationResolver().addImports(CALENDAR,
                        GREGORIAN_CALENDAR);
                initializer = "new GregorianCalendar(Calendar.getInstance().get(Calendar.YEAR), Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(Calendar.DAY_OF_MONTH), Calendar.getInstance().get(Calendar.HOUR_OF_DAY), Calendar.getInstance().get(Calendar.MINUTE), Calendar.getInstance().get(Calendar.SECOND) + new Double(Math.random() * 1000).intValue()).getTime()";
            }
        }
        else if (fieldType.equals(CALENDAR)) {
            builder.getImportRegistrationResolver().addImports(CALENDAR,
                    GREGORIAN_CALENDAR);

            final String calendarString = "new GregorianCalendar(Calendar.getInstance().get(Calendar.YEAR), Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(Calendar.DAY_OF_MONTH)";
            if (MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    PAST) != null) {
                initializer = calendarString + " - 1)";
            }
            else if (MemberFindingUtils.getAnnotationOfType(
                    field.getAnnotations(), FUTURE) != null) {
                initializer = calendarString + " + 1)";
            }
            else {
                initializer = "Calendar.getInstance()";
            }
        }
        else if (fieldType.equals(TIMESTAMP)) {
            builder.getImportRegistrationResolver().addImports(CALENDAR,
                    GREGORIAN_CALENDAR, TIMESTAMP);
            initializer = "new Timestamp(new GregorianCalendar(Calendar.getInstance().get(Calendar.YEAR), Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(Calendar.DAY_OF_MONTH), Calendar.getInstance().get(Calendar.HOUR_OF_DAY), Calendar.getInstance().get(Calendar.MINUTE), Calendar.getInstance().get(Calendar.SECOND) + new Double(Math.random() * 1000).intValue()).getTime().getTime())";
        }
        else if (fieldType.equals(STRING)) {
            if (fieldInitializer != null && fieldInitializer.contains("\"")) {
                final int offset = fieldInitializer.indexOf("\"");
                initializer = fieldInitializer.substring(offset + 1,
                        fieldInitializer.lastIndexOf("\""));
            }
            else {
                initializer = fieldName;
            }

            if (MemberFindingUtils.getAnnotationOfType(field.getAnnotations(),
                    VALIDATOR_CONSTRAINTS_EMAIL) != null
                    || fieldName.toLowerCase().contains("email")) {
                initializer = "\"foo\" + " + INDEX_VAR + " + \"@bar.com\"";
            }
            else {
                int maxLength = Integer.MAX_VALUE;

                // Check for @Size
                final AnnotationMetadata sizeAnnotation = MemberFindingUtils
                        .getAnnotationOfType(field.getAnnotations(), SIZE);
                if (sizeAnnotation != null) {
                    final AnnotationAttributeValue<?> maxValue = sizeAnnotation
                            .getAttribute(MAX_SYMBOL);
                    if (maxValue != null) {
                        validateNumericAnnotationAttribute(fieldName, "@Size",
                                "max", maxValue.getValue());
                        maxLength = ((Integer) maxValue.getValue()).intValue();
                    }
                    final AnnotationAttributeValue<?> minValue = sizeAnnotation
                            .getAttribute(MIN_SYMBOL);
                    if (minValue != null) {
                        validateNumericAnnotationAttribute(fieldName, "@Size",
                                "min", minValue.getValue());
                        final int minLength = ((Integer) minValue.getValue())
                                .intValue();
                        Validate.isTrue(
                                maxLength >= minLength,
                                "@Size attribute 'max' must be greater than 'min' for field '%s' in %s",
                                fieldName, entity.getFullyQualifiedTypeName());
                        if (initializer.length() + 2 < minLength) {
                            initializer = String
                                    .format("%1$-" + (minLength - 2) + "s",
                                            initializer).replace(' ', 'x');
                        }
                    }
                }
                else {
                    if (field.getCustomData().keySet()
                            .contains(CustomDataKeys.COLUMN_FIELD)) {
                        @SuppressWarnings("unchecked")
                        final Map<String, Object> columnValues = (Map<String, Object>) field
                                .getCustomData().get(
                                        CustomDataKeys.COLUMN_FIELD);
                        if (columnValues.keySet().contains("length")) {
                            final Object lengthValue = columnValues
                                    .get("length");
                            validateNumericAnnotationAttribute(fieldName,
                                    "@Column", "length", lengthValue);
                            maxLength = ((Integer) lengthValue).intValue();
                        }
                    }
                }

                switch (maxLength) {
                case 0:
                    initializer = "\"\"";
                    break;
                case 1:
                    initializer = "String.valueOf(" + INDEX_VAR + ")";
                    break;
                case 2:
                    initializer = "\"" + initializer.charAt(0) + "\" + "
                            + INDEX_VAR;
                    break;
                default:
                    if (initializer.length() + 2 > maxLength) {
                        initializer = "\""
                                + initializer.substring(0, maxLength - 2)
                                + "_\" + " + INDEX_VAR;
                    }
                    else {
                        initializer = "\"" + initializer + "_\" + " + INDEX_VAR;
                    }
                }
            }
        }
        else if (fieldType.equals(new JavaType(STRING
                .getFullyQualifiedTypeName(), 1, DataType.TYPE, null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ \"Y\", \"N\" }");
        }
        else if (fieldType.equals(JavaType.BOOLEAN_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "Boolean.TRUE");
        }
        else if (fieldType.equals(JavaType.BOOLEAN_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer, "true");
        }
        else if (fieldType.equals(JavaType.INT_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ")");
        }
        else if (fieldType.equals(JavaType.INT_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    INDEX_VAR);
        }
        else if (fieldType
                .equals(new JavaType(JavaType.INT_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer, "{ "
                    + INDEX_VAR + ", " + INDEX_VAR + " }");
        }
        else if (fieldType.equals(JavaType.DOUBLE_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").doubleValue()"); // Auto-boxed
        }
        else if (fieldType.equals(JavaType.DOUBLE_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").doubleValue()");
        }
        else if (fieldType
                .equals(new JavaType(JavaType.DOUBLE_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ new Integer(" + INDEX_VAR
                            + ").doubleValue(), new Integer(" + INDEX_VAR
                            + ").doubleValue() }");
        }
        else if (fieldType.equals(JavaType.FLOAT_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").floatValue()"); // Auto-boxed
        }
        else if (fieldType.equals(JavaType.FLOAT_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").floatValue()");
        }
        else if (fieldType
                .equals(new JavaType(JavaType.FLOAT_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ new Integer(" + INDEX_VAR
                            + ").floatValue(), new Integer(" + INDEX_VAR
                            + ").floatValue() }");
        }
        else if (fieldType.equals(JavaType.LONG_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").longValue()"); // Auto-boxed
        }
        else if (fieldType.equals(JavaType.LONG_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").longValue()");
        }
        else if (fieldType
                .equals(new JavaType(JavaType.LONG_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ new Integer(" + INDEX_VAR
                            + ").longValue(), new Integer(" + INDEX_VAR
                            + ").longValue() }");
        }
        else if (fieldType.equals(JavaType.SHORT_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").shortValue()"); // Auto-boxed
        }
        else if (fieldType.equals(JavaType.SHORT_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Integer(" + INDEX_VAR + ").shortValue()");
        }
        else if (fieldType
                .equals(new JavaType(JavaType.SHORT_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ new Integer(" + INDEX_VAR
                            + ").shortValue(), new Integer(" + INDEX_VAR
                            + ").shortValue() }");
        }
        else if (fieldType.equals(JavaType.CHAR_OBJECT)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "new Character('N')");
        }
        else if (fieldType.equals(JavaType.CHAR_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer, "'N'");
        }
        else if (fieldType
                .equals(new JavaType(JavaType.CHAR_OBJECT
                        .getFullyQualifiedTypeName(), 1, DataType.PRIMITIVE,
                        null, null))) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "{ 'Y', 'N' }");
        }
        else if (fieldType.equals(BIG_DECIMAL)) {
            builder.getImportRegistrationResolver().addImport(BIG_DECIMAL);
            initializer = BIG_DECIMAL.getSimpleTypeName() + ".valueOf("
                    + INDEX_VAR + ")";
        }
        else if (fieldType.equals(BIG_INTEGER)) {
            builder.getImportRegistrationResolver().addImport(BIG_INTEGER);
            initializer = BIG_INTEGER.getSimpleTypeName() + ".valueOf("
                    + INDEX_VAR + ")";
        }
        else if (fieldType.equals(JavaType.BYTE_OBJECT)) {
            initializer = "new Byte("
                    + StringUtils.defaultIfEmpty(fieldInitializer, "\"1\"")
                    + ")";
        }
        else if (fieldType.equals(JavaType.BYTE_PRIMITIVE)) {
            initializer = "new Byte("
                    + StringUtils.defaultIfEmpty(fieldInitializer, "\"1\"")
                    + ").byteValue()";
        }
        else if (fieldType.equals(JavaType.BYTE_ARRAY_PRIMITIVE)) {
            initializer = StringUtils.defaultIfEmpty(fieldInitializer,
                    "String.valueOf(" + INDEX_VAR + ").getBytes()");
        }
        else if (fieldType.equals(entity)) {
            // Avoid circular references (ROO-562)
            initializer = OBJ_VAR;
        }
        else if (fieldCustomDataKeys.contains(CustomDataKeys.ENUMERATED_FIELD)) {
            builder.getImportRegistrationResolver().addImport(fieldType);
            initializer = fieldType.getSimpleTypeName()
                    + ".class.getEnumConstants()[0]";
        }
        else if (collaboratingMetadata != null
                && collaboratingMetadata.getEntityType() != null) {
            requiredDataOnDemandCollaborators.add(fieldType);
            initializer = getFieldInitializerForRelatedEntity(field,
                    collaboratingMetadata, fieldCustomDataKeys);
        }

        return initializer;
    }

    private String getFieldInitializerForRelatedEntity(
            final FieldMetadata field,
            final DataOnDemandMetadata collaboratingMetadata,
            final Set<?> fieldCustomDataKeys) {
        // To avoid circular references, we don't try to set nullable fields
        final boolean nullableField = field.getAnnotation(NOT_NULL) == null
                && isNullableJoinColumn(field);
        if (nullableField) {
            return null;
        }
        final String collaboratingFieldName = getCollaboratingFieldName(
                field.getFieldType()).getSymbolName();
        if (fieldCustomDataKeys.contains(CustomDataKeys.ONE_TO_ONE_FIELD)) {
            // We try to keep the same ID (ROO-568)
            return collaboratingFieldName
                    + "."
                    + collaboratingMetadata.getSpecificPersistentEntityMethod()
                            .getMethodName().getSymbolName() + "(" + INDEX_VAR
                    + ")";
        }
        return collaboratingFieldName
                + "."
                + collaboratingMetadata.getRandomPersistentEntityMethod()
                        .getMethodName().getSymbolName() + "()";
    }

    private boolean isNullableJoinColumn(final FieldMetadata field) {
        final AnnotationMetadata joinColumnAnnotation = field
                .getAnnotation(JOIN_COLUMN);
        if (joinColumnAnnotation == null) {
            return true;
        }
        final AnnotationAttributeValue<?> nullableAttr = joinColumnAnnotation
                .getAttribute(new JavaSymbolName("nullable"));
        return nullableAttr == null || (Boolean) nullableAttr.getValue();
    }

    private List<MethodMetadataBuilder> getFieldMutatorMethods() {
        final List<MethodMetadataBuilder> fieldMutatorMethods = new ArrayList<MethodMetadataBuilder>();
        final List<JavaSymbolName> parameterNames = Arrays.asList(OBJ_SYMBOL,
                INDEX_SYMBOL);
        final JavaType[] parameterTypes = { entity, JavaType.INT_PRIMITIVE };

        for (final Map.Entry<FieldMetadata, String> entry : fieldInitializers
                .entrySet()) {
            final FieldMetadata field = entry.getKey();
            final JavaSymbolName mutatorName = BeanInfoUtils
                    .getMutatorMethodName(field.getFieldName());

            // Locate user-defined method
            if (governorHasMethod(mutatorName, parameterTypes)) {
                // Method found in governor so do not create method in ITD
                continue;
            }

            // Method not on governor so need to create it
            final String initializer = entry.getValue();
            if (!StringUtils.isBlank(initializer)) {
                final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
                bodyBuilder.append(getFieldValidationBody(field, initializer,
                        mutatorName, false));

                fieldMutatorMethods.add(new MethodMetadataBuilder(getId(),
                        Modifier.PUBLIC, mutatorName, JavaType.VOID_PRIMITIVE,
                        AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                        parameterNames, bodyBuilder));
            }
        }

        return fieldMutatorMethods;
    }

    private String getFieldValidationBody(final FieldMetadata field,
            final String initializer, final JavaSymbolName mutatorName,
            final boolean isFieldOfEmbeddableType) {
        final String fieldName = field.getFieldName().getSymbolName();
        final JavaType fieldType = field.getFieldType();

        String suffix = "";
        if (fieldType.equals(JavaType.LONG_OBJECT)
                || fieldType.equals(JavaType.LONG_PRIMITIVE)) {
            suffix = "L";
        }
        else if (fieldType.equals(JavaType.FLOAT_OBJECT)
                || fieldType.equals(JavaType.FLOAT_PRIMITIVE)) {
            suffix = "F";
        }
        else if (fieldType.equals(JavaType.DOUBLE_OBJECT)
                || fieldType.equals(JavaType.DOUBLE_PRIMITIVE)) {
            suffix = "D";
        }

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine(getTypeStr(fieldType) + " " + fieldName
                + " = " + initializer + ";");

        if (fieldType.equals(JavaType.STRING)) {
            boolean isUnique = isFieldOfEmbeddableType;
            @SuppressWarnings("unchecked")
            final Map<String, Object> values = (Map<String, Object>) field
                    .getCustomData().get(CustomDataKeys.COLUMN_FIELD);
            if (!isUnique && values != null && values.containsKey("unique")) {
                isUnique = (Boolean) values.get("unique");
            }

            // Check for @Size or @Column with length attribute
            final AnnotationMetadata sizeAnnotation = MemberFindingUtils
                    .getAnnotationOfType(field.getAnnotations(), SIZE);
            if (sizeAnnotation != null
                    && sizeAnnotation.getAttribute(MAX_SYMBOL) != null) {
                final Integer maxValue = (Integer) sizeAnnotation.getAttribute(
                        MAX_SYMBOL).getValue();
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".length() > " + maxValue + ") {");
                bodyBuilder.indent();
                if (isUnique) {
                    bodyBuilder.appendFormalLine(fieldName
                            + " = new Random().nextInt(10) + " + fieldName
                            + ".substring(1, " + maxValue + ");");
                }
                else {
                    bodyBuilder.appendFormalLine(fieldName + " = " + fieldName
                            + ".substring(0, " + maxValue + ");");
                }
                bodyBuilder.indentRemove();
                bodyBuilder.appendFormalLine("}");
            }
            else if (sizeAnnotation == null && values != null) {
                if (values.containsKey("length")) {
                    final Integer lengthValue = (Integer) values.get("length");
                    bodyBuilder.appendFormalLine("if (" + fieldName
                            + ".length() > " + lengthValue + ") {");
                    bodyBuilder.indent();
                    if (isUnique) {
                        bodyBuilder.appendFormalLine(fieldName
                                + " = new Random().nextInt(10) + " + fieldName
                                + ".substring(1, " + lengthValue + ");");
                    }
                    else {
                        bodyBuilder.appendFormalLine(fieldName + " = "
                                + fieldName + ".substring(0, " + lengthValue
                                + ");");
                    }
                    bodyBuilder.indentRemove();
                    bodyBuilder.appendFormalLine("}");
                }
            }
        }
        else if (JdkJavaType.isDecimalType(fieldType)) {
            // Check for @Digits, @DecimalMax, @DecimalMin
            final AnnotationMetadata digitsAnnotation = MemberFindingUtils
                    .getAnnotationOfType(field.getAnnotations(), DIGITS);
            final AnnotationMetadata decimalMinAnnotation = MemberFindingUtils
                    .getAnnotationOfType(field.getAnnotations(), DECIMAL_MIN);
            final AnnotationMetadata decimalMaxAnnotation = MemberFindingUtils
                    .getAnnotationOfType(field.getAnnotations(), DECIMAL_MAX);

            if (digitsAnnotation != null) {
                bodyBuilder.append(getDigitsBody(field, digitsAnnotation,
                        suffix));
            }
            else if (decimalMinAnnotation != null
                    || decimalMaxAnnotation != null) {
                bodyBuilder.append(getDecimalMinAndDecimalMaxBody(field,
                        decimalMinAnnotation, decimalMaxAnnotation, suffix));
            }
            else if (field.getCustomData().keySet()
                    .contains(CustomDataKeys.COLUMN_FIELD)) {
                @SuppressWarnings("unchecked")
                final Map<String, Object> values = (Map<String, Object>) field
                        .getCustomData().get(CustomDataKeys.COLUMN_FIELD);
                bodyBuilder.append(getColumnPrecisionAndScaleBody(field,
                        values, suffix));
            }
        }
        else if (JdkJavaType.isIntegerType(fieldType)) {
            // Check for @Min and @Max
            bodyBuilder.append(getMinAndMaxBody(field, suffix));
        }

        if (mutatorName != null) {
            bodyBuilder.appendFormalLine(OBJ_VAR + "."
                    + mutatorName.getSymbolName() + "(" + fieldName + ");");
        }

        return bodyBuilder.getOutput();
    }

    /**
     * Returns the DoD type's "void init()" method (existing or generated)
     *
     * @param findEntriesMethod (required)
     * @param persistMethod (required)
     * @param flushMethod (required)
     * @return never <code>null</code>
     */
    private MethodMetadataBuilder getInitMethod(final int quantity,
            final MemberTypeAdditions findEntriesMethod,
            final MemberTypeAdditions persistMethod,
            final MemberTypeAdditions flushMethod) {
        // Method definition to find or build
        final JavaSymbolName methodName = new JavaSymbolName("init");
        final JavaType[] parameterTypes = {};
        final List<JavaSymbolName> parameterNames = Collections
                .<JavaSymbolName> emptyList();
        final JavaType returnType = JavaType.VOID_PRIMITIVE;

        // Locate user-defined method
        final MethodMetadata userMethod = getGovernorMethod(methodName,
                parameterTypes);
        if (userMethod != null) {
            Validate.isTrue(userMethod.getReturnType().equals(returnType),
                    "Method '%s' on '%s' must return '%s'", methodName,
                    destination, returnType.getNameIncludingTypeParameters());
            return new MethodMetadataBuilder(userMethod);
        }

        // Create the method body
        builder.getImportRegistrationResolver().addImports(ARRAY_LIST,
                ITERATOR, CONSTRAINT_VIOLATION_EXCEPTION, CONSTRAINT_VIOLATION);

        findEntriesMethod.copyAdditionsTo(builder, governorTypeDetails);
        persistMethod.copyAdditionsTo(builder, governorTypeDetails);

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        final String dataField = getDataFieldName().getSymbolName();
        bodyBuilder.appendFormalLine("int from = 0;");
        bodyBuilder.appendFormalLine("int to = " + quantity + ";");
        bodyBuilder.appendFormalLine(dataField + " = "
                + findEntriesMethod.getMethodCall() + ";");
        bodyBuilder.appendFormalLine("if (" + dataField + " == null) {");
        bodyBuilder.indent();
        bodyBuilder
                .appendFormalLine("throw new IllegalStateException(\"Find entries implementation for '"
                        + entity.getSimpleTypeName()
                        + "' illegally returned null\");");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        bodyBuilder.appendFormalLine("if (!" + dataField + ".isEmpty()) {");
        bodyBuilder.indent();
        bodyBuilder.appendFormalLine("return;");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        bodyBuilder.appendFormalLine("");
        bodyBuilder.appendFormalLine(dataField + " = new ArrayList<"
                + entity.getSimpleTypeName() + ">();");
        bodyBuilder.appendFormalLine("for (int i = 0; i < " + quantity
                + "; i++) {");
        bodyBuilder.indent();
        bodyBuilder.appendFormalLine(entity.getSimpleTypeName() + " " + OBJ_VAR
                + " = "
                + newTransientEntityMethod.getMethodName().getSymbolName()
                + "(i);");
        bodyBuilder.appendFormalLine("try {");
        bodyBuilder.indent();
        bodyBuilder.appendFormalLine(persistMethod.getMethodCall() + ";");
        bodyBuilder.indentRemove();
        bodyBuilder
                .appendFormalLine("} catch (final ConstraintViolationException e) {");
        bodyBuilder.indent();
        bodyBuilder
                .appendFormalLine("final StringBuilder msg = new StringBuilder();");
        bodyBuilder
                .appendFormalLine("for (Iterator<ConstraintViolation<?>> iter = e.getConstraintViolations().iterator(); iter.hasNext();) {");
        bodyBuilder.indent();
        bodyBuilder
                .appendFormalLine("final ConstraintViolation<?> cv = iter.next();");
        bodyBuilder
                .appendFormalLine("msg.append(\"[\").append(cv.getRootBean().getClass().getName()).append(\".\").append(cv.getPropertyPath()).append(\": \").append(cv.getMessage()).append(\" (invalid value = \").append(cv.getInvalidValue()).append(\")\").append(\"]\");");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        bodyBuilder
                .appendFormalLine("throw new IllegalStateException(msg.toString(), e);");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        if (flushMethod != null) {
            bodyBuilder.appendFormalLine(flushMethod.getMethodCall() + ";");
            flushMethod.copyAdditionsTo(builder, governorTypeDetails);
        }
        bodyBuilder.appendFormalLine(dataField + ".add(" + OBJ_VAR + ");");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");

        // Create the method
        return new MethodMetadataBuilder(getId(), Modifier.PUBLIC, methodName,
                returnType,
                AnnotatedJavaType.convertFromJavaTypes(parameterTypes),
                parameterNames, bodyBuilder);
    }

    private String getMinAndMaxBody(final FieldMetadata field,
            final String suffix) {
        final String fieldName = field.getFieldName().getSymbolName();
        final JavaType fieldType = field.getFieldType();

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();

        final AnnotationMetadata minAnnotation = MemberFindingUtils
                .getAnnotationOfType(field.getAnnotations(), MIN);
        final AnnotationMetadata maxAnnotation = MemberFindingUtils
                .getAnnotationOfType(field.getAnnotations(), MAX);
        if (minAnnotation != null && maxAnnotation == null) {
            final Number minValue = (Number) minAnnotation.getAttribute(VALUE)
                    .getValue();

            if (fieldType.equals(BIG_INTEGER)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_INTEGER.getSimpleTypeName()
                        + "(\"" + minValue + "\")) == -1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_INTEGER.getSimpleTypeName() + "(\"" + minValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " < "
                        + minValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + minValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }
        else if (minAnnotation == null && maxAnnotation != null) {
            final Number maxValue = (Number) maxAnnotation.getAttribute(VALUE)
                    .getValue();

            if (fieldType.equals(BIG_INTEGER)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_INTEGER.getSimpleTypeName()
                        + "(\"" + maxValue + "\")) == 1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_INTEGER.getSimpleTypeName() + "(\"" + maxValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " > "
                        + maxValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + maxValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }
        else if (minAnnotation != null && maxAnnotation != null) {
            final Number minValue = (Number) minAnnotation.getAttribute(VALUE)
                    .getValue();
            final Number maxValue = (Number) maxAnnotation.getAttribute(VALUE)
                    .getValue();
            Validate.isTrue(
                    maxValue.longValue() >= minValue.longValue(),
                    "The value of @Max must be greater or equal to the value of @Min for field %s",
                    fieldName);

            if (fieldType.equals(BIG_INTEGER)) {
                bodyBuilder.appendFormalLine("if (" + fieldName
                        + ".compareTo(new " + BIG_INTEGER.getSimpleTypeName()
                        + "(\"" + minValue + "\")) == -1 || " + fieldName
                        + ".compareTo(new " + BIG_INTEGER.getSimpleTypeName()
                        + "(\"" + maxValue + "\")) == 1) {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = new "
                        + BIG_INTEGER.getSimpleTypeName() + "(\"" + maxValue
                        + "\");");
            }
            else {
                bodyBuilder.appendFormalLine("if (" + fieldName + " < "
                        + minValue + suffix + " || " + fieldName + " > "
                        + maxValue + suffix + ") {");
                bodyBuilder.indent();
                bodyBuilder.appendFormalLine(fieldName + " = " + maxValue
                        + suffix + ";");
            }

            bodyBuilder.indentRemove();
            bodyBuilder.appendFormalLine("}");
        }

        return bodyBuilder.getOutput();
    }

    /**
     * @return the "modifyEntity(Entity):boolean" method (never returns null)
     */
    public MethodMetadata getModifyMethod() {
        return modifyMethod;
    }

    /**
     * @return the "getNewTransientEntity(int index):Entity" method (never
     *         returns null)
     */
    public MethodMetadata getNewTransientEntityMethod() {
        return newTransientEntityMethod;
    }

    /**
     * @return the "getRandomEntity():Entity" method (never returns null)
     */
    public MethodMetadata getRandomPersistentEntityMethod() {
        return randomPersistentEntityMethod;
    }

    private FieldMetadataBuilder getRndField() {
        int index = -1;
        while (true) {
            // Compute the required field name
            index++;
            final JavaSymbolName fieldName = new JavaSymbolName("rnd"
                    + StringUtils.repeat("_", index));
            rndFieldName = fieldName;
            final FieldMetadata candidate = governorTypeDetails
                    .getField(fieldName);
            if (candidate != null) {
                // Verify if candidate is suitable
                if (!Modifier.isPrivate(candidate.getModifier())) {
                    // Candidate is not private, so we might run into naming
                    // clashes if someone subclasses this (therefore go onto the
                    // next possible name)
                    continue;
                }
                if (!candidate.getFieldType().equals(RANDOM)) {
                    // Candidate isn't a java.util.Random, so it isn't suitable
                    continue;
                }
                // If we got this far, we found a valid candidate
                // We don't check if there is a corresponding initializer, but
                // we assume the user knows what they're doing and have made one
                return new FieldMetadataBuilder(candidate);
            }

            // Candidate not found, so let's create one
            builder.getImportRegistrationResolver().addImports(RANDOM,
                    SECURE_RANDOM);

            final FieldMetadataBuilder fieldBuilder = new FieldMetadataBuilder(
                    getId());
            fieldBuilder.setModifier(Modifier.PRIVATE);
            fieldBuilder.setFieldName(fieldName);
            fieldBuilder.setFieldType(RANDOM);
            fieldBuilder.setFieldInitializer("new SecureRandom()");
            return fieldBuilder;
        }
    }

    /**
     * @return the "rnd" field to use, which is either provided by the user or
     *         produced on demand (never returns null)
     */
    private JavaSymbolName getRndFieldName() {
        return rndFieldName;
    }

    /**
     * @return the "getSpecificEntity(int):Entity" method (never returns null)
     */
    private MethodMetadata getSpecificPersistentEntityMethod() {
        return specificPersistentEntityMethod;
    }

    private String getTypeStr(final JavaType fieldType) {
        builder.getImportRegistrationResolver().addImport(fieldType);

        final String arrayStr = fieldType.isArray() ? "[]" : "";
        String typeStr = fieldType.getSimpleTypeName();

        if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.FLOAT_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "float" + arrayStr;
        }
        else if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.DOUBLE_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "double" + arrayStr;
        }
        else if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.INT_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "int" + arrayStr;
        }
        else if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.SHORT_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "short" + arrayStr;
        }
        else if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.BYTE_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "byte" + arrayStr;
        }
        else if (fieldType.getFullyQualifiedTypeName().equals(
                JavaType.CHAR_PRIMITIVE.getFullyQualifiedTypeName())
                && fieldType.isPrimitive()) {
            typeStr = "char" + arrayStr;
        }
        else if (fieldType.equals(new JavaType(STRING
                .getFullyQualifiedTypeName(), 1, DataType.TYPE, null, null))) {
            typeStr = "String[]";
        }
        return typeStr;
    }

    public boolean hasEmbeddedIdentifier() {
        return embeddedIdHolder != null;
    }

    private void setModifyMethod() {
        // Method definition to find or build
        final JavaSymbolName methodName = new JavaSymbolName("modify"
                + entity.getSimpleTypeName());
        final JavaType parameterType = entity;
        final List<JavaSymbolName> parameterNames = Arrays.asList(OBJ_SYMBOL);
        final JavaType returnType = JavaType.BOOLEAN_PRIMITIVE;

        // Locate user-defined method
        final MethodMetadata userMethod = getGovernorMethod(methodName,
                parameterType);
        if (userMethod != null) {
            Validate.isTrue(userMethod.getReturnType().equals(returnType),
                    "Method '%s' on '%s' must return '%s'", methodName,
                    destination, returnType.getNameIncludingTypeParameters());
            modifyMethod = userMethod;
            return;
        }

        // Create method
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("return false;");

        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(), Modifier.PUBLIC, methodName, returnType,
                AnnotatedJavaType.convertFromJavaTypes(parameterType),
                parameterNames, bodyBuilder);
        builder.addMethod(methodBuilder);
        modifyMethod = methodBuilder.build();
    }

    private void setNewTransientEntityMethod() {
        // Method definition to find or build
        final JavaSymbolName methodName = new JavaSymbolName("getNewTransient"
                + entity.getSimpleTypeName());

        final JavaType parameterType = JavaType.INT_PRIMITIVE;
        final List<JavaSymbolName> parameterNames = Arrays.asList(INDEX_SYMBOL);

        // Locate user-defined method
        final MethodMetadata userMethod = getGovernorMethod(methodName,
                parameterType);
        if (userMethod != null) {
            Validate.isTrue(userMethod.getReturnType().equals(entity),
                    "Method '%s' on '%s' must return '%s'", methodName,
                    destination, entity.getNameIncludingTypeParameters());
            newTransientEntityMethod = userMethod;
            return;
        }

        // Create method
        builder.getImportRegistrationResolver().addImport(entity);

        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine(entity.getSimpleTypeName() + " " + OBJ_VAR
                + " = new " + entity.getSimpleTypeName() + "();");

        // Create the composite key embedded id method call if required
        if (hasEmbeddedIdentifier()) {
            bodyBuilder.appendFormalLine(getEmbeddedIdMutatorMethodName() + "("
                    + OBJ_VAR + ", " + INDEX_VAR + ");");
        }

        // Create a mutator method call for each embedded class
        for (final EmbeddedHolder embeddedHolder : embeddedHolders) {
            bodyBuilder
                    .appendFormalLine(getEmbeddedFieldMutatorMethodName(embeddedHolder
                            .getEmbeddedField().getFieldName())
                            + "("
                            + OBJ_VAR
                            + ", " + INDEX_VAR + ");");
        }

        // Create mutator method calls for each entity field
        for (final FieldMetadata field : fieldInitializers.keySet()) {
            final JavaSymbolName mutatorName = BeanInfoUtils
                    .getMutatorMethodName(field);
            bodyBuilder.appendFormalLine(mutatorName.getSymbolName() + "("
                    + OBJ_VAR + ", " + INDEX_VAR + ");");
        }

        bodyBuilder.appendFormalLine("return " + OBJ_VAR + ";");

        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(), Modifier.PUBLIC, methodName, entity,
                AnnotatedJavaType.convertFromJavaTypes(parameterType),
                parameterNames, bodyBuilder);
        builder.addMethod(methodBuilder);
        newTransientEntityMethod = methodBuilder.build();
    }

    /**
     * @return the "getRandomEntity():Entity" method (never returns null)
     */
    private void setRandomPersistentEntityMethod() {
        // Method definition to find or build
        final JavaSymbolName methodName = new JavaSymbolName("getRandom"
                + entity.getSimpleTypeName());

        // Locate user-defined method
        final MethodMetadata userMethod = getGovernorMethod(methodName);
        if (userMethod != null) {
            Validate.isTrue(userMethod.getReturnType().equals(entity),
                    "Method '%s' on '%s' must return '%s'", methodName,
                    destination, entity.getNameIncludingTypeParameters());
            randomPersistentEntityMethod = userMethod;
            return;
        }

        builder.getImportRegistrationResolver().addImport(identifierType);

        // Create method
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("init();");
        bodyBuilder.appendFormalLine(entity.getSimpleTypeName() + " " + OBJ_VAR
                + " = " + getDataFieldName().getSymbolName() + ".get("
                + getRndFieldName().getSymbolName() + ".nextInt("
                + getDataField().getFieldName().getSymbolName() + ".size()));");
        bodyBuilder.appendFormalLine(identifierType.getSimpleTypeName()
                + " id = " + OBJ_VAR + "."
                + identifierAccessor.getMethodName().getSymbolName() + "();");
        bodyBuilder.appendFormalLine("return " + findMethod.getMethodCall()
                + ";");

        findMethod.copyAdditionsTo(builder, governorTypeDetails);

        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(), Modifier.PUBLIC, methodName, entity, bodyBuilder);
        builder.addMethod(methodBuilder);
        randomPersistentEntityMethod = methodBuilder.build();
    }

    private void setSpecificPersistentEntityMethod() {
        // Method definition to find or build
        final JavaSymbolName methodName = new JavaSymbolName("getSpecific"
                + entity.getSimpleTypeName());
        final JavaType parameterType = JavaType.INT_PRIMITIVE;
        final List<JavaSymbolName> parameterNames = Arrays.asList(INDEX_SYMBOL);

        // Locate user-defined method
        final MethodMetadata userMethod = getGovernorMethod(methodName,
                parameterType);
        if (userMethod != null) {
            Validate.isTrue(userMethod.getReturnType().equals(entity),
                    "Method '%s on '%s' must return '%s'", methodName,
                    destination, entity.getNameIncludingTypeParameters());
            specificPersistentEntityMethod = userMethod;
            return;
        }

        builder.getImportRegistrationResolver().addImport(identifierType);

        // Create method
        final InvocableMemberBodyBuilder bodyBuilder = new InvocableMemberBodyBuilder();
        bodyBuilder.appendFormalLine("init();");
        bodyBuilder.appendFormalLine("if (" + INDEX_VAR + " < 0) {");
        bodyBuilder.indent();
        bodyBuilder.appendFormalLine(INDEX_VAR + " = 0;");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        bodyBuilder.appendFormalLine("if (" + INDEX_VAR + " > ("
                + getDataFieldName().getSymbolName() + ".size() - 1)) {");
        bodyBuilder.indent();
        bodyBuilder.appendFormalLine(INDEX_VAR + " = "
                + getDataFieldName().getSymbolName() + ".size() - 1;");
        bodyBuilder.indentRemove();
        bodyBuilder.appendFormalLine("}");
        bodyBuilder.appendFormalLine(entity.getSimpleTypeName() + " " + OBJ_VAR
                + " = " + getDataFieldName().getSymbolName() + ".get("
                + INDEX_VAR + ");");
        bodyBuilder.appendFormalLine(identifierType.getSimpleTypeName()
                + " id = " + OBJ_VAR + "."
                + identifierAccessor.getMethodName().getSymbolName() + "();");
        bodyBuilder.appendFormalLine("return " + findMethod.getMethodCall()
                + ";");

        findMethod.copyAdditionsTo(builder, governorTypeDetails);

        final MethodMetadataBuilder methodBuilder = new MethodMetadataBuilder(
                getId(), Modifier.PUBLIC, methodName, entity,
                AnnotatedJavaType.convertFromJavaTypes(parameterType),
                parameterNames, bodyBuilder);
        builder.addMethod(methodBuilder);
        specificPersistentEntityMethod = methodBuilder.build();
    }

    @Override
    public String toString() {
        final ToStringBuilder builder = new ToStringBuilder(this);
        builder.append("identifier", getId());
        builder.append("valid", valid);
        builder.append("aspectName", aspectName);
        builder.append("destinationType", destination);
        builder.append("governor", governorPhysicalTypeMetadata.getId());
        builder.append("itdTypeDetails", itdTypeDetails);
        return builder.toString();
    }

    private void validateNumericAnnotationAttribute(final String fieldName,
            final String annotationName, final String attributeName,
            final Object object) {
        Validate.isTrue(
                NumberUtils.isNumber(object.toString()),
                "%s '%s' attribute for field '%s' in backing type %s must be numeric",
                annotationName, attributeName, fieldName,
                entity.getFullyQualifiedTypeName());
    }
}
TOP

Related Classes of org.springframework.roo.addon.dod.DataOnDemandMetadata

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.