Java tutorial
/** * 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); } }