All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.speedment.generator.standard.manager.GeneratedSqlAdapterTranslator Maven / Gradle / Ivy

Go to download

A Speedment bundle that shades all dependencies into one jar. This is useful when deploying an application on a server.

The newest version!
/*
 *
 * Copyright (c) 2006-2019, 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 com.speedment.common.codegen.constant.SimpleParameterizedType;
import com.speedment.common.codegen.constant.SimpleType;
import com.speedment.common.codegen.model.*;
import com.speedment.common.codegen.model.Class;
import com.speedment.common.injector.State;
import com.speedment.common.injector.annotation.ExecuteBefore;
import com.speedment.common.injector.annotation.Inject;
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.SqlAdapter;
import com.speedment.runtime.core.component.resultset.ResultSetMapperComponent;
import com.speedment.runtime.core.component.resultset.ResultSetMapping;
import com.speedment.runtime.core.component.sql.SqlTypeMapperHelper;
import com.speedment.runtime.core.db.SqlFunction;
import com.speedment.runtime.core.internal.util.sql.ResultSetUtil;
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.sql.SQLException;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Supplier;
import java.util.stream.Stream;

import static com.speedment.common.codegen.constant.DefaultAnnotationUsage.OVERRIDE;
import static com.speedment.common.codegen.constant.DefaultType.isPrimitive;
import static com.speedment.common.codegen.constant.DefaultType.wrapperFor;
import static com.speedment.common.codegen.util.Formatting.shortName;
import static com.speedment.generator.standard.internal.util.GenerateMethodBodyUtil.generateApplyResultSetBody;
import static com.speedment.runtime.core.util.DatabaseUtil.dbmsTypeOf;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toSet;

/**
 *
 * @author Emil Forslund
 * @since 3.0.1
 */
public final class GeneratedSqlAdapterTranslator
    extends AbstractEntityAndManagerTranslator {

    public final static String CREATE_HELPERS_METHOD_NAME = "createHelpers";
    public final static String INSTALL_METHOD_NAME = "installMethodName";
    public final static String OFFSET_PARAMETER_NAME = "offset";

    @Inject
    private ResultSetMapperComponent resultSetMapperComponent;
    @Inject
    private DbmsHandlerComponent dbmsHandlerComponent;
    @Inject
    private 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_()
                    .add(
                        SimpleParameterizedType.create(
                            SqlAdapter.class,
                            getSupport().entityType()
                        )
                    )
                    ////////////////////////////////////////////////////////////
                    // Generate constructor                                   //
                    ////////////////////////////////////////////////////////////
                    .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 members 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))
                    .add(
                        Method.of("identifier", tableIdentifierType)
                            .public_()
                            .add(OVERRIDE)
                            .add("return tableIdentifier;")
                    )
                    .add(
                        Method.of("entityMapper", SimpleParameterizedType.create(SqlFunction.class, ResultSet.class, getSupport().entityType()))
                            .public_()
                            .add(OVERRIDE)
                            .add("return entityMapper(0);")
                    )
                    .add(
                        Method.of("entityMapper", SimpleParameterizedType.create(SqlFunction.class, ResultSet.class, getSupport().entityType()))
                            .public_()
                            .add(OVERRIDE)
                            .add(Field.of(OFFSET_PARAMETER_NAME, int.class))
                            .add("return rs -> apply(rs, offset);")
                    )
                    .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 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;
    }

    private Method generateApplyResultSet(
        TranslatorSupport support,
        File file,
        Supplier> columnsSupplier) {

        return Method.of("apply", support.entityType())
            .protected_()
            .add(SQLException.class)
            .add(Field.of("resultSet", ResultSet.class))
            .add(Field.of(OFFSET_PARAMETER_NAME, int.class))
            .add(generateApplyResultSetBody(
                this::readFromResultSet, support, file, columnsSupplier
            ));
    }

    private static Set> 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,
        Object.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("*"));

            if (isCastingRequired(c, getterName)) {
                file.add(Import.of(SimpleType.create(c.getDatabaseType())));
                sb.append("(").append(shortName(c.getDatabaseType())).append(") ");
            }

            sb.append(getterName).append("(resultSet, ")
                .append(position.getAndIncrement()).append(" + ").append(OFFSET_PARAMETER_NAME).append(")");
        } else {
            if (isCastingRequired(c, getterName)) {
                file.add(Import.of(SimpleType.create(c.getDatabaseType())));
                sb.append("(").append(shortName(c.getDatabaseType())).append(") ");
            }

            sb.append("resultSet.").append(getterName)
                .append("(").append(position.getAndIncrement()).append(" + ").append(OFFSET_PARAMETER_NAME)
                .append(")");
        }

        if (isCustomTypeMapper) {
            sb.append(")");
        }

        return sb.toString();
    }

    private boolean isCastingRequired(Column column, String getterName) {
        return ("getObject".equals(getterName)
            && !Object.class.getName().equals(column.getDatabaseType()));
    }

    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";
    }
}