/** * Copyright 2015 Santhosh Kumar Tekuri * * The JLibs authors license this file to you 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 jlibs.jdbc.annotations.processor; import jlibs.core.annotation.processing.AnnotationError; import jlibs.core.annotation.processing.Printer; import jlibs.core.lang.ImpossibleException; import jlibs.core.lang.Noun; import jlibs.core.lang.StringUtil; import jlibs.core.lang.model.ModelUtil; import jlibs.jdbc.DAO; import jlibs.jdbc.JavaType; import jlibs.jdbc.SQLType; import jlibs.jdbc.annotations.Column; import jlibs.jdbc.annotations.Table; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.ElementFilter; import java.io.IOException; import java.sql.Connection; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import static jlibs.core.annotation.processing.Printer.MINUS; import static jlibs.core.annotation.processing.Printer.PLUS; /** * @author Santhosh Kumar T */ class Columns extends ArrayList<ColumnProperty>{ public static Map<TypeElement, Columns> ALL = new HashMap<TypeElement, Columns>(); public final String tableName; public final TypeElement tableClass; public Columns(TypeElement clazz){ ALL.put(clazz, this); tableClass = clazz; String tableName = ModelUtil.getAnnotationValue(clazz, Table.class, "name"); if(tableName.length()==0) tableName = Noun.pluralize(StringUtil.underscore(clazz.getSimpleName().toString())); this.tableName = tableName; } public String tableName(boolean quoted){ if(quoted) return String.format("jdbc.quote(\"%s\")", StringUtil.toLiteral(tableName, true)); else return StringUtil.toLiteral(tableName, true); } public ColumnProperty findByProperty(String propertyName){ for(ColumnProperty prop: this){ if(prop.propertyName().equals(propertyName)) return prop; } return null; } public ColumnProperty findByColumn(String columnName){ for(ColumnProperty prop: this){ if(prop.columnName().equals(columnName)) return prop; } return null; } public String columnName(String propertyName){ ColumnProperty column = findByProperty(propertyName); return column!=null ? column.columnName() : null; } public String columnName(String propertyName, boolean quoted){ ColumnProperty column = findByProperty(propertyName); return column!=null ? column.columnName(quoted) : null; } public String propertyName(String columnName){ ColumnProperty column = findByColumn(columnName); return column!=null ? column.propertyName() : null; } public int autoColumn = -1; @Override public boolean add(ColumnProperty columnProperty){ ColumnProperty clash = findByProperty(columnProperty.propertyName()); if(clash!=null) throw new AnnotationError(columnProperty.element, columnProperty.annotation, "this property is already used by: "+clash.element); clash = findByColumn(columnProperty.columnName()); if(clash!=null) throw new AnnotationError(columnProperty.element, columnProperty.annotation, "this column is already used by: "+clash.element); if(columnProperty.auto()){ if(autoColumn!=-1) throw new AnnotationError(columnProperty.element, columnProperty.annotation, "two auto columns found: "+get(autoColumn).propertyName()+", "+columnProperty.propertyName()); autoColumn = size(); } columnProperty.validateType(); columnProperty.reference = Reference.find(columnProperty.element); return super.add(columnProperty); } public void process(TypeElement c){ for(ExecutableElement method: ElementFilter.methodsIn(c.getEnclosedElements())){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(method, Column.class); if(mirror!=null){ if(!ModelUtil.isAccessible(method, true, false)) throw new AnnotationError(method, "Invalid access modifier used on method with @Column"); add(new MethodColumnProperty(method, mirror)); } } for(VariableElement element: ElementFilter.fieldsIn(c.getEnclosedElements())){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(element, Column.class); if(mirror!=null){ if(!ModelUtil.isAccessible(element, true, false)) throw new AnnotationError(element, "Invalid access modifier used on field with @Column"); add(new FieldColumnProperty(element, mirror)); } } } public void generateConstructor(Printer printer){ printer.printlns( "public "+printer.generatedClazz+"(JDBC jdbc){", PLUS, "super(jdbc, new TableMetaData(jdbc.quote(\""+StringUtil.toLiteral(tableName, false)+"\"),", PLUS ); int i = 0; for(ColumnProperty column: this){ printer.println( "new ColumnMetaData(" , "\""+StringUtil.toLiteral(column.propertyName(), false)+"\", ", "jdbc.quote(\""+StringUtil.toLiteral(column.columnName(), false)+"\"), ", JavaType.class.getSimpleName()+'.'+column.javaType().name()+", ", SQLType.class.getSimpleName()+'.'+column.sqlType().name()+", ", column.primary()+", ", column.auto()+")", (i==size()-1 ? "" : ",") ); i++; } printer.printlns( MINUS, "));", MINUS, "}" ); } private void addDefaultCase(Printer printer){ printer.printlns( "default:", PLUS, "throw new ImpossibleException();", MINUS, MINUS, "}", MINUS, "}" ); } public void generateGetColumnValue(Printer printer){ printer.printlns( "@Override", "public Object getColumnValue(int i, "+printer.clazz.getSimpleName()+" record){", PLUS, "Object value;", "switch(i){", PLUS ); int i = 0; for(ColumnProperty column: this){ String propertyCode = column.toNativeTypeCode(column.getPropertyCode("record")); printer.printlns( "case "+i+":", PLUS, "value = "+propertyCode+';', "return value==null ? "+SQLType.class.getSimpleName()+'.'+column.sqlType().name()+" : value;", MINUS ); i++; } addDefaultCase(printer); } public void generateSetColumnValue(Printer printer){ printer.printlns( "@Override", "public void setColumnValue(int i, "+printer.clazz.getSimpleName()+" record, Object value){", PLUS, "switch(i){", PLUS ); int i = 0; for(ColumnProperty column: this){ String value = "value"; if(column.typeMapper()!=null) value = '('+ModelUtil.toString(column.javaTypeMirror(), true)+')'+value; value = column.toUserTypeCode(value); printer.printlns( "case "+i+":", PLUS, column.setPropertyCode("record", value)+';', "break;", MINUS ); i++; } addDefaultCase(printer); } public void generateTypeMapperConstants(Printer printer){ for(ColumnProperty column: this){ TypeMirror typeMapper = column.typeMapper(); if(typeMapper!=null){ String typeMapperQName = ModelUtil.toString(typeMapper, false); String constant = StringUtil.underscore(column.propertyName()).toUpperCase(); printer.println("public static final "+typeMapperQName+" TYPE_MAPPER_"+constant+" = new "+typeMapperQName+"();"); } } } private void generateNewRow(Printer printer){ printer.printlns( "@Override", "public "+printer.clazz.getSimpleName()+" newRow(){", PLUS, "return new "+printer.clazz.getSimpleName()+"();", MINUS, "}" ); } private void generateNewRecord(Printer printer){ printer.printlns( "@Override", "public "+printer.clazz.getSimpleName()+" newRecord(ResultSet rs) throws SQLException{", PLUS, printer.clazz.getSimpleName()+" __record = newRow();" ); int i = 1; for(ColumnProperty column: this){ String code[] = column.getValueFromResultSet(i); if(code.length>1) printer.println(code[0]); String value = code[code.length-1]; printer.println("setColumnValue("+(i-1)+", __record, "+value+");"); i++; } printer.printlns( "return __record;", MINUS, "}" ); } private void generateGetAutoColumnValue(Printer printer){ printer.printlns( "@Override", "public Object getAutoColumnValue(ResultSet rs) throws SQLException{", PLUS ); if(autoColumn==-1) printer.println("throw new "+ ImpossibleException.class.getName()+"();"); else{ ColumnProperty column = get(autoColumn); String code[] = column.getValueFromResultSet(1); if(code.length>1) printer.println(code[0]); printer.println("return "+code[code.length-1]+';'); } printer.printlns( MINUS, "}" ); } private void generateDAO(Printer printer){ TypeElement extendClass = (TypeElement)((DeclaredType)ModelUtil.getAnnotationValue(printer.clazz, Table.class, "extend")).asElement(); printer.printPackage(); printer.importClass(ImpossibleException.class); printer.importPackage(DAO.class); printer.importPackage(Connection.class); printer.importClass(SQLType.class); if(ModelUtil.isInnerClass(printer.clazz)) printer.importClass(printer.clazz); printer.println(); printer.printClassDoc(); printer.print("public class "+printer.generatedClazz +" extends "+extendClass.getQualifiedName()); if(extendClass.getQualifiedName().contentEquals(DAO.class.getName())) printer.println("<"+printer.clazz.getSimpleName()+'>'); printer.println("{"); printer.indent++; generateTypeMapperConstants(printer); printer.println(); generateConstructor(printer); printer.println(); generateNewRow(printer); printer.println(); generateNewRecord(printer); printer.println(); generateGetAutoColumnValue(printer); printer.println(); generateGetColumnValue(printer); printer.println(); generateSetColumnValue(printer); for(ExecutableElement method: ElementFilter.methodsIn(extendClass.getEnclosedElements())){ DMLMethod dmlMethod = DMLMethod.create(printer, method, this); if(dmlMethod!=null) dmlMethod.generate(); } printer.indent--; printer.println("}"); } public void generateDAO(){ Printer pw = null; try{ pw = Printer.get(tableClass, Table.class, TableAnnotationProcessor.FORMAT); generateDAO(pw); }catch(IOException ex){ throw new RuntimeException(ex); }finally{ if(pw!=null) pw.close(); } } }