/** * 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.xml.sax.binding.impl.processor; import jlibs.core.annotation.processing.AnnotationError; import jlibs.core.annotation.processing.Printer; import jlibs.core.lang.StringUtil; import jlibs.core.lang.model.ModelUtil; import jlibs.xml.sax.binding.*; import org.xml.sax.Attributes; import javax.lang.model.element.*; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.xml.namespace.QName; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * @author Santhosh Kumar T */ abstract class BindingAnnotation{ protected String methodDecl; protected Class annotation; BindingAnnotation(Class annotation, String methodDecl){ this.annotation = annotation; this.methodDecl = methodDecl; } public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ validate(method, mirror); } protected boolean matches(AnnotationMirror mirror){ return ((TypeElement)mirror.getAnnotationType().asElement()).getQualifiedName().contentEquals(annotation.getCanonicalName()); } @SuppressWarnings({"UnusedDeclaration"}) protected void validate(ExecutableElement method, AnnotationMirror mirror){ validateModifiers(method); } public String lvalue(ExecutableElement method){ return ""; } public abstract String params(ExecutableElement method); protected void validateModifiers(ExecutableElement method){ Collection<Modifier> modifiers = method.getModifiers(); if(!modifiers.contains(Modifier.STATIC) && !modifiers.contains(Modifier.FINAL)) throw new AnnotationError(method, "method with annotation "+annotation+" must be final"); } protected boolean matches(ExecutableElement method, int paramIndex, Class expected){ return matches(method.getParameters().get(paramIndex), expected); } protected boolean matches(VariableElement param, Class expected){ if(param.asType().getKind()== TypeKind.DECLARED){ Name paramType = ((TypeElement)((DeclaredType)param.asType()).asElement()).getQualifiedName(); if(paramType.contentEquals(expected.getName())) return true; } return false; } protected String context(ExecutableElement method, int paramIndex, boolean parent){ return context(method.getParameters().get(paramIndex), parent); } protected String context(VariableElement param, boolean parent){ String str = ModelUtil.toString(param.asType(), true); if(str.equals(SAXContext.class.getName())) return toString(parent); else return "("+str+")"+toString(parent)+".object"; } private String toString(boolean parent){ return parent ? "parent" : "current"; } public void printMethod(Printer pw, Binding binding){ List<ExecutableElement> methods = new ArrayList<ExecutableElement>(); if(getMethods(binding, methods)){ pw.println("@Override"); pw.println(methodDecl); pw.indent++; pw.println("switch(state){"); pw.indent++; int id = 0; for(ExecutableElement method : methods){ if(method!=null){ pw.print("case "+id+":"); List<QName> path = binding.idMap.get(id); if(path.size()>0) pw.println(" // "+StringUtil.join(path.iterator(), "/")); else pw.println(); pw.indent++; printCase(pw, method); pw.println("break;"); pw.indent--; } id++; } pw.indent--; pw.println("}"); pw.indent--; pw.println("}"); pw.emptyLine(true); } } abstract boolean getMethods(Binding binding, List<ExecutableElement> methods); private void printCase(Printer pw, ExecutableElement method){ String lvalue = lvalue(method); pw.print(lvalue); if(method.getModifiers().contains(Modifier.STATIC)) pw.print(pw.clazz.getSimpleName()+"."+method.getSimpleName()+"("+ params(method)+")"); else pw.print("handler."+method.getSimpleName()+"("+ params(method)+")"); if(lvalue.length()>0 && !lvalue.endsWith("= ")) pw.print(")"); pw.println(";"); } } class ElementAnnotation extends BindingAnnotation{ ElementAnnotation(){ super(jlibs.xml.sax.binding.Binding.Element.class, null); } @Override public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ super.consume(binding, method, mirror); TypeElement bindingClazz = (TypeElement)((DeclaredType)ModelUtil.getAnnotationValue(method, mirror, "clazz")).asElement(); if(ModelUtil.getAnnotationMirror(bindingClazz, jlibs.xml.sax.binding.Binding.class)==null) throw new AnnotationError(method, mirror, bindingClazz.getQualifiedName()+" should have annotation "+jlibs.xml.sax.binding.Binding.class.getCanonicalName()); String element = ModelUtil.getAnnotationValue(method, mirror, "element"); binding.getBinding(method, mirror, element).element = bindingClazz; } @Override public String params(ExecutableElement method){ return null; } @Override boolean getMethods(Binding binding, List<ExecutableElement> methods){ throw new UnsupportedOperationException(); } } class BindingStartAnnotation extends BindingAnnotation{ BindingStartAnnotation(){ super( jlibs.xml.sax.binding.Binding.Start.class, "public void startElement(int state, SAXContext current, Attributes attributes) throws SAXException{" ); } @Override @SuppressWarnings({"unchecked"}) public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ super.consume(binding, method, mirror); for(AnnotationValue xpath: (Collection<AnnotationValue>)ModelUtil.getAnnotationValue(method, mirror, "value")) binding.getBinding(method, mirror, (String)xpath.getValue()).startMethod = method; } public String lvalue(ExecutableElement method){ switch(method.getReturnType().getKind()){ case VOID: return ""; case DECLARED: if(ModelUtil.toString(method.getReturnType(), true).equals(Attributes.class.getName())){ String m = ModelUtil.getAnnotationMirror(method, Temp.Add.class)==null ? "put" : "add"; return "current."+m+"Attributes("; } default: return "current.object = "; } } @Override public String params(ExecutableElement method){ List<String> params = new ArrayList<String>(); for(VariableElement param: method.getParameters()){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(param, Attr.class); if(mirror==null){ if(matches(param, Attributes.class)) params.add("attributes"); else params.add(context(param, false)); }else{ String value = ModelUtil.getAnnotationValue(param, mirror, "value"); if(value.length()==0) value = param.getSimpleName().toString(); StringBuilder buff = new StringBuilder(); buff.append("attributes.getValue("); if(value.contains(":")){ QName qname = Binding.toQName(param, mirror, value); if(qname.getNamespaceURI().length()>0) buff.append('"').append(qname.getNamespaceURI()).append("\", "); buff.append('"').append(qname.getLocalPart()).append('"'); }else buff.append('"').append(value).append('"'); buff.append(")"); params.add(buff.toString()); } } return StringUtil.join(params.iterator()); } @Override boolean getMethods(Binding binding, List<ExecutableElement> methods){ boolean nonEmpty = binding.startMethod!=null; methods.add(binding.startMethod); for(BindingRelation bindingRelation: binding.registry.values()) nonEmpty |= getMethods(bindingRelation.binding, methods); return nonEmpty; } } class BindingTextAnnotation extends BindingAnnotation{ BindingTextAnnotation(){ super( jlibs.xml.sax.binding.Binding.Text.class, "public void text(int state, SAXContext current, String text) throws SAXException{" ); } @Override @SuppressWarnings({"unchecked"}) public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ super.consume(binding, method, mirror); for(AnnotationValue xpath: (Collection<AnnotationValue>)ModelUtil.getAnnotationValue(method, mirror, "value")) binding.getBinding(method, mirror, (String)xpath.getValue()).textMethod = method; } public String lvalue(ExecutableElement method){ if(method.getReturnType().getKind()== TypeKind.VOID) return ""; else return "current.object = "; } @Override public String params(ExecutableElement method){ if(method.getParameters().size()>0){ if(!matches(method, method.getParameters().size()-1, String.class)) throw new AnnotationError(method, "method annotated with "+annotation.getCanonicalName()+" must take String as last argument"); } switch(method.getParameters().size()){ case 1: return "text"; case 2: return context(method, 0, false)+", text"; default: throw new AnnotationError(method, "method annotated with "+annotation.getCanonicalName()+" must take either one or two argument(s)"); } } @Override boolean getMethods(Binding binding, List<ExecutableElement> methods){ boolean nonEmpty = binding.textMethod!=null; methods.add(binding.textMethod); for(BindingRelation bindingRelation: binding.registry.values()) nonEmpty |= getMethods(bindingRelation.binding, methods); return nonEmpty; } } class BindingFinishAnnotation extends BindingAnnotation{ BindingFinishAnnotation(){ super( jlibs.xml.sax.binding.Binding.Finish.class, "public void endElement(int state, SAXContext current) throws SAXException{" ); } @Override @SuppressWarnings({"unchecked"}) public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ super.consume(binding, method, mirror); for(AnnotationValue xpath: (Collection<AnnotationValue>)ModelUtil.getAnnotationValue(method, mirror, "value")) binding.getBinding(method, mirror, (String)xpath.getValue()).finishMethod = method; } public String lvalue(ExecutableElement method){ if(method.getReturnType().getKind()== TypeKind.VOID) return ""; else return "current.object = "; } @Override public String params(ExecutableElement method){ List<String> params = new ArrayList<String>(); for(VariableElement param: method.getParameters()){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(param, Temp.class); if(mirror==null) params.add(context(param, false)); else{ String value = ModelUtil.getAnnotationValue(param, mirror, "value"); if(value.length()==0) value = param.getSimpleName().toString(); QName qname = Binding.toQName(param, mirror, value); StringBuilder buff = new StringBuilder(); buff.append("(").append(ModelUtil.toString(param.asType(), true)).append(")"); buff.append("current.get("); buff.append('"').append(qname.getNamespaceURI()).append("\", "); buff.append('"').append(qname.getLocalPart()).append('"'); buff.append(")"); params.add(buff.toString()); } } return StringUtil.join(params.iterator()); } @Override boolean getMethods(Binding binding, List<ExecutableElement> methods){ boolean nonEmpty = binding.finishMethod!=null; methods.add(binding.finishMethod); for(BindingRelation bindingRelation: binding.registry.values()) nonEmpty |= getMethods(bindingRelation.binding, methods); return nonEmpty; } } class RelationAnnotation extends BindingAnnotation{ boolean start; RelationAnnotation(boolean start){ super( start ? jlibs.xml.sax.binding.Relation.Start.class : jlibs.xml.sax.binding.Relation.Finish.class, "public void "+(start ? "start" : "end")+"Relation(int state, SAXContext parent, SAXContext current) throws SAXException{" ); this.start = start; } @Override @SuppressWarnings({"unchecked"}) public void consume(Binding binding, ExecutableElement method, AnnotationMirror mirror){ super.consume(binding, method, mirror); for(AnnotationValue child: (Collection<AnnotationValue>)ModelUtil.getAnnotationValue(method, mirror, "value")){ String xpath = (String)child.getValue(); Binding parentBinding; int slash = xpath.lastIndexOf('/'); if(slash==-1){ parentBinding = binding; }else{ parentBinding = binding.getBinding(method, mirror, xpath.substring(0, slash)); xpath = xpath.substring(slash+1); } Relation childRelation = parentBinding.getRelation(method, mirror, xpath); if(start) childRelation.startedMethod = method; else childRelation.finishedMethod = method; } } @Override public String lvalue(ExecutableElement method){ TypeMirror mirror = method.getReturnType(); switch(mirror.getKind()){ case VOID: return ""; default: String m = ModelUtil.getAnnotationMirror(method, Temp.Add.class)==null ? "put" : "add"; return "parent."+m+"(current.element(), "; } } @Override public String params(ExecutableElement method){ boolean parent = method.getReturnType().getKind() == TypeKind.VOID; List<String> params = new ArrayList<String>(); for(VariableElement param: method.getParameters()){ AnnotationMirror mirror = ModelUtil.getAnnotationMirror(param, Temp.class); if(mirror==null){ if(ModelUtil.getAnnotationMirror(param, Parent.class)!=null) parent = true; else if(ModelUtil.getAnnotationMirror(param, Current.class)!=null) parent = false; params.add(context(param, parent)); parent = !parent; }else{ String value = ModelUtil.getAnnotationValue(param, mirror, "value"); if(value.length()==0) value = param.getSimpleName().toString(); QName qname = Binding.toQName(param, mirror, value); StringBuilder buff = new StringBuilder(); buff.append("(").append(ModelUtil.toString(param.asType(), true)).append(")"); buff.append("current.get("); buff.append('"').append(qname.getNamespaceURI()).append("\", "); buff.append('"').append(qname.getLocalPart()).append('"'); buff.append(")"); params.add(buff.toString()); } } return StringUtil.join(params.iterator()); } @Override boolean getMethods(Binding binding, List<ExecutableElement> methods){ methods.add(null); return _getMethods(binding, methods); } private boolean _getMethods(Binding binding, List<ExecutableElement> methods){ boolean nonEmpty = false; for(BindingRelation bindingRelation: binding.registry.values()){ ExecutableElement method = start ? bindingRelation.relation.startedMethod : bindingRelation.relation.finishedMethod; nonEmpty |= method!=null; methods.add(method); nonEmpty |= _getMethods(bindingRelation.binding, methods); } return nonEmpty; } }