/** * 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.ArrayUtil; import jlibs.core.lang.model.ModelUtil; import jlibs.core.util.regex.TemplateMatcher; import jlibs.jdbc.JavaType; import jlibs.jdbc.annotations.*; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeKind; import java.util.*; import static jlibs.core.annotation.processing.Printer.MINUS; import static jlibs.core.annotation.processing.Printer.PLUS; /** * @author Santhosh Kumar T */ // @enhancement allow to ignore nulls in where condition to make dynamic queries abstract class DMLMethod{ protected static final Map<String, String> HINTS = new HashMap<String, String>(); static{ HINTS.put("where", "=?"); HINTS.put("eq", "=?"); HINTS.put("ne", "<>?"); HINTS.put("lt", "<?"); HINTS.put("le", "<=?"); HINTS.put("gt", ">?"); HINTS.put("ge", ">=?"); HINTS.put("like", "LIKE ?"); HINTS.put("nlike", "NOT LIKE ?"); } protected Printer printer; protected ExecutableElement method; protected AnnotationMirror mirror; protected Columns columns; protected DMLMethod(Printer printer, ExecutableElement method, AnnotationMirror mirror, Columns columns){ this.printer = printer; this.method = method; this.mirror = mirror; this.columns = columns; } public static DMLMethod create(Printer printer, ExecutableElement method, Columns columns){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(method, Select.class); if(mirror!=null){ String columnProp = ModelUtil.getAnnotationValue(method, mirror, "column"); String expressionProp = ModelUtil.getAnnotationValue(method, mirror, "expression"); if(columnProp.length()>0 || expressionProp.length()>0) return new SelectColumnMethod(printer, method, mirror, columns); else return new SelectMethod(printer, method, mirror, columns); } mirror = ModelUtil.getAnnotationMirror(method, Insert.class); if(mirror!=null) return new InsertMethod(printer, method, mirror, columns); mirror = ModelUtil.getAnnotationMirror(method, Update.class); if(mirror!=null) return new UpdateMethod(printer, method, mirror, columns); mirror = ModelUtil.getAnnotationMirror(method, Upsert.class); if(mirror!=null) return new UpsertMethod(printer, method, mirror, columns); mirror = ModelUtil.getAnnotationMirror(method, Delete.class); if(mirror!=null) return new DeleteMethod(printer, method, mirror, columns); return null; } protected String methodName(){ return mirror.getAnnotationType().asElement().getSimpleName().toString().toLowerCase(Locale.US); } protected String userSQL(){ try{ return ModelUtil.getAnnotationValue(method, mirror, "sql"); }catch(AnnotationError error){ // doesn't support user sql return ""; } } private CharSequence annotationAsString(){ StringBuilder buff = new StringBuilder("@"); buff.append(mirror.getAnnotationType().asElement().getSimpleName()); if(mirror.getElementValues().size()>0) buff.append('('); boolean first = true; for(Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : mirror.getElementValues().entrySet()){ if(first) first = false; else buff.append(", "); buff.append(entry.getKey().getSimpleName()).append('=').append(entry.getValue()); } if(mirror.getElementValues().size()>0) buff.append(')'); return buff.toString(); } public void generate(){ printer.printlns( "", "@Override //"+annotationAsString(), ModelUtil.signature(method, true)+"{", PLUS ); printer.printlns(code()); printer.printlns( MINUS, "}" ); } protected String[] code(){ List<String> code = new ArrayList<String>(); CharSequence[] sql = sql(); if(sql.length>2){ for(int i=0; i<sql.length-2; i++) code.add(sql[i].toString()); sql = new CharSequence[]{ sql[sql.length-2], sql[sql.length-1] }; } String queryMethod = queryMethod(sql)+';'; if(method.getReturnType().getKind()!= TypeKind.VOID) queryMethod = "return "+queryMethod; code.add(queryMethod); return code.toArray(new String[code.size()]); } protected String queryMethod(CharSequence sequences[]){ return queryMethod(methodName(), sequences); } protected static String queryMethod(String methodName, CharSequence... sequences){ CharSequence query = sequences[0]; CharSequence params = sequences[1]; if(!query.toString().contains("__query")) query = '"'+ query.toString()+'"'; if(methodName.indexOf('(')==-1) methodName = methodName+'('; String code = methodName+query; if(params.length()>0) code += ", "+params; return code += ')'; } protected void validateParamCount(){ if(method.getParameters().size()==0) throw new AnnotationError(method, "method with "+mirror.getAnnotationType().asElement().getSimpleName()+" annotation should take atleast one argument"); } protected final CharSequence[] sql(){ String userSQL = userSQL(); if(userSQL.length()==0){ validateParamCount(); return defaultSQL(); }else return preparedSQL(userSQL); } protected abstract CharSequence[] defaultSQL(); protected String replacePropertiesWithColumns(String sql){ TemplateMatcher matcher = new TemplateMatcher("#{", "}"); return matcher.replace(sql, new TemplateMatcher.VariableResolver(){ @Override public String resolve(String propertyName){ String columnName = columns.columnName(propertyName, true); if(columnName==null) throw new AnnotationError(method, mirror, "unknown property: "+propertyName); return "\"+"+columnName+"+\""; } }); } protected CharSequence[] preparedSQL(String value){ CharSequence query; final StringBuilder params = new StringBuilder(); value = replacePropertiesWithColumns(value); TemplateMatcher matcher = new TemplateMatcher("${", "}"); query = matcher.replace(value, new TemplateMatcher.VariableResolver(){ @Override public String resolve(String paramName){ String propertyName = null; if(paramName.startsWith("(")){ int close = paramName.indexOf(')'); if(close==-1) throw new AnnotationError(method, mirror, "brace ) is missing in sql"); propertyName = paramName.substring(1, close); paramName = paramName.substring(close+1); } VariableElement param = ModelUtil.getParameter(method, paramName); if(param==null) throw new AnnotationError(method, mirror, "unknown parameter: "+paramName); if(params.length()>0) params.append(", "); if(propertyName!=null) paramName = getColumn(param, propertyName).toNativeTypeCode(paramName); else{ String type = ModelUtil.toString(param.asType(), true); if(ModelUtil.isPrimitive(param.asType()) || ModelUtil.isPrimitiveWrapper(param.asType())) type = ModelUtil.getPrimitive(type); boolean found = false; for(JavaType javaType: JavaType.values()){ if(javaType.clazz.getName().equals(type)){ found = true; break; } } if(!found) throw new AnnotationError(method, mirror, "the column property must be specified for parameter "+param.getSimpleName()+" in query."); } params.append(paramName); return "?"; } }); return new CharSequence[]{ query, params }; } /*-------------------------------------------------[ Helpers ]---------------------------------------------------*/ protected ColumnProperty getColumn(VariableElement param){ return getColumn(param, param.getSimpleName().toString()); } protected ColumnProperty getColumn(VariableElement param, String propertyName){ ColumnProperty column = columns.findByProperty(propertyName); if(column==null) throw new AnnotationError(method, "invalid column property: "+propertyName); if(!ModelUtil.toString(column.propertyType(), true).equals(ModelUtil.toString(param.asType(), true))){ String type = ModelUtil.toString(column.propertyType(), true); if(ModelUtil.isPrimitive(column.propertyType()) || ModelUtil.isPrimitiveWrapper(column.propertyType())) type = ModelUtil.getPrimitive(type) + '/' + type; throw new AnnotationError(param, param.getSimpleName()+" must be of type "+type); } return column; } }