private List<Identifier> getIdentifiers(final Table table, final boolean usePrimaryKeys) {
    final List<Identifier> result = new ArrayList<Identifier>();

    // Add fields to the identifier class
    final Set<Column> columns = usePrimaryKeys ? table.getPrimaryKeys() : table.getColumns();
    for (final Column column : columns) {
      final String columnName = column.getName();
      JavaSymbolName fieldName;
      try {
        fieldName = new JavaSymbolName(DbreTypeUtils.suggestFieldName(columnName));
      } catch (final RuntimeException e) {
        throw new IllegalArgumentException(
            "Failed to create field name for column '"
                + columnName
                + "' in table '"
                + table.getName()
                + "': "
                + e.getMessage());
      }
      final JavaType fieldType = column.getJavaType();
      final String columnDefinition =
          table.isIncludeNonPortableAttributes() ? column.getTypeName() : "";
      result.add(
          new Identifier(
              fieldName,
              fieldType,
              columnName,
              column.getColumnSize(),
              column.getScale(),
              columnDefinition));
    }
    return result;
  }
 private boolean hasVersionField(final Table table) {
   if (!table.isDisableVersionFields()) {
     for (final Column column : table.getColumns()) {
       if (VERSION.equalsIgnoreCase(column.getName())) {
         return true;
       }
     }
   }
   return false;
 }
  /**
   * Creates a new DBRE-managed entity from the given table
   *
   * @param javaType the name of the entity to be created (required)
   * @param table the table from which to create the entity (required)
   * @param activeRecord whether to create "active record" CRUD methods in the new entity
   * @return the newly created entity
   */
  private ClassOrInterfaceTypeDetails createNewManagedEntityFromTable(
      final JavaType javaType, final Table table, final boolean activeRecord) {
    // Create type annotations for new entity
    final List<AnnotationMetadataBuilder> annotations = new ArrayList<AnnotationMetadataBuilder>();
    annotations.add(new AnnotationMetadataBuilder(ROO_JAVA_BEAN));
    annotations.add(new AnnotationMetadataBuilder(ROO_TO_STRING));

    // Find primary key from db metadata and add identifier attributes to
    // @RooJpaEntity
    final AnnotationMetadataBuilder jpaAnnotationBuilder =
        new AnnotationMetadataBuilder(activeRecord ? ROO_JPA_ACTIVE_RECORD : ROO_JPA_ENTITY);
    manageIdentifier(javaType, jpaAnnotationBuilder, new HashSet<JavaSymbolName>(), table);

    if (!hasVersionField(table)) {
      jpaAnnotationBuilder.addStringAttribute(VERSION_FIELD, "");
    }
    if (table.isDisableGeneratedIdentifiers()) {
      jpaAnnotationBuilder.addStringAttribute(SEQUENCE_NAME_FIELD, "");
    }

    jpaAnnotationBuilder.addStringAttribute("table", table.getName());
    if (!DbreModelService.NO_SCHEMA_REQUIRED.equals(table.getSchema().getName())) {
      jpaAnnotationBuilder.addStringAttribute("schema", table.getSchema().getName());
    }

    annotations.add(jpaAnnotationBuilder);

    // Add @RooDbManaged
    annotations.add(getRooDbManagedAnnotation());

    final JavaType superclass = OBJECT;
    final List<JavaType> extendsTypes = new ArrayList<JavaType>();
    extendsTypes.add(superclass);

    // Create entity class
    final String declaredByMetadataId =
        PhysicalTypeIdentifier.createIdentifier(
            javaType, projectOperations.getPathResolver().getFocusedPath(Path.SRC_MAIN_JAVA));
    final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
        new ClassOrInterfaceTypeDetailsBuilder(
            declaredByMetadataId, Modifier.PUBLIC, javaType, PhysicalTypeCategory.CLASS);
    cidBuilder.setExtendsTypes(extendsTypes);
    cidBuilder.setAnnotations(annotations);

    final ClassOrInterfaceTypeDetails entity = cidBuilder.build();
    typeManagementService.createOrUpdateTypeOnDisk(entity);

    shell.flash(
        Level.FINE,
        "Created " + javaType.getFullyQualifiedTypeName(),
        DbreDatabaseListenerImpl.class.getName());
    shell.flash(Level.FINE, "", DbreDatabaseListenerImpl.class.getName());

    return entity;
  }
  private void manageIdentifier(
      final JavaType javaType,
      final AnnotationMetadataBuilder jpaAnnotationBuilder,
      final Set<JavaSymbolName> attributesToDeleteIfPresent,
      final Table table) {
    final JavaType identifierType = getIdentifierType(javaType);
    final PhysicalTypeMetadata identifierPhysicalTypeMetadata =
        getPhysicalTypeMetadata(identifierType);

    // Process primary keys and add 'identifierType' attribute
    final int pkCount = table.getPrimaryKeyCount();
    if (pkCount == 1) {
      // Table has one primary key
      // Check for redundant, managed identifier class and delete if found
      if (isIdentifierDeletable(identifierType)) {
        deleteJavaType(
            identifierType, "the " + table.getName() + " table has only one primary key");
      }

      attributesToDeleteIfPresent.add(new JavaSymbolName(IDENTIFIER_TYPE));

      // We don't need a PK class, so we just tell the
      // JpaActiveRecordProvider via IdentifierService the column name,
      // field type and field name to use
      final List<Identifier> identifiers = getIdentifiersFromPrimaryKeys(table);
      identifierResults.put(javaType, identifiers);
    } else if (pkCount == 0 || pkCount > 1) {
      // Table has either no primary keys or more than one primary key so
      // create a composite key

      // Check if identifier class already exists and if not, create it
      if (identifierPhysicalTypeMetadata == null
          || !identifierPhysicalTypeMetadata.isValid()
          || identifierPhysicalTypeMetadata.getMemberHoldingTypeDetails() == null) {
        createIdentifierClass(identifierType);
      }

      jpaAnnotationBuilder.addClassAttribute(IDENTIFIER_TYPE, identifierType);

      // We need a PK class, so we tell the IdentifierMetadataProvider via
      // IdentifierService the various column names, field types and field
      // names to use
      // For tables with no primary keys, create a composite key using all
      // the table's columns
      final List<Identifier> identifiers =
          pkCount == 0 ? getIdentifiersFromColumns(table) : getIdentifiersFromPrimaryKeys(table);
      identifierResults.put(identifierType, identifiers);
    }
  }
  private Table updateOrDeleteManagedEntity(
      final ClassOrInterfaceTypeDetails managedEntity, final Database database) {
    // Update the attributes of the existing JPA-related annotation
    final AnnotationMetadata jpaAnnotation = getJpaAnnotation(managedEntity);
    Validate.validState(
        jpaAnnotation != null,
        "Neither @%s nor @%s found on existing DBRE-managed entity %s",
        ROO_JPA_ACTIVE_RECORD.getSimpleTypeName(),
        ROO_JPA_ENTITY.getSimpleTypeName(),
        managedEntity.getName().getFullyQualifiedTypeName());

    // Find table in database using 'table' and 'schema' attributes from the
    // JPA annotation
    final AnnotationAttributeValue<?> tableAttribute =
        jpaAnnotation.getAttribute(new JavaSymbolName("table"));
    final String errMsg =
        "Unable to maintain database-managed entity "
            + managedEntity.getName().getFullyQualifiedTypeName()
            + " because its associated table could not be found";
    Validate.notNull(tableAttribute, errMsg);
    final String tableName = (String) tableAttribute.getValue();
    Validate.notBlank(tableName, errMsg);

    final AnnotationAttributeValue<?> schemaAttribute =
        jpaAnnotation.getAttribute(new JavaSymbolName("schema"));
    final String schemaName = schemaAttribute != null ? (String) schemaAttribute.getValue() : null;

    final Table table = database.getTable(tableName, schemaName);
    if (table == null) {
      // Table is missing and probably has been dropped so delete managed
      // type and its identifier if applicable
      deleteManagedType(managedEntity, "no database table called '" + tableName + "'");
      return null;
    }

    table.setIncludeNonPortableAttributes(database.isIncludeNonPortableAttributes());
    table.setDisableVersionFields(database.isDisableVersionFields());
    table.setDisableGeneratedIdentifiers(database.isDisableGeneratedIdentifiers());

    // Update the @RooJpaEntity/@RooJpaActiveRecord attributes
    final AnnotationMetadataBuilder jpaAnnotationBuilder =
        new AnnotationMetadataBuilder(jpaAnnotation);
    final Set<JavaSymbolName> attributesToDeleteIfPresent = new LinkedHashSet<JavaSymbolName>();
    manageIdentifier(
        managedEntity.getName(), jpaAnnotationBuilder, attributesToDeleteIfPresent, table);

    // Manage versionField attribute
    final AnnotationAttributeValue<?> versionFieldAttribute =
        jpaAnnotation.getAttribute(new JavaSymbolName(VERSION_FIELD));
    if (versionFieldAttribute == null) {
      if (hasVersionField(table)) {
        attributesToDeleteIfPresent.add(new JavaSymbolName(VERSION_FIELD));
      } else {
        jpaAnnotationBuilder.addStringAttribute(VERSION_FIELD, "");
      }
    } else {
      final String versionFieldValue = (String) versionFieldAttribute.getValue();
      if (hasVersionField(table)
          && (StringUtils.isBlank(versionFieldValue) || VERSION.equals(versionFieldValue))) {
        attributesToDeleteIfPresent.add(new JavaSymbolName(VERSION_FIELD));
      }
    }

    final AnnotationAttributeValue<?> sequenceNameFieldAttribute =
        jpaAnnotation.getAttribute(new JavaSymbolName(SEQUENCE_NAME_FIELD));
    if (sequenceNameFieldAttribute == null) {
      if (!table.isDisableGeneratedIdentifiers()) {
        attributesToDeleteIfPresent.add(new JavaSymbolName(SEQUENCE_NAME_FIELD));
      } else {
        jpaAnnotationBuilder.addStringAttribute(SEQUENCE_NAME_FIELD, "");
      }
    } else {
      final String sequenceNameFieldValue = (String) sequenceNameFieldAttribute.getValue();
      if (!table.isDisableGeneratedIdentifiers() && ("".equals(sequenceNameFieldValue))) {
        attributesToDeleteIfPresent.add(new JavaSymbolName(SEQUENCE_NAME_FIELD));
      }
    }

    // Update the annotation on disk
    final ClassOrInterfaceTypeDetailsBuilder cidBuilder =
        new ClassOrInterfaceTypeDetailsBuilder(managedEntity);
    cidBuilder.updateTypeAnnotation(jpaAnnotationBuilder.build(), attributesToDeleteIfPresent);
    typeManagementService.createOrUpdateTypeOnDisk(cidBuilder.build());
    return table;
  }
  private void reverseEngineer(final Database database) {
    final Set<ClassOrInterfaceTypeDetails> managedEntities =
        typeLocationService.findClassesOrInterfaceDetailsWithAnnotation(ROO_DB_MANAGED);
    // Determine whether to create "active record" CRUD methods
    database.setActiveRecord(isActiveRecord(database, managedEntities));

    // Lookup the relevant destination package if not explicitly given
    final JavaPackage destinationPackage = getDestinationPackage(database, managedEntities);

    // Set the destination package in the database
    database.setDestinationPackage(destinationPackage);

    // Get tables from database
    final Set<Table> tables = new LinkedHashSet<Table>(database.getTables());

    // Manage existing entities with @RooDbManaged annotation
    for (final ClassOrInterfaceTypeDetails managedEntity : managedEntities) {
      // Remove table from set as each managed entity is processed.
      // The tables that remain in the set will be used for creation of
      // new entities later
      final Table table = updateOrDeleteManagedEntity(managedEntity, database);
      if (table != null) {
        tables.remove(table);
      }
    }

    // Create new entities from tables
    final List<ClassOrInterfaceTypeDetails> newEntities =
        new ArrayList<ClassOrInterfaceTypeDetails>();
    for (final Table table : tables) {
      // Don't create types from join tables in many-to-many associations
      if (!table.isJoinTable()) {
        JavaPackage schemaPackage = destinationPackage;
        if (database.hasMultipleSchemas()) {
          schemaPackage =
              new JavaPackage(
                  destinationPackage.getFullyQualifiedPackageName()
                      + "."
                      + DbreTypeUtils.suggestPackageName(table.getSchema().getName()));
        }
        final JavaType javaType =
            DbreTypeUtils.suggestTypeNameForNewTable(table.getName(), schemaPackage);
        final boolean activeRecord = database.isActiveRecord() && !database.isRepository();
        if (typeLocationService.getTypeDetails(javaType) == null) {
          table.setIncludeNonPortableAttributes(database.isIncludeNonPortableAttributes());
          table.setDisableVersionFields(database.isDisableVersionFields());
          table.setDisableGeneratedIdentifiers(database.isDisableGeneratedIdentifiers());
          newEntities.add(createNewManagedEntityFromTable(javaType, table, activeRecord));
        }
      }
    }

    // Create repositories if required
    if (database.isRepository()) {
      for (final ClassOrInterfaceTypeDetails entity : newEntities) {
        final JavaType type = entity.getType();
        repositoryJpaOperations.setupRepository(
            new JavaType(type.getFullyQualifiedTypeName() + "Repository"), type);
      }
    }

    // Create services if required
    if (database.isService()) {
      for (final ClassOrInterfaceTypeDetails entity : newEntities) {
        final JavaType type = entity.getType();
        final String typeName = type.getFullyQualifiedTypeName();
        serviceOperations.setupService(
            new JavaType(typeName + "Service"),
            new JavaType(typeName + "ServiceImpl"),
            type,
            false,
            "",
            false,
            false);
      }
    }

    // Create integration tests if required
    if (database.isTestAutomatically()) {
      for (final ClassOrInterfaceTypeDetails entity : newEntities) {
        integrationTestOperations.newIntegrationTest(entity.getType());
      }
    }

    // Notify
    final List<ClassOrInterfaceTypeDetails> allEntities =
        new ArrayList<ClassOrInterfaceTypeDetails>();
    allEntities.addAll(newEntities);
    allEntities.addAll(managedEntities);
    notify(allEntities);
  }