aeon.compiler.generators.querybuilder.PersisterQueryBuilderGeneratorImpl.java Source code

Java tutorial

Introduction

Here is the source code for aeon.compiler.generators.querybuilder.PersisterQueryBuilderGeneratorImpl.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.querybuilder;

import aeon.compiler.context.Context;
import aeon.compiler.context.Field;
import aeon.compiler.context.SqliteField;
import aeon.compiler.generators.AbstractNestedClassGenerator;
import aeon.compiler.utils.Utils;
import aeon.query.*;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.FluentIterable;
import com.google.common.escape.Escaper;
import com.google.common.escape.Escapers;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;

import javax.lang.model.element.Modifier;

/**
 * Generator for the Persister.Builder
 *
 * @author Sven Jacobs
 */
class PersisterQueryBuilderGeneratorImpl extends AbstractNestedClassGenerator
        implements PersisterQueryBuilderGenerator {

    private static final String CLASS_NAME = "QueryBuilder";
    private static final String ORDER_BY_CLASS_NAME = "OrderBy";

    private static final String LIMIT_PARAM = "limit";

    @Inject
    public PersisterQueryBuilderGeneratorImpl(@Assisted final Context context) {
        super(context);
    }

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

        final TypeSpec.Builder builder = TypeSpec.classBuilder(CLASS_NAME).superclass(superclass)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addType(orderByClass()).addMethod(constructor())
                .addMethod(andMethod()).addMethod(orMethod()).addMethod(beginGroupMethod())
                .addMethod(endGroupMethod()).addMethod(orderByMethod()).addMethod(limitMethod())
                .addMethod(limitOffsetMethod());

        addBuilderMethods(builder);

        return builder.build();
    }

    @Override
    public MethodSpec queryMethod() {
        return MethodSpec.methodBuilder("query").addModifiers(Modifier.PUBLIC).returns(className())
                .addStatement("return new $T()", className()).build();
    }

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

    private TypeSpec orderByClass() {
        final MethodSpec constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE).build();

        final TypeSpec.Builder classBuilder = TypeSpec.classBuilder(ORDER_BY_CLASS_NAME)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(constructor);

        final ParameterizedTypeName returnType = ParameterizedTypeName.get(ClassName.get(OrderByBuilder.class),
                getContext().getTargetClassName(), ClassName.get(Utils.getIdFieldType(getContext())), className());

        for (final Field field : getContext().getFieldContext().getFields()) {
            final SqliteField sqliteField = SqliteField.of(field);
            final String methodName = Utils.normalizeFieldName(field.getName());

            final MethodSpec method = MethodSpec.methodBuilder(methodName).addModifiers(Modifier.PUBLIC)
                    .returns(returnType).addStatement("return new $T($L.this, $S)", returnType, CLASS_NAME,
                            sqliteField.getName().asEscapedName())
                    .build();

            classBuilder.addMethod(method);
        }

        return classBuilder.build();
    }

    private MethodSpec constructor() {
        return MethodSpec.constructorBuilder().addModifiers(Modifier.PRIVATE)
                .addStatement("super($T.this, getMarshaller(), getDatabase(), $S, $L)",
                        getContext().getPersisterClassName(), getSqliteContext().getTableName().asEscapedName(),
                        "new String[] {" + getFieldsAsStrings() + "}")
                .build();
    }

    private MethodSpec andMethod() {
        return unitMethod("and", "addAnd",
                "Combines following condition with AND.<br/>\n"
                        + "AND concatenation is implicit and must not be explicitly called unless for clarity.\n"
                        + "\n" + "@return Same QueryBuilder instance\n");
    }

    private MethodSpec orMethod() {
        return unitMethod("or", "addOr",
                "Combines following condition with OR.\n" + "\n" + "@return Same QueryBuilder instance\n");
    }

    private MethodSpec beginGroupMethod() {
        return unitMethod("beginGroup", "addBeginGroup",
                "Begins a group (open parentheses).\n" + "\n" + "@return Same QueryBuilder instance\n");
    }

    private MethodSpec endGroupMethod() {
        return unitMethod("endGroup", "addEndGroup",
                "Ends a group (close parentheses).\n" + "\n" + "@return Same QueryBuilder instance\n");
    }

    private MethodSpec limitMethod() {
        return MethodSpec.methodBuilder("limit").addModifiers(Modifier.PUBLIC)
                .addJavadoc("Sets LIMIT for this query.\n\n" + "@param $L Value for LIMIT\n"
                        + "@return Same QueryBuilder instance", LIMIT_PARAM)
                .returns(className()).addParameter(int.class, LIMIT_PARAM, Modifier.FINAL)
                .addStatement("setLimit($L)", LIMIT_PARAM).addStatement("return this").build();
    }

    private MethodSpec limitOffsetMethod() {
        final String OFFSET_PARAM = "offset";

        return MethodSpec.methodBuilder("limit").addModifiers(Modifier.PUBLIC)
                .addJavadoc(
                        "Sets LIMIT and OFFSET for this query.\n\n" + "@param $L Value for LIMIT\n"
                                + "@param $L Value for OFFSET\n" + "@return Same QueryBuilder instance",
                        LIMIT_PARAM, OFFSET_PARAM)
                .returns(className()).addParameter(int.class, LIMIT_PARAM, Modifier.FINAL)
                .addParameter(int.class, OFFSET_PARAM, Modifier.FINAL)
                .addStatement("setLimit($L, $L)", LIMIT_PARAM, OFFSET_PARAM).addStatement("return this").build();
    }

    private MethodSpec orderByMethod() {
        final ClassName returnType = className().peerClass(ORDER_BY_CLASS_NAME);

        return MethodSpec.methodBuilder("orderBy").addModifiers(Modifier.PUBLIC).returns(returnType)
                .addStatement("return new $T()", returnType).build();
    }

    private MethodSpec unitMethod(final String methodName, final String unitMethod, final String javaDoc) {

        return MethodSpec.methodBuilder(methodName).addModifiers(Modifier.PUBLIC).returns(className())
                .addStatement("$L()", unitMethod).addStatement("return this").addJavadoc(javaDoc).build();
    }

    private void addBuilderMethods(final TypeSpec.Builder typeSpecBuilder) {
        for (final Field field : getContext().getFieldContext().getFields()) {
            final Class<?> conditionBuilderClass = getConditionBuilderForType(field.getType());
            final ParameterizedTypeName returnType = ParameterizedTypeName.get(ClassName.get(conditionBuilderClass),
                    getContext().getTargetClassName(), ClassName.get(Utils.getIdFieldType(getContext())),
                    className());

            final MethodSpec method = MethodSpec.methodBuilder(Utils.normalizeFieldName(field.getName()))
                    .addModifiers(Modifier.PUBLIC)
                    .addJavadoc("Defines a condition for the field $S.\n\n" + "@return Condition for $S\n",
                            field.getName(), field.getName())
                    .returns(returnType).addStatement("return new $T<>(this, $S, $L)", conditionBuilderClass,
                            SqliteField.of(field).getName().asEscapedName(), field.isNullable())
                    .build();

            typeSpecBuilder.addMethod(method);
        }
    }

    private Class<?> getConditionBuilderForType(final Field.Type type) {
        switch (type) {
        case BOOLEAN:
        case BOOLEAN_REF:
            return BooleanConditionBuilder.class;
        case BYTE:
        case BYTE_REF:
            return ByteConditionBuilder.class;
        case CHAR:
        case CHAR_REF:
            return CharacterConditionBuilder.class;
        case DATE:
            return DateConditionBuilder.class;
        case DOUBLE:
        case DOUBLE_REF:
            return DoubleConditionBuilder.class;
        case FLOAT:
        case FLOAT_REF:
            return FloatConditionBuilder.class;
        case INT:
        case INT_REF:
            return IntegerConditionBuilder.class;
        case LONG:
        case LONG_REF:
            return LongConditionBuilder.class;
        case SHORT:
        case SHORT_REF:
            return ShortConditionBuilder.class;
        case STRING:
            return StringConditionBuilder.class;
        default:
            throw new IllegalArgumentException("Unknown type");
        }
    }

    private String getFieldsAsStrings() {
        final Escaper escaper = Escapers.builder().addEscape('"', "\\\"").build();

        final FluentIterable<String> asStrings = getSqliteContext().getFieldContext().getFields()
                .transform(new Function<SqliteField, String>() {
                    @Override
                    public String apply(final SqliteField input) {
                        return "\"" + escaper.escape(input.getName().asEscapedName()) + "\"";
                    }
                });

        return Joiner.on(", ").join(asStrings);
    }
}