aeon.compiler.generators.marshaller.PersisterMarshallerGeneratorImpl.java Source code

Java tutorial

Introduction

Here is the source code for aeon.compiler.generators.marshaller.PersisterMarshallerGeneratorImpl.java

Source

/**
 * Copyright 2015 Appvengers
 *
 * 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 aeon.compiler.generators.marshaller;

import aeon.compiler.context.Context;
import aeon.compiler.context.Field;
import aeon.compiler.context.SqliteField;
import aeon.compiler.generators.AbstractNestedClassGenerator;
import aeon.compiler.generators.CodeBlocks;
import aeon.compiler.generators.Names;
import aeon.compiler.generators.TypeMap;
import aeon.compiler.generators.idaccessor.PersisterIdAccessorGeneratorFactory;
import aeon.compiler.utils.Utils;
import aeon.internal.binder.Binders;
import aeon.internal.marshall.AbstractMarshaller;
import aeon.internal.marshall.Marshaller;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.squareup.javapoet.*;

import javax.lang.model.element.Modifier;
import java.util.Collections;

import static aeon.compiler.generators.ClassNames.Cursor;
import static aeon.compiler.generators.ClassNames.SQLiteStatement;

/**
 * Generator for the Persister.Marshaller
 *
 * @author Sven Jacobs
 */
class PersisterMarshallerGeneratorImpl extends AbstractNestedClassGenerator
        implements PersisterMarshallerGenerator {

    private static final String CLASS_NAME = "Marshaller";

    private static final String OBJ_PARAM = "obj";
    private static final String STMT_PARAM = "stmt";
    private static final String CURSOR_PARAM = "cursor";

    private final PersisterIdAccessorGeneratorFactory mIdAccessorGeneratorFactory;

    @Inject
    public PersisterMarshallerGeneratorImpl(final PersisterIdAccessorGeneratorFactory idAccessorGeneratorFactory,
            @Assisted final Context context) {

        super(context);

        mIdAccessorGeneratorFactory = idAccessorGeneratorFactory;
    }

    @Override
    public TypeSpec classSpec() {
        final ParameterizedTypeName superclass = ParameterizedTypeName.get(ClassName.get(AbstractMarshaller.class),
                getContext().getTargetClassName(), ClassName.get(Utils.getIdFieldType(getContext())));

        return TypeSpec.classBuilder(CLASS_NAME).superclass(superclass)
                .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL).addMethod(constructorMethod())
                .addMethod(unmarshallMethod()).addMethod(unmarshallWithInstanceMethod())
                .addMethod(bindInsertStatementMethod()).addMethod(bindUpdateStatementMethod())
                .addMethod(bindIdMethod()).addMethod(getInsertStatementMethod())
                .addMethod(getUpdateStatementMethod()).build();
    }

    @Override
    public MethodSpec createMarshallerMethod() {
        final ParameterizedTypeName returnType = ParameterizedTypeName.get(ClassName.get(Marshaller.class),
                getContext().getTargetClassName());

        return MethodSpec.methodBuilder("createMarshaller").addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED).returns(returnType).addStatement("return new $T()", className())
                .build();
    }

    @Override
    protected String getPeerClassName() {
        return CLASS_NAME;
    }

    private MethodSpec constructorMethod() {
        return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE)
                .addStatement("super(new $T())", mIdAccessorGeneratorFactory.create(getContext()).className())
                .build();
    }

    private MethodSpec unmarshallMethod() {
        final ClassName targetClass = getContext().getTargetClassName();

        return MethodSpec.methodBuilder("unmarshall").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)
                .addParameter(Cursor(), CURSOR_PARAM, Modifier.FINAL).returns(targetClass)
                .addStatement("return unmarshall(new $T(), $L)", targetClass, CURSOR_PARAM).build();
    }

    private MethodSpec unmarshallWithInstanceMethod() {
        // TODO: Use Cursor#getColumnIndex()? But Persister is generated during every compile anyway...

        final ClassName targetClass = getContext().getTargetClassName();

        final CodeBlock.Builder builder = CodeBlock.builder().add(CodeBlocks.checkNotNull(OBJ_PARAM))
                .add(CodeBlocks.checkNotNull(CURSOR_PARAM));

        int columnIndex = 0;
        for (final Field field : getContext().getFieldContext().getFields()) {
            final String bindersMethod = TypeMap.binderNameOf(field.getType());

            builder.addStatement("$L.$L = $T.$L().unbind($L, $L, $L)", OBJ_PARAM, field.getName(), Binders.class,
                    bindersMethod, CURSOR_PARAM, columnIndex, field.isNullable());

            columnIndex++;
        }

        builder.addStatement("return $L", OBJ_PARAM);

        return MethodSpec.methodBuilder("unmarshall").addAnnotation(Override.class).addModifiers(Modifier.PUBLIC)
                .addParameter(targetClass, OBJ_PARAM, Modifier.FINAL)
                .addParameter(Cursor(), CURSOR_PARAM, Modifier.FINAL).returns(targetClass).addCode(builder.build())
                .build();
    }

    private MethodSpec bindInsertStatementMethod() {
        final CodeBlock.Builder builder = buildBindings(CodeBlock.builder(),
                getContext().getFieldContext().getFields(), 1, true).addStatement("return $L", STMT_PARAM);

        return MethodSpec.methodBuilder("bindInsertStatement").addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED).returns(SQLiteStatement())
                .addParameter(SQLiteStatement(), STMT_PARAM, Modifier.FINAL)
                .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build())
                .build();
    }

    private MethodSpec bindUpdateStatementMethod() {
        final FluentIterable<Field> fieldsWithoutId = getContext().getFieldContext().getFieldsWithoutId();
        final CodeBlock.Builder builder = CodeBlock.builder();

        if (fieldsWithoutId.size() == 0) {
            builder.addStatement("throw new $T($S)", UnsupportedOperationException.class,
                    String.format("Class %s cannot be updated. Only has one field (the ID field).",
                            getContext().getTargetClassName().simpleName()));
        } else {
            final Field idField = getContext().getFieldContext().getIdField();
            if (idField.getType().isReferenceType()) {
                builder.beginControlFlow("if ($L.$L == null)", OBJ_PARAM, idField.getName())
                        .addStatement("throw new $T($S)", NullPointerException.class, "ID field cannot be null")
                        .endControlFlow();
            }

            buildBindings(builder, fieldsWithoutId, 1, false);
            buildBindings(builder, Lists.newArrayList(idField), fieldsWithoutId.size() + 1, false);

            builder.addStatement("return $L", STMT_PARAM);
        }

        return MethodSpec.methodBuilder("bindUpdateStatement").addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED).returns(SQLiteStatement())
                .addParameter(SQLiteStatement(), STMT_PARAM, Modifier.FINAL)
                .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build())
                .build();
    }

    private MethodSpec bindIdMethod() {
        final String ID_PARAM = "id";

        final Field idField = getContext().getFieldContext().getIdField();
        final Field.Type type = idField.getType();
        final CodeBlock.Builder builder = CodeBlock.builder();

        if (type == Field.Type.STRING) {
            builder.add("// Do nothing\n");
        } else {
            idFieldDefaultValueControlFlow(builder, idField, OBJ_PARAM)
                    .addStatement("getIdAccessor().setId($L, $L)", ID_PARAM, OBJ_PARAM).endControlFlow();
        }

        return MethodSpec.methodBuilder("bindId").addAnnotation(Override.class).addModifiers(Modifier.PROTECTED)
                .addParameter(long.class, ID_PARAM, Modifier.FINAL)
                .addParameter(getContext().getTargetClassName(), OBJ_PARAM, Modifier.FINAL).addCode(builder.build())
                .build();
    }

    private MethodSpec getInsertStatementMethod() {
        final FluentIterable<SqliteField> fields = getSqliteContext().getFieldContext().getFields();
        final String columns = Joiner.on(", ").join(Names.escapedNames(fields));
        final String parameters = Joiner.on(", ").join(Collections.nCopies(fields.size(), "?"));

        final String stmt = String.format("INSERT INTO %s (%s) VALUES (%s)",
                getSqliteContext().getTableName().asEscapedName(), columns, parameters);

        return MethodSpec.methodBuilder("getInsertStatement").addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED).returns(String.class).addStatement("return $S", stmt).build();
    }

    private MethodSpec getUpdateStatementMethod() {
        final FluentIterable<SqliteField> fieldsWithoutId = getSqliteContext().getFieldContext()
                .getFieldsWithoutId();
        final String stmt;

        if (fieldsWithoutId.size() == 0) {
            stmt = "";
        } else {
            final FluentIterable<String> assignments = fieldsWithoutId
                    .transform(new Function<SqliteField, String>() {
                        @Override
                        public String apply(final SqliteField input) {
                            return input.getName().asEscapedName() + " = ?";
                        }
                    });

            stmt = String.format("UPDATE %s SET %s WHERE %s = ?", getSqliteContext().getTableName().asEscapedName(),
                    Joiner.on(", ").join(assignments),
                    getSqliteContext().getFieldContext().getIdField().getName().asEscapedName());
        }

        return MethodSpec.methodBuilder("getUpdateStatement").addAnnotation(Override.class)
                .addModifiers(Modifier.PROTECTED).returns(String.class).addStatement("return $S", stmt).build();
    }

    private static CodeBlock.Builder buildBindings(final CodeBlock.Builder builder, final Iterable<Field> fields,
            final int startIndex, final boolean permitIdWithDefault) {

        int index = startIndex;
        for (final Field field : fields) {
            final String bindersMethod = TypeMap.binderNameOf(field.getType());

            if (field.isId() && permitIdWithDefault) {
                idFieldDefaultValueControlFlow(builder, field, OBJ_PARAM)
                        .addStatement("$T.nullBinder().bind(null, $L, $L, true)", Binders.class, STMT_PARAM, index)
                        .nextControlFlow("else");
            }

            builder.addStatement("$T.$L().bind($L.$L, $L, $L, $L)", Binders.class, bindersMethod, OBJ_PARAM,
                    field.getName(), STMT_PARAM, index, field.isNullable());

            if (field.isId() && permitIdWithDefault) {
                builder.endControlFlow();
            }

            index++;
        }

        return builder;
    }

    private static CodeBlock.Builder idFieldDefaultValueControlFlow(final CodeBlock.Builder builder,
            final Field idField, final String objVarName) {

        final Field.Type type = idField.getType();
        final String name = idField.getName();

        if (type == Field.Type.STRING || !type.isReferenceType()) {
            final String defaultValue = type == Field.Type.STRING ? "null" : "0";

            builder.beginControlFlow("if ($L.$L == $L)", objVarName, name, defaultValue);
        } else {
            builder.beginControlFlow("if ($L.$L == null || $L.$L == 0)", objVarName, name, objVarName, name);
        }

        return builder;
    }
}