/**
*
* Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); You may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.speedment.generator.standard.manager;
import static com.speedment.common.codegen.constant.DefaultType.isPrimitive;
import static com.speedment.common.codegen.constant.DefaultType.wrapperFor;
import com.speedment.common.codegen.constant.SimpleParameterizedType;
import com.speedment.common.codegen.model.AnnotationUsage;
import com.speedment.common.codegen.model.Class;
import com.speedment.common.codegen.model.Constructor;
import com.speedment.common.codegen.model.Field;
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.codegen.model.Value;
import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.Inject;
import com.speedment.common.injector.annotation.WithState;
import static com.speedment.generator.standard.internal.util.GenerateMethodBodyUtil.generateApplyResultSetBody;
import com.speedment.generator.translator.AbstractEntityAndManagerTranslator;
import com.speedment.generator.translator.TranslatorSupport;
import com.speedment.generator.translator.component.TypeMapperComponent;
import com.speedment.generator.translator.exception.SpeedmentTranslatorException;
import com.speedment.runtime.config.Column;
import com.speedment.runtime.config.Dbms;
import com.speedment.runtime.config.Project;
import com.speedment.runtime.config.Table;
import com.speedment.runtime.config.identifier.TableIdentifier;
import com.speedment.runtime.config.trait.HasEnabled;
import com.speedment.runtime.core.component.DbmsHandlerComponent;
import com.speedment.runtime.core.component.ProjectComponent;
import com.speedment.runtime.core.component.resultset.ResultSetMapperComponent;
import com.speedment.runtime.core.component.resultset.ResultSetMapping;
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.core.exception.SpeedmentException;
import com.speedment.runtime.core.internal.util.sql.ResultSetUtil;
import static com.speedment.runtime.core.util.DatabaseUtil.dbmsTypeOf;
import com.speedment.runtime.typemapper.TypeMapper;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;
import java.util.stream.Stream;
/**
*
* @author Emil Forslund
* @since 3.0.1
*/
public final class GeneratedSqlAdapterTranslator extends AbstractEntityAndManagerTranslator<Class> {
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 typeMapperComponent;
public GeneratedSqlAdapterTranslator(Table table) {
super(table, Class::of);
}
@Override
protected Class makeCodeGenModel(File file) {
final Type tableIdentifierType = SimpleParameterizedType
.create(TableIdentifier.class, getSupport().entityType());
return newBuilder(file, getClassOrInterfaceName())
.forEveryTable((clazz, table) -> {
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().getId(),
getSupport().schemaOrThrow().getId(),
getSupport().tableOrThrow().getId()
).map(s -> "\"" + s + "\"").collect(joining(", "))
+ ");")
)
// Generate member fields
.add(Field.of("tableIdentifier", tableIdentifierType).private_().final_())
// Generate methods
.add(Method.of(INSTALL_METHOD_NAME, void.class).add(withExecuteBefore(file))
.add(Field.of("streamSupplierComponent", SqlStreamSupplierComponent.class)
.add(AnnotationUsage.of(WithState.class).set(Value.ofReference("RESOLVED")))
)
.add(Field.of("persistenceComponent", SqlPersistenceComponent.class)
.add(AnnotationUsage.of(WithState.class).set(Value.ofReference("RESOLVED")))
)
.add("streamSupplierComponent.install(tableIdentifier, this::apply);")
.add("persistenceComponent.install(tableIdentifier);")
)
.add(generateApplyResultSet(getSupport(), file, table::columns))
.add(generateCreateEntity(file))
.call(() -> {
file.add(Import.of(State.class).setStaticMember("RESOLVED").static_());
// 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 Type javaType = tm.getJavaType(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 +
"'."
)),
isPrimitive(javaType)
? wrapperFor(javaType)
: javaType
);
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();
}
private Method generateCreateEntity(File file) {
final Type entityImplType = getSupport().entityImplType();
file.add(Import.of(entityImplType));
return Method.of("createEntity", entityImplType).protected_()
.add("return new "+getSupport().entityImplName()+"();");
}
@Override
protected String getJavadocRepresentText() {
return "The generated Sql Adapter for a {@link "
+ getSupport().entityType().getTypeName() + "} entity.";
}
@Override
protected String getClassOrInterfaceName() {
return getSupport().generatedSqlAdapterName();
}
@Override
public boolean isInGeneratedPackage() {
return true;
}
public Type getImplType() {
return getSupport().managerImplType();
}
private Method generateApplyResultSet(TranslatorSupport<Table> support, File file, Supplier<Stream<? extends Column>> columnsSupplier) {
return Method.of("apply", support.entityType())
.protected_()
.add(SpeedmentException.class)
.add(Field.of("resultSet", ResultSet.class))
.add(generateApplyResultSetBody(this::readFromResultSet, support, file, columnsSupplier));
}
private static Set<java.lang.Class<?>> NULL_AWARE_GETTERS = Stream.of(
String.class,
BigDecimal.class,
java.sql.Time.class,
java.sql.Date.class,
java.sql.Timestamp.class,
Blob.class,
Clob.class
).collect(toSet());
private String readFromResultSet(File file, Column c, AtomicInteger position) {
final Dbms dbms = c.getParentOrThrow().getParentOrThrow().getParentOrThrow();
final ResultSetMapping<?> mapping = resultSetMapperComponent.apply(
dbmsTypeOf(dbmsHandlerComponent, c.getParentOrThrow().getParentOrThrow().getParentOrThrow()),
c.findDatabaseType()
);
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 (isCustomTypeMapper) {
sb.append(helperName(c)).append(".apply(");
}
final String getterName = "get" + mapping.getResultSetMethodName(dbms);
// We do not need to wrap-get some classes X since getX() returns null for null X:es.
if (c.isNullable() && !NULL_AWARE_GETTERS.contains(mapping.getJavaClass())) {
file.add(Import.of(ResultSetUtil.class).static_().setStaticMember("*"));
sb.append(getterName).append("(resultSet, ")
.append(position.getAndIncrement()).append(")");
} else {
sb.append("resultSet.").append(getterName)
.append("(").append(position.getAndIncrement()).append(")");
}
if (isCustomTypeMapper) {
sb.append(")");
}
return sb.toString();
}
private AnnotationUsage withExecuteBefore(File file) {
file.add(Import.of(State.class).static_().setStaticMember("RESOLVED"));
return AnnotationUsage.of(ExecuteBefore.class).set(Value.ofReference("RESOLVED"));
}
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;
}
}
}
}