Package com.asakusafw.dmdl.windgate.jdbc.driver

Source Code of com.asakusafw.dmdl.windgate.jdbc.driver.JdbcSupportEmitter$SupportGenerator

/**
* Copyright 2011-2014 Asakusa Framework Team.
*
* 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.asakusafw.dmdl.windgate.jdbc.driver;

import java.io.IOException;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

import org.apache.hadoop.io.Text;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.dmdl.java.emitter.EmitContext;
import com.asakusafw.dmdl.java.spi.JavaDataModelDriver;
import com.asakusafw.dmdl.model.BasicTypeKind;
import com.asakusafw.dmdl.semantics.ModelDeclaration;
import com.asakusafw.dmdl.semantics.PropertyDeclaration;
import com.asakusafw.dmdl.semantics.Type;
import com.asakusafw.dmdl.semantics.type.BasicType;
import com.asakusafw.runtime.value.Date;
import com.asakusafw.runtime.value.DateTime;
import com.asakusafw.runtime.value.DateUtil;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.collections.Maps;
import com.asakusafw.utils.java.model.syntax.ClassDeclaration;
import com.asakusafw.utils.java.model.syntax.Expression;
import com.asakusafw.utils.java.model.syntax.ExpressionStatement;
import com.asakusafw.utils.java.model.syntax.FieldDeclaration;
import com.asakusafw.utils.java.model.syntax.FormalParameterDeclaration;
import com.asakusafw.utils.java.model.syntax.InfixOperator;
import com.asakusafw.utils.java.model.syntax.MethodDeclaration;
import com.asakusafw.utils.java.model.syntax.ModelFactory;
import com.asakusafw.utils.java.model.syntax.Name;
import com.asakusafw.utils.java.model.syntax.PostfixOperator;
import com.asakusafw.utils.java.model.syntax.SimpleName;
import com.asakusafw.utils.java.model.syntax.Statement;
import com.asakusafw.utils.java.model.syntax.TypeBodyDeclaration;
import com.asakusafw.utils.java.model.syntax.TypeParameterDeclaration;
import com.asakusafw.utils.java.model.syntax.WildcardBoundKind;
import com.asakusafw.utils.java.model.util.AttributeBuilder;
import com.asakusafw.utils.java.model.util.ExpressionBuilder;
import com.asakusafw.utils.java.model.util.JavadocBuilder;
import com.asakusafw.utils.java.model.util.Models;
import com.asakusafw.utils.java.model.util.TypeBuilder;
import com.asakusafw.windgate.core.vocabulary.DataModelJdbcSupport;
import com.asakusafw.windgate.core.vocabulary.DataModelJdbcSupport.DataModelPreparedStatement;
import com.asakusafw.windgate.core.vocabulary.DataModelJdbcSupport.DataModelResultSet;

/**
* Emits {@link DataModelJdbcSupport} implementations.
* @since 0.2.2
*/
public class JdbcSupportEmitter extends JavaDataModelDriver {

    static final Logger LOG = LoggerFactory.getLogger(JdbcSupportEmitter.class);

    /**
     * Category name for JDBC support.
     */
    public static final String CATEGORY_JDBC = "jdbc";

    @Override
    public void generateResources(EmitContext context, ModelDeclaration model) throws IOException {
        if (isTarget(model) == false) {
            return;
        }
        checkColumnExists(model);
        checkColumnType(model);
        checkColumnConflict(model);
        Name supportClassName = generateSupport(context, model);
        if (hasTableTrait(model)) {
            generateImporterDescription(context, model, supportClassName);
            generateExporterDescription(context, model, supportClassName);
        }
    }

    private Name generateSupport(EmitContext context, ModelDeclaration model) throws IOException {
        assert context != null;
        assert model != null;
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                CATEGORY_JDBC,
                "{0}JdbcSupport");
        LOG.debug("Generating JDBC support for {}",
                context.getQualifiedTypeName().toNameString());
        SupportGenerator.emit(next, model);
        LOG.debug("Generated JDBC support for {}: {}",
                context.getQualifiedTypeName().toNameString(),
                next.getQualifiedTypeName().toNameString());
        return next.getQualifiedTypeName();
    }

    private void generateImporterDescription(
            EmitContext context,
            ModelDeclaration model,
            Name supportClassName) throws IOException {
        assert context != null;
        assert model != null;
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                CATEGORY_JDBC,
                "Abstract{0}JdbcImporterDescription");
        LOG.debug("Generating importer description using JDBC for {}",
                context.getQualifiedTypeName().toNameString());
        DescriptionGenerator.emitImporter(next, model, supportClassName);
        LOG.debug("Generated importer description using JDBC for {}: {}",
                context.getQualifiedTypeName().toNameString(),
                next.getQualifiedTypeName().toNameString());
    }

    private void generateExporterDescription(
            EmitContext context,
            ModelDeclaration model,
            Name supportClassName) throws IOException {
        assert context != null;
        assert model != null;
        EmitContext next = new EmitContext(
                context.getSemantics(),
                context.getConfiguration(),
                model,
                CATEGORY_JDBC,
                "Abstract{0}JdbcExporterDescription");
        LOG.debug("Generating exporter description using JDBC for {}",
                context.getQualifiedTypeName().toNameString());
        DescriptionGenerator.emitExporter(next, model, supportClassName);
        LOG.debug("Generated exporter description using JDBC for {}: {}",
                context.getQualifiedTypeName().toNameString(),
                next.getQualifiedTypeName().toNameString());
    }

    private boolean isTarget(ModelDeclaration model) {
        assert model != null;
        return hasTableTrait(model) || hasColumnTrait(model);
    }

    private boolean hasTableTrait(ModelDeclaration model) {
        assert model != null;
        return model.getTrait(JdbcTableTrait.class) != null;
    }

    private boolean hasColumnTrait(ModelDeclaration model) {
        assert model != null;
        boolean sawTrait = false;
        for (PropertyDeclaration prop : model.getDeclaredProperties()) {
            if (prop.getTrait(JdbcColumnTrait.class) != null) {
                sawTrait = true;
            }
        }
        return sawTrait;
    }

    private void checkColumnExists(ModelDeclaration model) throws IOException {
        assert model != null;
        if (hasColumnTrait(model) == false) {
            throw new IOException(MessageFormat.format(
                    "Model \"{0}\" has no columns, please specify @{1} into properties ",
                    model.getName().identifier,
                    JdbcColumnDriver.TARGET_NAME));
        }
    }

    private void checkColumnType(ModelDeclaration model) throws IOException {
        assert model != null;
        for (PropertyDeclaration prop : model.getDeclaredProperties()) {
            if (model.getTrait(JdbcColumnTrait.class) == null) {
                continue;
            }
            Type type = prop.getType();
            if ((type instanceof BasicType) == false) {
                throw new IOException(MessageFormat.format(
                        "Type \"{0}\" can not map to a column: {1}.{2} ",
                        type,
                        model.getName().identifier,
                        prop.getName().identifier));
            }
        }
    }

    private void checkColumnConflict(ModelDeclaration model) throws IOException {
        assert model != null;
        Map<String, PropertyDeclaration> saw = Maps.create();
        for (PropertyDeclaration prop : model.getDeclaredProperties()) {
            JdbcColumnTrait trait = prop.getTrait(JdbcColumnTrait.class);
            if (trait == null) {
                continue;
            }
            String name = trait.getName();
            if (saw.containsKey(name)) {
                PropertyDeclaration other = saw.get(name);
                throw new IOException(MessageFormat.format(
                        "Column name \"{0}\" is already declared in \"{3}\": {1}.{2}",
                        name,
                        model.getName().identifier,
                        prop.getName().identifier,
                        other.getName().identifier));
            }
            saw.put(name, prop);
        }
    }

    private static final class SupportGenerator {

        private static final String NAME_CALENDAR = "calendar";

        private static final String NAME_DATETIME = "datetime";

        private static final String NAME_DATE = "date";

        private static final String NAME_TEXT = "text";

        private static final String NAME_RESULT_SET_SUPPORT = "ResultSetSupport";

        private static final String NAME_PREPARED_STATEMENT_SUPPORT = "PreparedStatementSupport";

        private static final String NAME_PROPERTY_POSITIONS = "PROPERTY_POSITIONS";

        private static final String NAME_CREATE_VECTOR = "createPropertyVector";

        private final EmitContext context;

        private final ModelDeclaration model;

        private final ModelFactory f;

        private SupportGenerator(EmitContext context, ModelDeclaration model) {
            assert context != null;
            assert model != null;
            this.context = context;
            this.model = model;
            this.f = context.getModelFactory();
        }

        static void emit(EmitContext context, ModelDeclaration model) throws IOException {
            assert context != null;
            assert model != null;
            SupportGenerator emitter = new SupportGenerator(context, model);
            emitter.emit();
        }

        private void emit() throws IOException {
            ClassDeclaration decl = f.newClassDeclaration(
                    new JavadocBuilder(f)
                        .text("Supports JDBC interfaces for ",
                                model.getName())
                        .linkType(context.resolve(model.getSymbol()))
                        .text(".")
                        .toJavadoc(),
                    new AttributeBuilder(f)
                        .Public()
                        .Final()
                        .toAttributes(),
                    context.getTypeName(),
                    Collections.<TypeParameterDeclaration>emptyList(),
                    null,
                    Collections.singletonList(f.newParameterizedType(
                            context.resolve(DataModelJdbcSupport.class),
                            context.resolve(model.getSymbol()))),
                    createMembers());
            context.emit(decl);
        }

        private List<TypeBodyDeclaration> createMembers() {
            List<TypeBodyDeclaration> results = Lists.create();
            results.addAll(createMetaData());
            results.add(createGetSupportedType());
            results.add(createIsSupported());
            results.add(createCreateResultSetSupport());
            results.add(createCreatePreparedStatementSupport());
            results.add(createCreatePropertyVector());
            results.add(createResultSetSupportClass());
            results.add(createPreparedStatementSupportClass());
            return results;
        }

        private List<TypeBodyDeclaration> createMetaData() {
            List<TypeBodyDeclaration> results = Lists.create();
            results.add(f.newFieldDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .Static()
                        .Final()
                        .toAttributes(),
                    f.newParameterizedType(
                            context.resolve(Map.class),
                            context.resolve(String.class),
                            context.resolve(Integer.class)),
                    f.newSimpleName(NAME_PROPERTY_POSITIONS),
                    null));
            List<Statement> statements = Lists.create();
            SimpleName map = f.newSimpleName("map");
            statements.add(new TypeBuilder(f, context.resolve(TreeMap.class))
                .parameterize(context.resolve(String.class), context.resolve(Integer.class))
                .newObject()
                .toLocalVariableDeclaration(
                        f.newParameterizedType(
                                context.resolve(Map.class),
                                context.resolve(String.class),
                                context.resolve(Integer.class)),
                        map));

            List<PropertyDeclaration> properties = getProperties();
            for (int i = 0, n = properties.size(); i < n; i++) {
                PropertyDeclaration property = properties.get(i);
                JdbcColumnTrait trait = property.getTrait(JdbcColumnTrait.class);
                assert trait != null;
                statements.add(new ExpressionBuilder(f, map)
                    .method("put", Models.toLiteral(f, trait.getName()), Models.toLiteral(f, i))
                    .toStatement());
            }
            statements.add(new ExpressionBuilder(f, f.newSimpleName(NAME_PROPERTY_POSITIONS))
                .assignFrom(map)
                .toStatement());
            results.add(f.newInitializerDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Static()
                        .toAttributes(),
                    f.newBlock(statements)));
            return results;
        }

        private MethodDeclaration createGetSupportedType() {
            MethodDeclaration decl = f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    f.newParameterizedType(
                            context.resolve(Class.class),
                            context.resolve(model.getSymbol())),
                    f.newSimpleName("getSupportedType"),
                    Collections.<FormalParameterDeclaration>emptyList(),
                    Arrays.asList(new Statement[] {
                            new TypeBuilder(f, context.resolve(model.getSymbol()))
                                .dotClass()
                                .toReturnStatement()
                    }));
            return decl;
        }

        private MethodDeclaration createIsSupported() {
            SimpleName columnNames = f.newSimpleName("columnNames");
            List<Statement> statements = Lists.create();
            statements.add(createNullCheck(columnNames));
            statements.add(f.newIfStatement(
                    new ExpressionBuilder(f, columnNames)
                        .method("isEmpty")
                        .toExpression(),
                    f.newBlock(f.newReturnStatement(Models.toLiteral(f, false)))));
            statements.add(f.newTryStatement(
                    f.newBlock(
                            new ExpressionBuilder(f, f.newThis())
                                .method(NAME_CREATE_VECTOR, columnNames)
                                .toStatement(),
                            new ExpressionBuilder(f, Models.toLiteral(f, true))
                                .toReturnStatement()),
                    Arrays.asList(f.newCatchClause(
                            f.newFormalParameterDeclaration(
                                    context.resolve(IllegalArgumentException.class),
                                    f.newSimpleName("e")),
                            f.newBlock(new ExpressionBuilder(f, Models.toLiteral(f, false))
                                .toReturnStatement()))),
                    null));
            MethodDeclaration decl = f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    context.resolve(boolean.class),
                    f.newSimpleName("isSupported"),
                    Arrays.asList(f.newFormalParameterDeclaration(
                            f.newParameterizedType(
                                    context.resolve(List.class),
                                    context.resolve(String.class)),
                            columnNames)),
                    statements);
            return decl;
        }

        private MethodDeclaration createCreateResultSetSupport() {
            SimpleName resultSet = f.newSimpleName("resultSet");
            SimpleName columnNames = f.newSimpleName("columnNames");
            List<Statement> statements = Lists.create();
            statements.add(createNullCheck(resultSet));
            statements.add(createNullCheck(columnNames));
            SimpleName vector = f.newSimpleName("vector");
            statements.add(new ExpressionBuilder(f, f.newThis())
                .method(NAME_CREATE_VECTOR, columnNames)
                .toLocalVariableDeclaration(context.resolve(int[].class), vector));
            statements.add(new TypeBuilder(f, f.newNamedType(f.newSimpleName(NAME_RESULT_SET_SUPPORT)))
                .newObject(resultSet, vector)
                .toReturnStatement());
            MethodDeclaration decl = f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    context.resolve(f.newParameterizedType(
                            context.resolve(DataModelResultSet.class),
                            context.resolve(model.getSymbol()))),
                    f.newSimpleName("createResultSetSupport"),
                    Arrays.asList(
                            f.newFormalParameterDeclaration(
                                context.resolve(ResultSet.class),
                                resultSet),
                            f.newFormalParameterDeclaration(
                                    f.newParameterizedType(
                                            context.resolve(List.class),
                                            context.resolve(String.class)),
                                    columnNames)),
                    statements);
            return decl;
        }

        private MethodDeclaration createCreatePreparedStatementSupport() {
            SimpleName preparedStatement = f.newSimpleName("statement");
            SimpleName columnNames = f.newSimpleName("columnNames");
            List<Statement> statements = Lists.create();
            statements.add(createNullCheck(preparedStatement));
            statements.add(createNullCheck(columnNames));
            SimpleName vector = f.newSimpleName("vector");
            statements.add(new ExpressionBuilder(f, f.newThis())
                .method(NAME_CREATE_VECTOR, columnNames)
                .toLocalVariableDeclaration(context.resolve(int[].class), vector));
            statements.add(new TypeBuilder(f, f.newNamedType(f.newSimpleName(NAME_PREPARED_STATEMENT_SUPPORT)))
                .newObject(preparedStatement, vector)
                .toReturnStatement());
            MethodDeclaration decl = f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    context.resolve(f.newParameterizedType(
                            context.resolve(DataModelPreparedStatement.class),
                            context.resolve(model.getSymbol()))),
                    f.newSimpleName("createPreparedStatementSupport"),
                    Arrays.asList(
                            f.newFormalParameterDeclaration(
                                context.resolve(PreparedStatement.class),
                                preparedStatement),
                            f.newFormalParameterDeclaration(
                                    f.newParameterizedType(
                                            context.resolve(List.class),
                                            context.resolve(String.class)),
                                    columnNames)),
                    statements);
            return decl;
        }

        private Statement createNullCheck(SimpleName parameter) {
            assert parameter != null;
            return f.newIfStatement(
                    new ExpressionBuilder(f, parameter)
                        .apply(InfixOperator.EQUALS, Models.toNullLiteral(f))
                        .toExpression(),
                    f.newBlock(new TypeBuilder(f, context.resolve(IllegalArgumentException.class))
                        .newObject(Models.toLiteral(f, MessageFormat.format(
                                "{0} must not be null",
                                parameter.getToken())))
                        .toThrowStatement()));
        }

        private MethodDeclaration createCreatePropertyVector() {
            SimpleName columnNames = f.newSimpleName("columnNames");
            SimpleName vector = f.newSimpleName("vector");
            List<Statement> statements = Lists.create();

            statements.add(new TypeBuilder(f, context.resolve(int[].class))
                .newArray(new ExpressionBuilder(f, f.newSimpleName(NAME_PROPERTY_POSITIONS))
                    .method("size")
                    .toExpression())
                .toLocalVariableDeclaration(context.resolve(int[].class), vector));

            SimpleName index = f.newSimpleName("i");
            SimpleName column = f.newSimpleName("column");
            SimpleName position = f.newSimpleName("position");
            statements.add(f.newForStatement(
                    f.newLocalVariableDeclaration(
                            new AttributeBuilder(f).toAttributes(),
                            context.resolve(int.class),
                            Arrays.asList(
                                    f.newVariableDeclarator(
                                            index,
                                            Models.toLiteral(f, 0)),
                                    f.newVariableDeclarator(
                                            f.newSimpleName("n"),
                                            new ExpressionBuilder(f, columnNames)
                                                .method("size")
                                                .toExpression()))),
                    new ExpressionBuilder(f, index)
                        .apply(InfixOperator.LESS, f.newSimpleName("n"))
                        .toExpression(),
                    f.newStatementExpressionList(new ExpressionBuilder(f, index)
                        .apply(PostfixOperator.INCREMENT)
                        .toExpression()),
                    f.newBlock(new Statement[] {
                            new ExpressionBuilder(f, columnNames)
                                .method("get", index)
                                .toLocalVariableDeclaration(context.resolve(String.class), column),
                            new ExpressionBuilder(f, f.newSimpleName(NAME_PROPERTY_POSITIONS))
                                .method("get", column)
                                .toLocalVariableDeclaration(context.resolve(Integer.class), position),
                            f.newIfStatement(
                                    new ExpressionBuilder(f, position)
                                        .apply(InfixOperator.EQUALS, Models.toNullLiteral(f))
                                        .apply(InfixOperator.CONDITIONAL_OR, new ExpressionBuilder(f, vector)
                                            .array(position)
                                            .apply(InfixOperator.NOT_EQUALS, Models.toLiteral(f, 0))
                                            .toExpression())
                                        .toExpression(),
                                    f.newBlock(new TypeBuilder(f, context.resolve(IllegalArgumentException.class))
                                        .newObject(column)
                                        .toThrowStatement())),
                            new ExpressionBuilder(f, vector)
                                .array(position)
                                .assignFrom(new ExpressionBuilder(f, index)
                                    .apply(InfixOperator.PLUS, Models.toLiteral(f, 1))
                                    .toExpression())
                            .toStatement()
                    })));
            statements.add(new ExpressionBuilder(f, vector).toReturnStatement());
            return f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .toAttributes(),
                    context.resolve(context.resolve(int[].class)),
                    f.newSimpleName(NAME_CREATE_VECTOR),
                    Arrays.asList(f.newFormalParameterDeclaration(
                            f.newParameterizedType(
                                    context.resolve(List.class),
                                    context.resolve(String.class)),
                            columnNames)),
                    statements);
        }

        private ClassDeclaration createResultSetSupportClass() {
            SimpleName resultSet = f.newSimpleName("resultSet");
            SimpleName properties = f.newSimpleName("properties");
            List<TypeBodyDeclaration> members = Lists.create();
            members.add(createPrivateField(ResultSet.class, resultSet, false));
            members.add(createPrivateField(int[].class, properties, false));
            Set<BasicTypeKind> kinds = collectTypeKinds();
            if (kinds.contains(BasicTypeKind.TEXT)) {
                members.add(createPrivateField(Text.class, f.newSimpleName(NAME_TEXT), true));
            }
            if (kinds.contains(BasicTypeKind.DATE)) {
                members.add(createPrivateField(Date.class, f.newSimpleName(NAME_DATE), true));
            }
            if (kinds.contains(BasicTypeKind.DATETIME)) {
                members.add(createPrivateField(DateTime.class, f.newSimpleName(NAME_DATETIME), true));
            }
            if (kinds.contains(BasicTypeKind.DATE) || kinds.contains(BasicTypeKind.DATETIME)) {
                members.add(createCalendarBuffer());
            }
            members.add(f.newConstructorDeclaration(
                    null,
                    new AttributeBuilder(f).toAttributes(),
                    f.newSimpleName(NAME_RESULT_SET_SUPPORT),
                    Arrays.asList(
                            f.newFormalParameterDeclaration(context.resolve(ResultSet.class), resultSet),
                            f.newFormalParameterDeclaration(context.resolve(int[].class), properties)),
                    Arrays.asList(mapField(resultSet), mapField(properties))));

            SimpleName object = f.newSimpleName("object");
            List<Statement> statements = Lists.create();
            statements.add(f.newIfStatement(
                    new ExpressionBuilder(f, resultSet)
                        .method("next")
                        .apply(InfixOperator.EQUALS, Models.toLiteral(f, false))
                        .toExpression(),
                    f.newBlock(new ExpressionBuilder(f, Models.toLiteral(f, false))
                        .toReturnStatement())));
            List<PropertyDeclaration> declared = getProperties();
            for (int i = 0, n = declared.size(); i < n; i++) {
                statements.add(createResultSetSupportStatement(
                        object,
                        resultSet,
                        f.newArrayAccessExpression(properties, Models.toLiteral(f, i)),
                        declared.get(i)));
            }
            statements.add(new ExpressionBuilder(f, Models.toLiteral(f, true))
                .toReturnStatement());
            members.add(f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    Collections.<TypeParameterDeclaration>emptyList(),
                    context.resolve(boolean.class),
                    f.newSimpleName("next"),
                    Arrays.asList(f.newFormalParameterDeclaration(context.resolve(model.getSymbol()), object)),
                    0,
                    Arrays.asList(context.resolve(SQLException.class)),
                    f.newBlock(statements)));

            return f.newClassDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .Static()
                        .Final()
                        .toAttributes(),
                    f.newSimpleName(NAME_RESULT_SET_SUPPORT),
                    null,
                    Arrays.asList(f.newParameterizedType(
                            context.resolve(DataModelResultSet.class),
                            context.resolve(model.getSymbol()))),
                    members);
        }

        private Set<BasicTypeKind> collectTypeKinds() {
            EnumSet<BasicTypeKind> kinds = EnumSet.noneOf(BasicTypeKind.class);
            for (PropertyDeclaration prop : getProperties()) {
                kinds.add(toBasicKind(prop.getType()));
            }
            return kinds;
        }

        private FieldDeclaration createCalendarBuffer() {
            return f.newFieldDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .Final()
                        .toAttributes(),
                    context.resolve(Calendar.class),
                    f.newSimpleName(NAME_CALENDAR),
                    new TypeBuilder(f, context.resolve(Calendar.class))
                        .method("getInstance")
                        .toExpression());
        }

        private ClassDeclaration createPreparedStatementSupportClass() {
            SimpleName preparedStatement = f.newSimpleName("statement");
            SimpleName properties = f.newSimpleName("properties");
            List<TypeBodyDeclaration> members = Lists.create();
            members.add(createPrivateField(PreparedStatement.class, preparedStatement, false));
            members.add(createPrivateField(int[].class, properties, false));
            Set<BasicTypeKind> kinds = collectTypeKinds();
            if (kinds.contains(BasicTypeKind.DATE)) {
                members.add(f.newFieldDeclaration(
                        null,
                        new AttributeBuilder(f)
                            .Private()
                            .Final()
                            .toAttributes(),
                        context.resolve(java.sql.Date.class),
                        f.newSimpleName(NAME_DATE),
                        new TypeBuilder(f, context.resolve(java.sql.Date.class))
                            .newObject(Models.toLiteral(f, 0L))
                            .toExpression()));
            }
            if (kinds.contains(BasicTypeKind.DATETIME)) {
                members.add(f.newFieldDeclaration(
                        null,
                        new AttributeBuilder(f)
                            .Private()
                            .Final()
                            .toAttributes(),
                        context.resolve(java.sql.Timestamp.class),
                        f.newSimpleName(NAME_DATETIME),
                        new TypeBuilder(f, context.resolve(java.sql.Timestamp.class))
                            .newObject(Models.toLiteral(f, 0L))
                            .toExpression()));
            }
            if (kinds.contains(BasicTypeKind.DATE) || kinds.contains(BasicTypeKind.DATETIME)) {
                members.add(createCalendarBuffer());
            }
            members.add(f.newConstructorDeclaration(
                    null,
                    new AttributeBuilder(f).toAttributes(),
                    f.newSimpleName(NAME_PREPARED_STATEMENT_SUPPORT),
                    Arrays.asList(
                            f.newFormalParameterDeclaration(
                                    context.resolve(PreparedStatement.class), preparedStatement),
                            f.newFormalParameterDeclaration(
                                    context.resolve(int[].class), properties)),
                    Arrays.asList(mapField(preparedStatement), mapField(properties))));

            SimpleName object = f.newSimpleName("object");
            List<Statement> statements = Lists.create();
            List<PropertyDeclaration> declared = getProperties();
            for (int i = 0, n = declared.size(); i < n; i++) {
                statements.add(createPreparedStatementSupportStatement(
                        object,
                        preparedStatement,
                        f.newArrayAccessExpression(properties, Models.toLiteral(f, i)),
                        declared.get(i)));
            }
            members.add(f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    Collections.<TypeParameterDeclaration>emptyList(),
                    context.resolve(void.class),
                    f.newSimpleName("setParameters"),
                    Arrays.asList(f.newFormalParameterDeclaration(context.resolve(model.getSymbol()), object)),
                    0,
                    Arrays.asList(context.resolve(SQLException.class)),
                    f.newBlock(statements)));

            return f.newClassDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .Static()
                        .Final()
                        .toAttributes(),
                    f.newSimpleName(NAME_PREPARED_STATEMENT_SUPPORT),
                    null,
                    Arrays.asList(f.newParameterizedType(
                            context.resolve(DataModelPreparedStatement.class),
                            context.resolve(model.getSymbol()))),
                    members);
        }

        private Statement createResultSetSupportStatement(
                Expression object,
                Expression resultSet,
                Expression position,
                PropertyDeclaration property) {
            List<Statement> statements = Lists.create();
            SimpleName value = f.newSimpleName("value");
            SimpleName calendar = f.newSimpleName(NAME_CALENDAR);
            SimpleName text = f.newSimpleName(NAME_TEXT);
            SimpleName date = f.newSimpleName(NAME_DATE);
            SimpleName datetime = f.newSimpleName(NAME_DATETIME);
            BasicTypeKind kind = toBasicKind(property.getType());
            switch (kind) {
            case INT:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getInt"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case LONG:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getLong"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case FLOAT:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getFloat"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case DOUBLE:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getDouble"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case BYTE:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getByte"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case SHORT:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getShort"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case BOOLEAN:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getBoolean"));
                statements.add(createResultSetNullMapping(object, resultSet, property));
                break;
            case DECIMAL:
                statements.add(createResultSetMapping(object, resultSet, position, property, "getBigDecimal"));
                break;
            case TEXT:
                statements.add(new ExpressionBuilder(f, resultSet)
                    .method("getString", position)
                    .toLocalVariableDeclaration(context.resolve(String.class), value));
                statements.add(f.newIfStatement(
                        new ExpressionBuilder(f, value)
                            .apply(InfixOperator.NOT_EQUALS, Models.toNullLiteral(f))
                            .toExpression(),
                        f.newBlock(
                                new ExpressionBuilder(f, text)
                                    .method("set", value)
                                    .toStatement(),
                                new ExpressionBuilder(f, object)
                                    .method(context.getValueSetterName(property), text)
                                    .toStatement()),
                        f.newBlock(setNullToProperty(object, property))));
                break;
            case DATE:
                statements.add(new ExpressionBuilder(f, resultSet)
                    .method("getDate", position, calendar)
                    .toLocalVariableDeclaration(context.resolve(java.sql.Date.class), value));
                statements.add(f.newIfStatement(
                        new ExpressionBuilder(f, value)
                            .apply(InfixOperator.NOT_EQUALS, Models.toNullLiteral(f))
                            .toExpression(),
                        f.newBlock(
                                new ExpressionBuilder(f, calendar)
                                    .method("setTime", value)
                                    .toStatement(),
                                new ExpressionBuilder(f, date)
                                    .method("setElapsedDays", new TypeBuilder(f, context.resolve(DateUtil.class))
                                        .method("getDayFromCalendar", calendar)
                                        .toExpression())
                                    .toStatement(),
                                new ExpressionBuilder(f, object)
                                    .method(context.getValueSetterName(property), date)
                                    .toStatement()),
                        f.newBlock(setNullToProperty(object, property))));
                break;
            case DATETIME:
                statements.add(new ExpressionBuilder(f, resultSet)
                    .method("getTimestamp", position, calendar)
                    .toLocalVariableDeclaration(context.resolve(java.sql.Timestamp.class), value));
                statements.add(f.newIfStatement(
                        new ExpressionBuilder(f, value)
                            .apply(InfixOperator.NOT_EQUALS, Models.toNullLiteral(f))
                            .toExpression(),
                        f.newBlock(
                                new ExpressionBuilder(f, calendar)
                                    .method("setTime", value)
                                    .toStatement(),
                                new ExpressionBuilder(f, datetime)
                                    .method("setElapsedSeconds", new TypeBuilder(f, context.resolve(DateUtil.class))
                                        .method("getSecondFromCalendar", calendar)
                                        .toExpression())
                                    .toStatement(),
                                new ExpressionBuilder(f, object)
                                    .method(context.getValueSetterName(property), datetime)
                                    .toStatement()),
                        f.newBlock(setNullToProperty(object, property))));
                break;
            default:
                throw new AssertionError(kind);
            }
            return f.newIfStatement(
                    new ExpressionBuilder(f, position)
                        .apply(InfixOperator.NOT_EQUALS, Models.toLiteral(f, 0))
                        .toExpression(),
                    f.newBlock(statements));
        }

        private Statement createResultSetNullMapping(
                Expression object,
                Expression resultSet,
                PropertyDeclaration property) {
            return f.newIfStatement(
                    new ExpressionBuilder(f, resultSet)
                        .method("wasNull")
                        .toExpression(),
                    f.newBlock(setNullToProperty(object, property)));
        }

        private ExpressionStatement setNullToProperty(
                Expression object,
                PropertyDeclaration property) {
            assert object != null;
            assert property != null;
            return new ExpressionBuilder(f, object)
                .method(context.getOptionSetterName(property), Models.toNullLiteral(f))
                .toStatement();
        }

        private Statement createPreparedStatementSupportStatement(
                Expression object,
                Expression statement,
                Expression position,
                PropertyDeclaration property) {
            assert object != null;
            assert statement != null;
            assert position != null;
            assert property != null;
            return f.newIfStatement(
                    new ExpressionBuilder(f, position)
                        .apply(InfixOperator.NOT_EQUALS, Models.toLiteral(f, 0))
                        .toExpression(),
                    f.newBlock(f.newIfStatement(
                            new ExpressionBuilder(f, object)
                                .method(context.getOptionGetterName(property))
                                .method("isNull")
                                .toExpression(),
                            f.newBlock(new ExpressionBuilder(f, statement)
                                .method("setNull", position, createNullType(property))
                                .toStatement()),
                            f.newBlock(createParameterSetter(object, statement, position, property)))));
        }

        private List<Statement> createParameterSetter(
                Expression object,
                Expression statement,
                Expression position,
                PropertyDeclaration property) {
            assert object != null;
            assert statement != null;
            assert position != null;
            assert property != null;
            List<Statement> statements = Lists.create();
            SimpleName date = f.newSimpleName(NAME_DATE);
            SimpleName calendar = f.newSimpleName(NAME_CALENDAR);
            SimpleName datetime = f.newSimpleName(NAME_DATETIME);
            BasicTypeKind kind = toBasicKind(property.getType());
            switch (kind) {
            case INT:
                statements.add(createParameterMapping(object, statement, position, property, "setInt"));
                break;
            case LONG:
                statements.add(createParameterMapping(object, statement, position, property, "setLong"));
                break;
            case FLOAT:
                statements.add(createParameterMapping(object, statement, position, property, "setFloat"));
                break;
            case DOUBLE:
                statements.add(createParameterMapping(object, statement, position, property, "setDouble"));
                break;
            case BYTE:
                statements.add(createParameterMapping(object, statement, position, property, "setByte"));
                break;
            case SHORT:
                statements.add(createParameterMapping(object, statement, position, property, "setShort"));
                break;
            case BOOLEAN:
                statements.add(createParameterMapping(object, statement, position, property, "setBoolean"));
                break;
            case DECIMAL:
                statements.add(createParameterMapping(object, statement, position, property, "setBigDecimal"));
                break;
            case TEXT:
                statements.add(new ExpressionBuilder(f, statement)
                    .method("setString", position, new ExpressionBuilder(f, object)
                        .method(context.getValueGetterName(property))
                        .method("toString")
                        .toExpression())
                    .toStatement());
                break;
            case DATE:
                statements.add(new TypeBuilder(f, context.resolve(DateUtil.class))
                    .method("setDayToCalendar",
                            new ExpressionBuilder(f, object)
                                .method(context.getValueGetterName(property))
                                .method("getElapsedDays")
                                .toExpression(),
                            calendar)
                    .toStatement());
                statements.add(new ExpressionBuilder(f, date)
                    .method("setTime", new ExpressionBuilder(f, calendar)
                        .method("getTimeInMillis")
                        .toExpression())
                    .toStatement());
                statements.add(new ExpressionBuilder(f, statement)
                    .method("setDate", position, date)
                    .toStatement());
                break;
            case DATETIME:
                statements.add(new TypeBuilder(f, context.resolve(DateUtil.class))
                    .method("setSecondToCalendar",
                            new ExpressionBuilder(f, object)
                                .method(context.getValueGetterName(property))
                                .method("getElapsedSeconds")
                                .toExpression(),
                            calendar)
                    .toStatement());
                statements.add(new ExpressionBuilder(f, datetime)
                    .method("setTime", new ExpressionBuilder(f, calendar)
                        .method("getTimeInMillis")
                        .toExpression())
                    .toStatement());
                statements.add(new ExpressionBuilder(f, statement)
                    .method("setTimestamp", position, datetime)
                    .toStatement());
                break;
            default:
                throw new AssertionError(kind);
            }
            return statements;
        }

        private Expression createNullType(PropertyDeclaration property) {
            assert property != null;
            return new TypeBuilder(f, context.resolve(Types.class))
                .field(getJdbcTypeName(property))
                .toExpression();
        }

        private String getJdbcTypeName(PropertyDeclaration property) {
            assert property != null;
            BasicTypeKind kind = toBasicKind(property.getType());
            switch (kind) {
            case INT:
                return "INTEGER";
            case LONG:
                return "BIGINT";
            case FLOAT:
                return "FLOAT";
            case DOUBLE:
                return "DOUBLE";
            case BYTE:
                return "TINYINT";
            case SHORT:
                return "SMALLINT";
            case BOOLEAN:
                return "BOOLEAN";
            case DECIMAL:
                return "DECIMAL";
            case TEXT:
                return "VARCHAR";
            case DATE:
                return "DATE";
            case DATETIME:
                return "TIMESTAMP";
            default:
                throw new AssertionError(kind);
            }
        }

        private Statement createParameterMapping(
                Expression object,
                Expression statement,
                Expression position,
                PropertyDeclaration property,
                String name) {
            assert object != null;
            assert statement != null;
            assert position != null;
            assert property != null;
            assert name != null;
            return new ExpressionBuilder(f, statement)
                .method(name, position, new ExpressionBuilder(f, object)
                    .method(context.getValueGetterName(property))
                    .toExpression())
                .toStatement();
        }

        private ExpressionStatement createResultSetMapping(
                Expression object,
                Expression resultSet,
                Expression position,
                PropertyDeclaration property,
                String name) {
            assert object != null;
            assert resultSet != null;
            assert position != null;
            assert property != null;
            assert name != null;
            return new ExpressionBuilder(f, object)
                .method(context.getValueSetterName(property), new ExpressionBuilder(f, resultSet)
                    .method(name, position)
                    .toExpression())
                .toStatement();
        }

        private BasicTypeKind toBasicKind(Type type) {
            assert type instanceof BasicType;
            BasicType basicType = (BasicType) type;
            return basicType.getKind();
        }

        private ExpressionStatement mapField(SimpleName name) {
            return new ExpressionBuilder(f, f.newThis())
                .field(name)
                .assignFrom(name)
                .toStatement();
        }

        private FieldDeclaration createPrivateField(Class<?> type, SimpleName name, boolean newInstance) {
            Expression initializer;
            if (newInstance) {
                initializer = new TypeBuilder(f, context.resolve(type))
                    .newObject()
                    .toExpression();
            } else {
                initializer = null;
            }
            return f.newFieldDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .Private()
                        .Final()
                        .toAttributes(),
                    context.resolve(type),
                    name,
                    initializer);
        }

        private List<PropertyDeclaration> getProperties() {
            List<PropertyDeclaration> results = Lists.create();
            for (PropertyDeclaration property : model.getDeclaredProperties()) {
                if (property.getTrait(JdbcColumnTrait.class) != null) {
                    results.add(property);
                }
            }
            return results;
        }
    }

    private static final class DescriptionGenerator {

        // for reduce library dependencies
        private static final String IMPORTER_TYPE_NAME = "com.asakusafw.vocabulary.windgate.JdbcImporterDescription";

        // for reduce library dependencies
        private static final String EXPORTER_TYPE_NAME = "com.asakusafw.vocabulary.windgate.JdbcExporterDescription";

        private final EmitContext context;

        private final ModelDeclaration model;

        private final com.asakusafw.utils.java.model.syntax.Type supportClass;

        private final ModelFactory f;

        private final boolean importer;

        private final JdbcTableTrait tableTrait;

        private DescriptionGenerator(
                EmitContext context,
                ModelDeclaration model,
                Name supportClassName,
                boolean importer) {
            assert context != null;
            assert model != null;
            assert supportClassName != null;
            this.context = context;
            this.model = model;
            this.f = context.getModelFactory();
            this.importer = importer;
            this.tableTrait = model.getTrait(JdbcTableTrait.class);
            this.supportClass = context.resolve(supportClassName);

            assert tableTrait != null;
        }

        static void emitImporter(
                EmitContext context,
                ModelDeclaration model,
                Name supportClassName) throws IOException {
            assert context != null;
            assert model != null;
            assert supportClassName != null;
            DescriptionGenerator emitter = new DescriptionGenerator(context, model, supportClassName, true);
            emitter.emit();
        }

        static void emitExporter(
                EmitContext context,
                ModelDeclaration model,
                Name supportClassName) throws IOException {
            assert context != null;
            assert model != null;
            assert supportClassName != null;
            DescriptionGenerator emitter = new DescriptionGenerator(context, model, supportClassName, false);
            emitter.emit();
        }

        private void emit() throws IOException {
            ClassDeclaration decl = f.newClassDeclaration(
                    new JavadocBuilder(f)
                        .text("An abstract implementation of ")
                        .linkType(context.resolve(model.getSymbol()))
                        .text(" {0} description using WindGate JDBC",
                                importer ? "importer" : "exporter")
                        .text(".")
                        .toJavadoc(),
                    new AttributeBuilder(f)
                        .Public()
                        .Abstract()
                        .toAttributes(),
                    context.getTypeName(),
                    context.resolve(Models.toName(f, importer ? IMPORTER_TYPE_NAME : EXPORTER_TYPE_NAME)),
                    Collections.<com.asakusafw.utils.java.model.syntax.Type>emptyList(),
                    createMembers());
            context.emit(decl);
        }

        private List<TypeBodyDeclaration> createMembers() {
            List<TypeBodyDeclaration> results = Lists.create();
            results.add(createGetModelType());
            results.add(createGetJdbcSupport());
            results.add(createGetTableName());
            results.add(createGetColumnNames());
            return results;
        }

        private MethodDeclaration createGetModelType() {
            return createGetter(
                    new TypeBuilder(f, context.resolve(Class.class))
                        .parameterize(f.newWildcard(
                                WildcardBoundKind.UPPER_BOUNDED,
                                context.resolve(model.getSymbol())))
                        .toType(),
                    "getModelType",
                    f.newClassLiteral(context.resolve(model.getSymbol())));
        }

        private MethodDeclaration createGetJdbcSupport() {
            return createGetter(
                    new TypeBuilder(f, context.resolve(Class.class))
                        .parameterize(supportClass)
                        .toType(),
                    "getJdbcSupport",
                    f.newClassLiteral(supportClass));
        }

        private MethodDeclaration createGetTableName() {
            return createGetter(
                    context.resolve(String.class),
                    "getTableName",
                    Models.toLiteral(f, tableTrait.getName()));
        }

        private MethodDeclaration createGetColumnNames() {
            List<Expression> arguments = Lists.create();
            for (PropertyDeclaration property : model.getDeclaredProperties()) {
                JdbcColumnTrait columnTrait = property.getTrait(JdbcColumnTrait.class);
                if (columnTrait != null) {
                    arguments.add(Models.toLiteral(f, columnTrait.getName()));
                }
            }
            return createGetter(
                    new TypeBuilder(f, context.resolve(List.class))
                        .parameterize(context.resolve(String.class))
                        .toType(),
                    "getColumnNames",
                    new TypeBuilder(f, context.resolve(Arrays.class))
                        .method("asList", arguments)
                        .toExpression());
        }

        private MethodDeclaration createGetter(
                com.asakusafw.utils.java.model.syntax.Type type,
                String name,
                Expression value) {
            assert type != null;
            assert name != null;
            assert value != null;
            return f.newMethodDeclaration(
                    null,
                    new AttributeBuilder(f)
                        .annotation(context.resolve(Override.class))
                        .Public()
                        .toAttributes(),
                    type,
                    f.newSimpleName(name),
                    Collections.<FormalParameterDeclaration>emptyList(),
                    Arrays.asList(new ExpressionBuilder(f, value).toReturnStatement()));
        }
    }
}
TOP

Related Classes of com.asakusafw.dmdl.windgate.jdbc.driver.JdbcSupportEmitter$SupportGenerator

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.