diff --git a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/entity/GeneratedEntityImplTranslator.java b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/entity/GeneratedEntityImplTranslator.java index 01ef00c1e7..1529a8f523 100644 --- a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/entity/GeneratedEntityImplTranslator.java +++ b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/entity/GeneratedEntityImplTranslator.java @@ -68,6 +68,16 @@ protected Class makeCodeGenModel(File file) { return newBuilder(file, getSupport().generatedEntityImplName()) + /** + * Class details + */ + .forEveryTable((clazz, table) -> { + clazz.public_() + .abstract_() + .add(getSupport().entityType()) + .add(Constructor.of().protected_()); + }) + /** * Getters */ @@ -99,7 +109,7 @@ protected Class makeCodeGenModel(File file) { .forEveryColumn((clazz, col) -> { clazz .add(Method.of(SETTER_METHOD_PREFIX + getSupport().typeName(col), getSupport().entityType()) - .public_().final_() + .public_() .add(OVERRIDE) .add(fieldFor(col)) .add("this." + getSupport().variableName(col) + " = " + getSupport().variableName(col) + ";") @@ -149,10 +159,7 @@ protected Class makeCodeGenModel(File file) { ); }); }) - - /** - * Class details - */ + // We need to make it POST_MAKE because other plugins might add fields .forEveryTable(Phase.POST_MAKE, (clazz, table) -> { clazz @@ -160,11 +167,7 @@ protected Class makeCodeGenModel(File file) { .add(equalsMethod()) .add(hashCodeMethod()); }) - .build() - .public_() - .abstract_() - .add(getSupport().generatedEntityType()) - .add(Constructor.of().protected_()); + .build(); } diff --git a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/internal/util/GenerateMethodBodyUtil.java b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/internal/util/GenerateMethodBodyUtil.java index edc13ed28d..705874de57 100644 --- a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/internal/util/GenerateMethodBodyUtil.java +++ b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/internal/util/GenerateMethodBodyUtil.java @@ -95,8 +95,10 @@ public interface ReadFromResultSet { String readFromResultSet(File file, Column c, AtomicInteger position); } - public static String[] generateNewEntityFromBody(ReadFromResultSet readFromResultSet, TranslatorSupport support, File file, Supplier> columnsSupplier) { + public static String[] generateApplyResultSetBody(ReadFromResultSet readFromResultSet, TranslatorSupport
support, File file, Supplier> columnsSupplier) { + file.add(Import.of(SQLException.class)); + final List rows = new LinkedList<>(); rows.add("final " + support.entityName() + " entity = manager." + ENTITY_CREATE_METHOD_NAME + "();"); @@ -110,7 +112,7 @@ public static String[] generateNewEntityFromBody(ReadFromResultSet readFromResul }); rows.add("try " + block(streamBuilder.build())); - rows.add("catch (" + SQLException.class.getSimpleName() + " sqle) " + block( + rows.add("catch (final " + SQLException.class.getSimpleName() + " sqle) " + block( "throw new " + SpeedmentException.class.getSimpleName() + "(sqle);" )); rows.add("return entity;"); diff --git a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/manager/GeneratedSqlAdapterTranslator.java b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/manager/GeneratedSqlAdapterTranslator.java index aebfc0e1ad..b5955d553d 100644 --- a/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/manager/GeneratedSqlAdapterTranslator.java +++ b/generator-parent/generator-standard/src/main/java/com/speedment/generator/standard/manager/GeneratedSqlAdapterTranslator.java @@ -24,7 +24,6 @@ import com.speedment.common.codegen.model.File; import com.speedment.common.codegen.model.Import; import com.speedment.common.codegen.model.Method; -import com.speedment.common.injector.Injector; import com.speedment.common.injector.annotation.Inject; import com.speedment.generator.translator.AbstractEntityAndManagerTranslator; import com.speedment.generator.translator.TranslatorSupport; @@ -39,47 +38,45 @@ import java.lang.reflect.Type; import java.sql.ResultSet; -import java.sql.SQLException; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import java.util.stream.Stream; import com.speedment.common.codegen.internal.model.value.ReferenceValue; -import static com.speedment.common.codegen.internal.util.Formatting.indent; import com.speedment.common.injector.State; import com.speedment.common.injector.annotation.ExecuteBefore; -import com.speedment.common.injector.annotation.WithState; -import com.speedment.runtime.config.util.DocumentDbUtil; import com.speedment.runtime.core.exception.SpeedmentException; import static com.speedment.runtime.core.util.DatabaseUtil.dbmsTypeOf; -import static com.speedment.generator.standard.internal.util.ColumnUtil.usesOptional; -import static com.speedment.generator.standard.internal.util.GenerateMethodBodyUtil.generateNewEntityFromBody; import com.speedment.generator.translator.component.TypeMapperComponent; +import com.speedment.generator.translator.exception.SpeedmentTranslatorException; import com.speedment.runtime.config.Project; import com.speedment.runtime.config.identifier.TableIdentifier; +import com.speedment.runtime.config.trait.HasEnabled; import com.speedment.runtime.core.component.sql.SqlPersistenceComponent; import com.speedment.runtime.core.component.sql.SqlStreamSupplierComponent; +import com.speedment.runtime.core.component.sql.SqlTypeMapperHelper; +import com.speedment.runtime.typemapper.TypeMapper; import static java.util.stream.Collectors.joining; +import static com.speedment.generator.standard.internal.util.GenerateMethodBodyUtil.generateApplyResultSetBody; /** * - * @author Emil Forslund + * @author Emil Forslund + * @since 3.0.1 */ public final class GeneratedSqlAdapterTranslator extends AbstractEntityAndManagerTranslator { - public final static String ENTITY_COPY_METHOD_NAME = "entityCopy", - ENTITY_CREATE_METHOD_NAME = "entityCreate", - FIELDS_METHOD = "fields", + public final static String + CREATE_HELPERS_METHOD_NAME = "createHelpers", + INSTALL_METHOD_NAME = "installMethodName", + ENTITY_COPY_METHOD_NAME = "entityCopy", + ENTITY_CREATE_METHOD_NAME = "entityCreate", + FIELDS_METHOD = "fields", PRIMARY_KEYS_FIELDS_METHOD = "primaryKeyFields"; - private @Inject - ResultSetMapperComponent resultSetMapperComponent; - private @Inject - DbmsHandlerComponent dbmsHandlerComponent; - private @Inject - TypeMapperComponent typeMappers; - private @Inject - Injector injector; + private @Inject ResultSetMapperComponent resultSetMapperComponent; + private @Inject DbmsHandlerComponent dbmsHandlerComponent; + private @Inject TypeMapperComponent typeMapperComponent; public GeneratedSqlAdapterTranslator(Table table) { super(table, Class::of); @@ -87,55 +84,95 @@ public GeneratedSqlAdapterTranslator(Table table) { @Override protected Class makeCodeGenModel(File file) { - file.add(Import.of(Project.class)); + final Type tableIdentifierType = SimpleParameterizedType + .create(TableIdentifier.class, getSupport().entityType()); + return newBuilder(file, getClassOrInterfaceName()) .forEveryTable((clazz, table) -> { - clazz - .public_() - .abstract_() - .add(Field.of("manager", getSupport().managerType()).add(inject()).private_()) - .add(Field.of("tableIdentifier", SimpleParameterizedType.create(TableIdentifier.class, getSupport().entityType())).private_().final_()) - .add(Field.of("project", Project.class).private_()) - .add(Method.of("createHelpers", void.class).add(withExecuteBefore(file)) - .add(Field.of("projectComponent", ProjectComponent.class)) - .add("this.project = projectComponent.getProject();") + final Method createHelpers = Method.of(CREATE_HELPERS_METHOD_NAME, void.class) + .add(withExecuteBefore(file)) + .add(Field.of("projectComponent", ProjectComponent.class)) + .add("final Project project = projectComponent.getProject();"); + + clazz.public_().abstract_() + + // Generate conxtructor + .add(Constructor.of().protected_() + .add("this.tableIdentifier = " + + TableIdentifier.class.getSimpleName() + ".of(" + + Stream.of( + getSupport().dbmsOrThrow().getName(), + getSupport().schemaOrThrow().getName(), + getSupport().tableOrThrow().getName() + ).map(s -> "\"" + s + "\"").collect(joining(", ")) + + ");") ) - .add(Method.of("install", void.class).add(withExecuteBefore(file)) - .add("") - .add(Field.of("manager", getSupport().managerType())) + + // Generate member fields + .add(Field.of("tableIdentifier", tableIdentifierType).private_().final_()) + .add(Field.of("manager", getSupport().managerType()).add(inject()).private_()) + + // Generate methods + .add(Method.of(INSTALL_METHOD_NAME, void.class).add(withExecuteBefore(file)) .add(Field.of("streamSupplierComponent", SqlStreamSupplierComponent.class)) .add(Field.of("persistenceComponent", SqlPersistenceComponent.class)) .add("streamSupplierComponent.install(tableIdentifier, this::apply);") .add("persistenceComponent.install(tableIdentifier);") ) - .add(Constructor.of().protected_() - // .add("this.tableIdentifier = "+TableIdentifier.class.getSimpleName()+".of(\\\"\" + getSupport().dbmsOrThrow().getName() + \"\\\"); - .add("this.tableIdentifier = " + TableIdentifier.class.getSimpleName() + ".of(" - + Stream.of(getSupport().dbmsOrThrow().getName(), getSupport().schemaOrThrow().getName(), getSupport().tableOrThrow().getName()) - .map(s -> "\"" + s + "\"").collect(joining(", ")) - + ");") - ) - .add(generateNewEntityFrom(getSupport(), file, table::columns)); + + .add(generateApplyResultSet(getSupport(), file, table::columns)) + + .call(() -> { + // Operate on enabled columns that has a type mapper + // that is not either empty, an identity mapper or a + // primitive mapper. + table.columns() + .filter(HasEnabled::test) + .filter(c -> c.getTypeMapper() + .filter(tm -> !"".equals(tm)) + .filter(tm -> !tm.equals(TypeMapper.identity().getClass().getName())) + .filter(tm -> !tm.equals(TypeMapper.primitive().getClass().getName())) + .isPresent() + ).forEachOrdered(col -> { + // If the method has not yet been added, add it + if (clazz.getMethods().stream() + .map(Method::getName) + .noneMatch(CREATE_HELPERS_METHOD_NAME::equals)) { + file.add(Import.of(Project.class)); + clazz.add(createHelpers); + } + + // Append the line for this helper to the method + final String tmName = col.getTypeMapper().get(); + final TypeMapper tm = typeMapperComponent.get(col); + + final String tmsName = helperName(col); + final Type tmsType = SimpleParameterizedType.create( + SqlTypeMapperHelper.class, + typeMapperComponent.findDatabaseTypeOf(tm) + .orElseThrow(() -> new SpeedmentTranslatorException( + "Could not find appropriate " + + "database type for column '" + col + + "'." + )), + tm.getJavaType(col) + ); + + clazz.add(Field.of(tmsName, tmsType).private_()); + + createHelpers + .add(tmsName + " = " + SqlTypeMapperHelper.class.getSimpleName() + + ".create(project, " + getSupport().entityName() + + "." + getSupport().namer().javaStaticFieldName(col.getJavaName()) + + ", " + getSupport().entityName() + ".class);" + ); + }); + }) + ; }) - .build() - .call(i -> file.add(Import.of(getSupport().entityImplType()))); + .build(); } -// private Method generateApplyMethod(File file, Table table) { -// return Method.of("apply", getSupport().entityType()).protected_() -// .add("final " + getSupport().entityName() + " entity = manager.entityCreate();") -// .add("try {") -// .add(indent(fieldsss(table))) -// .add("} catch (final SQLException sqle) {") -// .add(indent("throw new SpeedmentException(sqle);")) -// .add("}") -// .add("return entity;"); -// } - -// private String[] fieldsss(Table table) { -// throw new UnsupportedOperationException("Todo"); -// } - @Override protected String getJavadocRepresentText() { return "The generated Sql Adapter for a {@link " @@ -156,18 +193,15 @@ public Type getImplType() { return getSupport().managerImplType(); } - private Method generateNewEntityFrom(TranslatorSupport
support, File file, Supplier> columnsSupplier) { + private Method generateApplyResultSet(TranslatorSupport
support, File file, Supplier> columnsSupplier) { return Method.of("apply", support.entityType()) .protected_() - .add(SQLException.class) .add(SpeedmentException.class) .add(Field.of("resultSet", ResultSet.class)) - .add(generateNewEntityFromBody(this::readFromResultSet, support, file, columnsSupplier)); + .add(generateApplyResultSetBody(this::readFromResultSet, support, file, columnsSupplier)); } private String readFromResultSet(File file, Column c, AtomicInteger position) { - - final TranslatorSupport
support = new TranslatorSupport<>(injector, c.getParentOrThrow()); final Dbms dbms = c.getParentOrThrow().getParentOrThrow().getParentOrThrow(); final ResultSetMapping mapping = resultSetMapperComponent.apply( @@ -175,60 +209,34 @@ private String readFromResultSet(File file, Column c, AtomicInteger position) { c.findDatabaseType() ); - final boolean isIdentityMapper = !c.getTypeMapper().isPresent(); - - file.add(Import.of(DocumentDbUtil.class)); + final java.lang.Class typeMapperClass = typeMapperComponent.get(c).getClass(); + final boolean isCustomTypeMapper = c.getTypeMapper().isPresent() + && !TypeMapper.identity().getClass().isAssignableFrom(typeMapperClass) + && !TypeMapper.primitive().getClass().isAssignableFrom(typeMapperClass); final StringBuilder sb = new StringBuilder(); - if (!isIdentityMapper) { - sb - .append(typeMapperName(support, c)) - .append(".toJavaType(DocumentDbUtil.referencedColumn(project, ") - .append(support.entityName()) - .append(".") - .append(support.namer().javaStaticFieldName(c.getJavaName())) - .append(".identifier()), manager.getEntityClass(), "); + if (isCustomTypeMapper) { + sb.append(helperName(c)).append(".apply("); } + final String getterName = "get" + mapping.getResultSetMethodName(dbms); - - final boolean isResultSetMethod = Stream.of(ResultSet.class.getMethods()) - .map(java.lang.reflect.Method::getName) - .anyMatch(getterName::equals); - - final boolean isResultSetMethodReturnsPrimitive = Stream.of(ResultSet.class.getMethods()) - .filter(m -> m.getName().equals(getterName)) - .anyMatch(m -> m.getReturnType().isPrimitive()); - - if (isResultSetMethod && !(usesOptional(c) && isResultSetMethodReturnsPrimitive)) { - sb - .append("resultSet.") - .append("get") - .append(mapping.getResultSetMethodName(dbms)) - .append("(").append(position.getAndIncrement()).append(")"); - } else { + if (c.isNullable()) { + file.add(Import.of(ResultSetUtil.class).static_().setStaticMember("*")); - sb - .append("get") - .append(mapping.getResultSetMethodName(dbms)) - .append("(resultSet, ") + sb.append(getterName).append("(resultSet, ") .append(position.getAndIncrement()).append(")"); + } else { + sb.append("resultSet.").append(getterName) + .append("(").append(position.getAndIncrement()).append(")"); } - if (!isIdentityMapper) { + + if (isCustomTypeMapper) { sb.append(")"); } return sb.toString(); } - private static String typeMapperName(TranslatorSupport
support, Column col) { - return support.entityName() + "." + support.namer().javaStaticFieldName(col.getJavaName()) + ".typeMapper()"; - } - -// private static boolean isPrimaryKey(Column column) { -// return column.getParentOrThrow().findPrimaryKeyColumn(column.getName()).isPresent(); -// } - - private AnnotationUsage withExecuteBefore(File file) { file.add(Import.of(State.class).static_().setStaticMember("RESOLVED")); return AnnotationUsage.of(ExecuteBefore.class).set(new ReferenceValue("RESOLVED")); @@ -237,5 +245,30 @@ private AnnotationUsage withExecuteBefore(File file) { private AnnotationUsage inject() { return AnnotationUsage.of(Inject.class); } - -} + + private String helperName(Column column) { + return getSupport().namer() + .javaVariableName(column.getJavaName()) + "Helper"; + } + + private enum TypeMapperType { + IDENTITY, PRIMITIVE, OTHER; + + static TypeMapperType of(TypeMapperComponent mappers, Column col) { + + if (!col.getTypeMapper().isPresent()) { + return IDENTITY; + } + + final TypeMapper mapper = mappers.get(col); + + if (TypeMapper.identity().getClass().isAssignableFrom(mapper.getClass())) { + return IDENTITY; + } else if (TypeMapper.primitive().getClass().isAssignableFrom(mapper.getClass())) { + return PRIMITIVE; + } else { + return OTHER; + } + } + } +} \ No newline at end of file diff --git a/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/component/TypeMapperComponent.java b/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/component/TypeMapperComponent.java index f71d4a571f..a5cf3fef1b 100644 --- a/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/component/TypeMapperComponent.java +++ b/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/component/TypeMapperComponent.java @@ -75,4 +75,22 @@ public interface TypeMapperComponent { * @return the mapper class */ TypeMapper get(Column column); + + /** + * Locates the specified type mapper in the store and returns the database + * type that it is been installed for. If the same {@code TypeMapper} has + * been installed for multiple classes, the returned class is unspecified + * but will be one of the installed types. + *

+ * If the specified type mapper has not been installed in this component, + * an empty optional is returned. + *

+ * Warning! This is potentially a very expensive operation. + * + * @param the database type + * @param the java type + * @param typeMapper the type mapper to locate + * @return a database type for which the mapper is used + */ + Optional> findDatabaseTypeOf(TypeMapper typeMapper); } \ No newline at end of file diff --git a/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/internal/component/TypeMapperComponentImpl.java b/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/internal/component/TypeMapperComponentImpl.java index eb447d97f0..377fa3b094 100644 --- a/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/internal/component/TypeMapperComponentImpl.java +++ b/generator-parent/generator-translator/src/main/java/com/speedment/generator/translator/internal/component/TypeMapperComponentImpl.java @@ -23,6 +23,7 @@ import com.speedment.runtime.config.Column; import com.speedment.runtime.typemapper.TypeMapper; import com.speedment.runtime.typemapper.bigdecimal.BigDecimalToDouble; +import com.speedment.runtime.typemapper.exception.SpeedmentTypeMapperException; import com.speedment.runtime.typemapper.integer.IntegerZeroOneToBooleanMapper; import com.speedment.runtime.typemapper.largeobject.ClobToStringMapper; import com.speedment.runtime.typemapper.primitive.PrimitiveTypeMapper; @@ -151,4 +152,34 @@ public void install(Class databaseType, Supplier> typeMapper }).orElseGet(TypeMapper::identity) ); } + + @Override + public Optional> findDatabaseTypeOf(TypeMapper typeMapper) { + final Class needle = typeMapper.getClass(); + return mappers.entrySet().stream() + .filter(e -> e.getValue().stream() + .map(Supplier>::get) + .map(TypeMapper::getClass) + .anyMatch(needle::equals) + ) + .map(Map.Entry::getKey) + .findAny() + .map(key -> { + try { + @SuppressWarnings("unchecked") + final Class result = (Class) Class.forName(key); + return result; + } catch (final ClassNotFoundException ex) { + throw new SpeedmentTranslatorException( + "Could not find installed type mapper key '" + key + + "' on class path.", ex + ); + } catch (final ClassCastException ex) { + throw new SpeedmentTranslatorException( + "An error occured when an installed type mapper key " + + "was thrown to the expected type '" + key + "'.", ex + ); + } + }); + } } \ No newline at end of file diff --git a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/ReferenceStreamBuilder.java b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/ReferenceStreamBuilder.java index 86023d167d..aff368d5b9 100644 --- a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/ReferenceStreamBuilder.java +++ b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/ReferenceStreamBuilder.java @@ -433,5 +433,4 @@ public Spliterator spliterator() { throw new UnsupportedOperationException(UNSUPPORTED_BECAUSE_OF_CLOSE_MAY_NOT_BE_CALLED); //return streamTerminator.spliterator(pipeline()); } - -} +} \ No newline at end of file