jaroslav@26: /** jaroslav@106: * Back 2 Browser Bytecode Translator jaroslav@106: * Copyright (C) 2012 Jaroslav Tulach jaroslav@26: * jaroslav@26: * This program is free software: you can redistribute it and/or modify jaroslav@26: * it under the terms of the GNU General Public License as published by jaroslav@26: * the Free Software Foundation, version 2 of the License. jaroslav@26: * jaroslav@26: * This program is distributed in the hope that it will be useful, jaroslav@26: * but WITHOUT ANY WARRANTY; without even the implied warranty of jaroslav@26: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the jaroslav@26: * GNU General Public License for more details. jaroslav@26: * jaroslav@26: * You should have received a copy of the GNU General Public License jaroslav@26: * along with this program. Look for COPYING file in the top folder. jaroslav@26: * If not, see http://opensource.org/licenses/GPL-2.0. jaroslav@26: */ jaroslav@26: package org.apidesign.bck2brwsr.htmlpage; jaroslav@26: jaroslav@26: import java.io.IOException; jaroslav@26: import java.io.InputStream; jaroslav@26: import java.io.OutputStreamWriter; jaroslav@770: import java.io.StringWriter; jaroslav@26: import java.io.Writer; jaroslav@117: import java.util.ArrayList; jaroslav@491: import java.util.Collection; jaroslav@117: import java.util.Collections; jaroslav@498: import java.util.HashMap; jaroslav@498: import java.util.LinkedHashSet; jaroslav@117: import java.util.List; jaroslav@498: import java.util.Map; jaroslav@26: import java.util.Set; jaroslav@906: import java.util.WeakHashMap; jaroslav@26: import javax.annotation.processing.AbstractProcessor; jaroslav@117: import javax.annotation.processing.Completion; jaroslav@117: import javax.annotation.processing.Completions; jaroslav@26: import javax.annotation.processing.Processor; jaroslav@26: import javax.annotation.processing.RoundEnvironment; jaroslav@26: import javax.annotation.processing.SupportedAnnotationTypes; jaroslav@117: import javax.lang.model.element.AnnotationMirror; jaroslav@26: import javax.lang.model.element.Element; jaroslav@28: import javax.lang.model.element.ElementKind; jaroslav@28: import javax.lang.model.element.ExecutableElement; jaroslav@28: import javax.lang.model.element.Modifier; jaroslav@26: import javax.lang.model.element.PackageElement; jaroslav@26: import javax.lang.model.element.TypeElement; jaroslav@491: import javax.lang.model.element.VariableElement; jaroslav@490: import javax.lang.model.type.MirroredTypeException; jaroslav@813: import javax.lang.model.type.TypeKind; jaroslav@124: import javax.lang.model.type.TypeMirror; jaroslav@929: import javax.lang.model.util.Elements; jaroslav@768: import javax.lang.model.util.Types; jaroslav@26: import javax.tools.Diagnostic; jaroslav@26: import javax.tools.FileObject; jaroslav@26: import javax.tools.StandardLocation; jaroslav@491: import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty; jaroslav@770: import org.apidesign.bck2brwsr.htmlpage.api.Model; jaroslav@435: import org.apidesign.bck2brwsr.htmlpage.api.On; jaroslav@879: import org.apidesign.bck2brwsr.htmlpage.api.OnFunction; jaroslav@26: import org.apidesign.bck2brwsr.htmlpage.api.Page; jaroslav@490: import org.apidesign.bck2brwsr.htmlpage.api.Property; jaroslav@26: import org.openide.util.lookup.ServiceProvider; jaroslav@26: jaroslav@26: /** Annotation processor to process an XHTML page and generate appropriate jaroslav@26: * "id" file. jaroslav@26: * jaroslav@26: * @author Jaroslav Tulach jaroslav@26: */ jaroslav@26: @ServiceProvider(service=Processor.class) jaroslav@28: @SupportedAnnotationTypes({ jaroslav@770: "org.apidesign.bck2brwsr.htmlpage.api.Model", jaroslav@28: "org.apidesign.bck2brwsr.htmlpage.api.Page", jaroslav@879: "org.apidesign.bck2brwsr.htmlpage.api.OnFunction", jaroslav@435: "org.apidesign.bck2brwsr.htmlpage.api.On" jaroslav@28: }) jaroslav@26: public final class PageProcessor extends AbstractProcessor { jaroslav@906: private final Map models = new WeakHashMap<>(); jaroslav@26: @Override jaroslav@26: public boolean process(Set annotations, RoundEnvironment roundEnv) { jaroslav@765: boolean ok = true; jaroslav@770: for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) { jaroslav@770: if (!processModel(e)) { jaroslav@770: ok = false; jaroslav@26: } jaroslav@26: } jaroslav@543: for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) { jaroslav@770: if (!processPage(e)) { jaroslav@765: ok = false; jaroslav@543: } jaroslav@543: } jaroslav@906: if (roundEnv.processingOver()) { jaroslav@906: models.clear(); jaroslav@906: } jaroslav@765: return ok; jaroslav@26: } jaroslav@26: jaroslav@26: private InputStream openStream(String pkg, String name) throws IOException { jaroslav@26: try { jaroslav@107: FileObject fo = processingEnv.getFiler().getResource( jaroslav@107: StandardLocation.SOURCE_PATH, pkg, name); jaroslav@26: return fo.openInputStream(); jaroslav@26: } catch (IOException ex) { jaroslav@26: return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream(); jaroslav@26: } jaroslav@26: } jaroslav@770: jaroslav@770: private boolean processModel(Element e) { jaroslav@770: boolean ok = true; jaroslav@770: Model m = e.getAnnotation(Model.class); jaroslav@770: if (m == null) { jaroslav@770: return true; jaroslav@770: } jaroslav@770: String pkg = findPkgName(e); jaroslav@770: Writer w; jaroslav@770: String className = m.className(); jaroslav@770: try { jaroslav@770: StringWriter body = new StringWriter(); jaroslav@770: List propsGetSet = new ArrayList<>(); jaroslav@914: List functions = new ArrayList<>(); jaroslav@770: Map> propsDeps = new HashMap<>(); jaroslav@770: if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@875: if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@914: if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { jaroslav@914: ok = false; jaroslav@914: } jaroslav@770: FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); jaroslav@770: w = new OutputStreamWriter(java.openOutputStream()); jaroslav@770: try { jaroslav@770: w.append("package " + pkg + ";\n"); jaroslav@770: w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); jaroslav@770: w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n"); jaroslav@908: w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n"); jaroslav@930: w.append("final class ").append(className).append(" implements Cloneable {\n"); jaroslav@770: w.append(" private Object json;\n"); jaroslav@770: w.append(" private boolean locked;\n"); jaroslav@770: w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); jaroslav@770: w.append(body.toString()); jaroslav@925: w.append(" private static Class<" + inPckName(e) + "> modelFor() { return null; }\n"); jaroslav@909: w.append(" public ").append(className).append("() {\n"); jaroslav@909: w.append(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, "); jaroslav@909: writeStringArray(propsGetSet, w); jaroslav@909: w.append(", "); jaroslav@914: writeStringArray(functions, w); jaroslav@909: w.append(" );\n"); jaroslav@909: w.append(" };\n"); jaroslav@919: writeToString(m.properties(), w); jaroslav@930: writeClone(className, m.properties(), w); jaroslav@770: w.append("}\n"); jaroslav@770: } finally { jaroslav@770: w.close(); jaroslav@770: } jaroslav@770: } catch (IOException ex) { jaroslav@770: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e); jaroslav@770: return false; jaroslav@770: } jaroslav@770: return ok; jaroslav@770: } jaroslav@770: jaroslav@770: private boolean processPage(Element e) { jaroslav@770: boolean ok = true; jaroslav@770: Page p = e.getAnnotation(Page.class); jaroslav@770: if (p == null) { jaroslav@770: return true; jaroslav@770: } jaroslav@770: String pkg = findPkgName(e); jaroslav@770: jaroslav@770: ProcessPage pp; jaroslav@770: try (InputStream is = openStream(pkg, p.xhtml())) { jaroslav@770: pp = ProcessPage.readPage(is); jaroslav@770: is.close(); jaroslav@770: } catch (IOException iOException) { jaroslav@923: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e); jaroslav@770: ok = false; jaroslav@770: pp = null; jaroslav@770: } jaroslav@770: Writer w; jaroslav@770: String className = p.className(); jaroslav@770: if (className.isEmpty()) { jaroslav@770: int indx = p.xhtml().indexOf('.'); jaroslav@770: className = p.xhtml().substring(0, indx); jaroslav@770: } jaroslav@770: try { jaroslav@770: StringWriter body = new StringWriter(); jaroslav@770: List propsGetSet = new ArrayList<>(); jaroslav@879: List functions = new ArrayList<>(); jaroslav@770: Map> propsDeps = new HashMap<>(); jaroslav@770: if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@875: if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@879: if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { jaroslav@879: ok = false; jaroslav@879: } jaroslav@770: jaroslav@770: FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e); jaroslav@770: w = new OutputStreamWriter(java.openOutputStream()); jaroslav@770: try { jaroslav@770: w.append("package " + pkg + ";\n"); jaroslav@770: w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n"); jaroslav@770: w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n"); jaroslav@770: w.append("final class ").append(className).append(" {\n"); jaroslav@770: w.append(" private boolean locked;\n"); jaroslav@770: if (!initializeOnClick(className, (TypeElement) e, w, pp)) { jaroslav@770: ok = false; jaroslav@770: } else { jaroslav@923: if (pp != null) for (String id : pp.ids()) { jaroslav@770: String tag = pp.tagNameForId(id); jaroslav@770: String type = type(tag); jaroslav@770: w.append(" ").append("public final "). jaroslav@770: append(type).append(' ').append(cnstnt(id)).append(" = new "). jaroslav@770: append(type).append("(\"").append(id).append("\");\n"); jaroslav@770: } jaroslav@770: } jaroslav@770: w.append(" private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n"); jaroslav@770: w.append(body.toString()); jaroslav@770: if (!propsGetSet.isEmpty()) { jaroslav@770: w.write("public " + className + " applyBindings() {\n"); jaroslav@770: w.write(" ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings("); jaroslav@770: w.write(className + ".class, this, "); jaroslav@909: writeStringArray(propsGetSet, w); jaroslav@909: w.append(", "); jaroslav@909: writeStringArray(functions, w); jaroslav@909: w.write(");\n return this;\n}\n"); jaroslav@770: jaroslav@770: w.write("public void triggerEvent(Element e, OnEvent ev) {\n"); jaroslav@770: w.write(" org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n"); jaroslav@770: w.write("}\n"); jaroslav@770: } jaroslav@770: w.append("}\n"); jaroslav@770: } finally { jaroslav@770: w.close(); jaroslav@770: } jaroslav@770: } catch (IOException ex) { jaroslav@770: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e); jaroslav@770: return false; jaroslav@770: } jaroslav@770: return ok; jaroslav@770: } jaroslav@26: jaroslav@871: private static String type(String tag) { jaroslav@871: if (tag.equals("title")) { jaroslav@871: return "Title"; jaroslav@871: } jaroslav@871: if (tag.equals("button")) { jaroslav@871: return "Button"; jaroslav@871: } jaroslav@871: if (tag.equals("input")) { jaroslav@871: return "Input"; jaroslav@871: } jaroslav@871: if (tag.equals("canvas")) { jaroslav@871: return "Canvas"; jaroslav@871: } jaroslav@871: if (tag.equals("img")) { jaroslav@871: return "Image"; jaroslav@871: } jaroslav@871: return "Element"; jaroslav@871: } jaroslav@871: jaroslav@26: private static String cnstnt(String id) { jaroslav@892: return id.replace('.', '_').replace('-', '_'); jaroslav@26: } jaroslav@28: jaroslav@510: private boolean initializeOnClick( jaroslav@510: String className, TypeElement type, Writer w, ProcessPage pp jaroslav@510: ) throws IOException { jaroslav@765: boolean ok = true; jaroslav@124: TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); jaroslav@489: { //for (Element clazz : pe.getEnclosedElements()) { jaroslav@489: // if (clazz.getKind() != ElementKind.CLASS) { jaroslav@489: // continue; jaroslav@489: // } jaroslav@512: w.append(" public ").append(className).append("() {\n"); jaroslav@512: StringBuilder dispatch = new StringBuilder(); jaroslav@512: int dispatchCnt = 0; jaroslav@489: for (Element method : type.getEnclosedElements()) { jaroslav@435: On oc = method.getAnnotation(On.class); jaroslav@28: if (oc != null) { jaroslav@124: for (String id : oc.id()) { jaroslav@765: if (pp == null) { jaroslav@765: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page."); jaroslav@765: ok = false; jaroslav@765: continue; jaroslav@765: } jaroslav@124: if (pp.tagNameForId(id) == null) { jaroslav@435: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method); jaroslav@765: ok = false; jaroslav@765: continue; jaroslav@124: } jaroslav@124: ExecutableElement ee = (ExecutableElement)method; jaroslav@879: CharSequence params = wrapParams(ee, id, className, "ev", null); jaroslav@124: if (!ee.getModifiers().contains(Modifier.STATIC)) { jaroslav@435: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee); jaroslav@765: ok = false; jaroslav@765: continue; jaroslav@124: } jaroslav@124: if (ee.getModifiers().contains(Modifier.PRIVATE)) { jaroslav@435: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee); jaroslav@765: ok = false; jaroslav@765: continue; jaroslav@124: } jaroslav@435: w.append(" OnEvent." + oc.event()).append(".of(").append(cnstnt(id)). jaroslav@512: append(").perform(new OnDispatch(" + dispatchCnt + "));\n"); jaroslav@512: jaroslav@512: dispatch. jaroslav@512: append(" case ").append(dispatchCnt).append(": "). jaroslav@512: append(type.getSimpleName().toString()). jaroslav@512: append('.').append(ee.getSimpleName()).append("("). jaroslav@512: append(params). jaroslav@512: append("); break;\n"); jaroslav@512: jaroslav@512: dispatchCnt++; jaroslav@512: } jaroslav@28: } jaroslav@28: } jaroslav@512: w.append(" }\n"); jaroslav@512: if (dispatchCnt > 0) { jaroslav@813: w.append("class OnDispatch implements OnHandler {\n"); jaroslav@512: w.append(" private final int dispatch;\n"); jaroslav@512: w.append(" OnDispatch(int d) { dispatch = d; }\n"); jaroslav@813: w.append(" public void onEvent(Object ev) {\n"); jaroslav@512: w.append(" switch (dispatch) {\n"); jaroslav@512: w.append(dispatch); jaroslav@512: w.append(" }\n"); jaroslav@512: w.append(" }\n"); jaroslav@512: w.append("}\n"); jaroslav@512: } jaroslav@512: jaroslav@512: jaroslav@28: } jaroslav@765: return ok; jaroslav@28: } jaroslav@117: jaroslav@117: @Override jaroslav@117: public Iterable getCompletions( jaroslav@117: Element element, AnnotationMirror annotation, jaroslav@117: ExecutableElement member, String userText jaroslav@117: ) { jaroslav@117: if (!userText.startsWith("\"")) { jaroslav@117: return Collections.emptyList(); jaroslav@117: } jaroslav@117: jaroslav@117: Element cls = findClass(element); jaroslav@117: Page p = cls.getAnnotation(Page.class); jaroslav@770: String pkg = findPkgName(cls); jaroslav@117: ProcessPage pp; jaroslav@117: try { jaroslav@117: InputStream is = openStream(pkg, p.xhtml()); jaroslav@117: pp = ProcessPage.readPage(is); jaroslav@117: is.close(); jaroslav@117: } catch (IOException iOException) { jaroslav@117: return Collections.emptyList(); jaroslav@117: } jaroslav@117: jaroslav@770: List cc = new ArrayList<>(); jaroslav@117: userText = userText.substring(1); jaroslav@117: for (String id : pp.ids()) { jaroslav@117: if (id.startsWith(userText)) { jaroslav@117: cc.add(Completions.of("\"" + id + "\"", id)); jaroslav@117: } jaroslav@117: } jaroslav@117: return cc; jaroslav@117: } jaroslav@117: jaroslav@117: private static Element findClass(Element e) { jaroslav@117: if (e == null) { jaroslav@117: return null; jaroslav@117: } jaroslav@117: Page p = e.getAnnotation(Page.class); jaroslav@117: if (p != null) { jaroslav@117: return e; jaroslav@117: } jaroslav@117: return e.getEnclosingElement(); jaroslav@117: } jaroslav@490: jaroslav@770: private boolean generateProperties( jaroslav@875: Element where, jaroslav@770: Writer w, Property[] properties, jaroslav@770: Collection props, Map> deps jaroslav@492: ) throws IOException { jaroslav@770: boolean ok = true; jaroslav@490: for (Property p : properties) { jaroslav@770: final String tn; jaroslav@875: tn = typeName(where, p); jaroslav@760: String[] gs = toGetSet(p.name(), tn, p.array()); jaroslav@490: jaroslav@760: if (p.array()) { jaroslav@761: w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\"" jaroslav@761: + p.name() + "\""); jaroslav@761: final Collection dependants = deps.get(p.name()); jaroslav@761: if (dependants != null) { jaroslav@761: for (String depProp : dependants) { jaroslav@761: w.write(", "); jaroslav@761: w.write('\"'); jaroslav@761: w.write(depProp); jaroslav@761: w.write('\"'); jaroslav@761: } jaroslav@498: } jaroslav@761: w.write(");\n"); jaroslav@760: w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n"); jaroslav@760: w.write(" if (locked) throw new IllegalStateException();\n"); jaroslav@761: w.write(" prop_" + p.name() + ".assign(ko);\n"); jaroslav@760: w.write(" return prop_" + p.name() + ";\n"); jaroslav@760: w.write("}\n"); jaroslav@760: } else { jaroslav@760: w.write("private " + tn + " prop_" + p.name() + ";\n"); jaroslav@760: w.write("public " + tn + " " + gs[0] + "() {\n"); jaroslav@760: w.write(" if (locked) throw new IllegalStateException();\n"); jaroslav@760: w.write(" return prop_" + p.name() + ";\n"); jaroslav@760: w.write("}\n"); jaroslav@760: w.write("public void " + gs[1] + "(" + tn + " v) {\n"); jaroslav@760: w.write(" if (locked) throw new IllegalStateException();\n"); jaroslav@760: w.write(" prop_" + p.name() + " = v;\n"); jaroslav@760: w.write(" if (ko != null) {\n"); jaroslav@760: w.write(" ko.valueHasMutated(\"" + p.name() + "\");\n"); jaroslav@760: final Collection dependants = deps.get(p.name()); jaroslav@760: if (dependants != null) { jaroslav@760: for (String depProp : dependants) { jaroslav@760: w.write(" ko.valueHasMutated(\"" + depProp + "\");\n"); jaroslav@760: } jaroslav@543: } jaroslav@760: w.write(" }\n"); jaroslav@760: w.write("}\n"); jaroslav@498: } jaroslav@492: jaroslav@492: props.add(p.name()); jaroslav@492: props.add(gs[2]); jaroslav@492: props.add(gs[3]); jaroslav@530: props.add(gs[0]); jaroslav@490: } jaroslav@770: return ok; jaroslav@490: } jaroslav@490: jaroslav@498: private boolean generateComputedProperties( jaroslav@765: Writer w, Property[] fixedProps, jaroslav@765: Collection arr, Collection props, jaroslav@498: Map> deps jaroslav@492: ) throws IOException { jaroslav@765: boolean ok = true; jaroslav@491: for (Element e : arr) { jaroslav@491: if (e.getKind() != ElementKind.METHOD) { jaroslav@491: continue; jaroslav@491: } jaroslav@491: if (e.getAnnotation(ComputedProperty.class) == null) { jaroslav@491: continue; jaroslav@491: } jaroslav@491: ExecutableElement ee = (ExecutableElement)e; jaroslav@767: final TypeMirror rt = ee.getReturnType(); jaroslav@768: final Types tu = processingEnv.getTypeUtils(); jaroslav@768: TypeMirror ert = tu.erasure(rt); jaroslav@929: String tn = fqn(ert, ee); jaroslav@768: boolean array = false; jaroslav@768: if (tn.equals("java.util.List")) { jaroslav@768: array = true; jaroslav@768: } jaroslav@768: jaroslav@498: final String sn = ee.getSimpleName().toString(); jaroslav@768: String[] gs = toGetSet(sn, tn, array); jaroslav@498: jaroslav@505: w.write("public " + tn + " " + gs[0] + "() {\n"); jaroslav@500: w.write(" if (locked) throw new IllegalStateException();\n"); jaroslav@500: int arg = 0; jaroslav@491: for (VariableElement pe : ee.getParameters()) { jaroslav@498: final String dn = pe.getSimpleName().toString(); jaroslav@765: jaroslav@765: if (!verifyPropName(pe, dn, fixedProps)) { jaroslav@765: ok = false; jaroslav@765: } jaroslav@765: jaroslav@929: final String dt = fqn(pe.asType(), ee); jaroslav@760: String[] call = toGetSet(dn, dt, false); jaroslav@500: w.write(" " + dt + " arg" + (++arg) + " = "); jaroslav@500: w.write(call[0] + "();\n"); jaroslav@498: jaroslav@498: Collection depends = deps.get(dn); jaroslav@498: if (depends == null) { jaroslav@770: depends = new LinkedHashSet<>(); jaroslav@498: deps.put(dn, depends); jaroslav@498: } jaroslav@498: depends.add(sn); jaroslav@491: } jaroslav@500: w.write(" try {\n"); jaroslav@500: w.write(" locked = true;\n"); jaroslav@500: w.write(" return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "("); jaroslav@500: String sep = ""; jaroslav@500: for (int i = 1; i <= arg; i++) { jaroslav@500: w.write(sep); jaroslav@500: w.write("arg" + i); jaroslav@500: sep = ", "; jaroslav@500: } jaroslav@491: w.write(");\n"); jaroslav@500: w.write(" } finally {\n"); jaroslav@500: w.write(" locked = false;\n"); jaroslav@500: w.write(" }\n"); jaroslav@491: w.write("}\n"); jaroslav@768: jaroslav@492: props.add(e.getSimpleName().toString()); jaroslav@492: props.add(gs[2]); jaroslav@492: props.add(null); jaroslav@530: props.add(gs[0]); jaroslav@491: } jaroslav@498: jaroslav@765: return ok; jaroslav@491: } jaroslav@491: jaroslav@760: private static String[] toGetSet(String name, String type, boolean array) { jaroslav@491: String n = Character.toUpperCase(name.charAt(0)) + name.substring(1); jaroslav@492: String bck2brwsrType = "L" + type.replace('.', '_') + "_2"; jaroslav@492: if ("int".equals(type)) { jaroslav@492: bck2brwsrType = "I"; jaroslav@492: } jaroslav@492: if ("double".equals(type)) { jaroslav@492: bck2brwsrType = "D"; jaroslav@492: } jaroslav@492: String pref = "get"; jaroslav@492: if ("boolean".equals(type)) { jaroslav@492: pref = "is"; jaroslav@492: bck2brwsrType = "Z"; jaroslav@492: } jaroslav@498: final String nu = n.replace('.', '_'); jaroslav@760: if (array) { jaroslav@760: return new String[] { jaroslav@760: "get" + n, jaroslav@760: null, jaroslav@887: "get" + nu + "__Ljava_util_List_2", jaroslav@760: null jaroslav@760: }; jaroslav@760: } jaroslav@492: return new String[]{ jaroslav@492: pref + n, jaroslav@492: "set" + n, jaroslav@498: pref + nu + "__" + bck2brwsrType, jaroslav@498: "set" + nu + "__V" + bck2brwsrType jaroslav@492: }; jaroslav@490: } jaroslav@490: jaroslav@875: private String typeName(Element where, Property p) { jaroslav@764: String ret; jaroslav@930: boolean[] isModel = { false }; jaroslav@930: boolean[] isEnum = { false }; jaroslav@930: ret = checkType(p, isModel, isEnum); jaroslav@764: if (p.array()) { jaroslav@769: String bt = findBoxedType(ret); jaroslav@769: if (bt != null) { jaroslav@769: return bt; jaroslav@764: } jaroslav@764: } jaroslav@930: if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) { jaroslav@770: String bt = findBoxedType(ret); jaroslav@770: if (bt == null) { jaroslav@875: processingEnv.getMessager().printMessage( jaroslav@875: Diagnostic.Kind.ERROR, jaroslav@875: "Only primitive types supported in the mapping. Not " + ret, jaroslav@875: where jaroslav@875: ); jaroslav@770: } jaroslav@769: } jaroslav@764: return ret; jaroslav@543: } jaroslav@769: jaroslav@769: private static String findBoxedType(String ret) { jaroslav@876: if (ret.equals("boolean")) { jaroslav@876: return Boolean.class.getName(); jaroslav@876: } jaroslav@769: if (ret.equals("byte")) { jaroslav@769: return Byte.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("short")) { jaroslav@769: return Short.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("char")) { jaroslav@769: return Character.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("int")) { jaroslav@769: return Integer.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("long")) { jaroslav@769: return Long.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("float")) { jaroslav@769: return Float.class.getName(); jaroslav@769: } jaroslav@769: if (ret.equals("double")) { jaroslav@769: return Double.class.getName(); jaroslav@769: } jaroslav@769: return null; jaroslav@769: } jaroslav@765: jaroslav@765: private boolean verifyPropName(Element e, String propName, Property[] existingProps) { jaroslav@765: StringBuilder sb = new StringBuilder(); jaroslav@765: String sep = ""; jaroslav@765: for (Property property : existingProps) { jaroslav@765: if (property.name().equals(propName)) { jaroslav@765: return true; jaroslav@765: } jaroslav@765: sb.append(sep); jaroslav@765: sb.append('"'); jaroslav@765: sb.append(property.name()); jaroslav@765: sb.append('"'); jaroslav@765: sep = ", "; jaroslav@765: } jaroslav@765: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, jaroslav@765: propName + " is not one of known properties: " + sb jaroslav@765: , e jaroslav@765: ); jaroslav@765: return false; jaroslav@765: } jaroslav@770: jaroslav@770: private static String findPkgName(Element e) { jaroslav@770: for (;;) { jaroslav@770: if (e.getKind() == ElementKind.PACKAGE) { jaroslav@770: return ((PackageElement)e).getQualifiedName().toString(); jaroslav@770: } jaroslav@770: e = e.getEnclosingElement(); jaroslav@490: } jaroslav@490: } jaroslav@879: jaroslav@879: private boolean generateFunctions( jaroslav@879: Element clazz, StringWriter body, String className, jaroslav@879: List enclosedElements, List functions jaroslav@879: ) { jaroslav@879: for (Element m : enclosedElements) { jaroslav@879: if (m.getKind() != ElementKind.METHOD) { jaroslav@879: continue; jaroslav@879: } jaroslav@879: ExecutableElement e = (ExecutableElement)m; jaroslav@879: OnFunction onF = e.getAnnotation(OnFunction.class); jaroslav@879: if (onF == null) { jaroslav@879: continue; jaroslav@879: } jaroslav@879: if (!e.getModifiers().contains(Modifier.STATIC)) { jaroslav@879: processingEnv.getMessager().printMessage( jaroslav@879: Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e jaroslav@879: ); jaroslav@879: return false; jaroslav@879: } jaroslav@879: if (e.getModifiers().contains(Modifier.PRIVATE)) { jaroslav@879: processingEnv.getMessager().printMessage( jaroslav@879: Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e jaroslav@879: ); jaroslav@879: return false; jaroslav@879: } jaroslav@879: if (e.getReturnType().getKind() != TypeKind.VOID) { jaroslav@879: processingEnv.getMessager().printMessage( jaroslav@879: Diagnostic.Kind.ERROR, "@OnFunction method should return void", e jaroslav@879: ); jaroslav@879: return false; jaroslav@879: } jaroslav@879: String n = e.getSimpleName().toString(); jaroslav@927: body.append("private void ").append(n).append("(Object data, Object ev) {\n"); jaroslav@879: body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); jaroslav@879: body.append(wrapParams(e, null, className, "ev", "data")); jaroslav@879: body.append(");\n"); jaroslav@879: body.append("}\n"); jaroslav@879: jaroslav@909: functions.add(n); jaroslav@909: functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2"); jaroslav@879: } jaroslav@879: return true; jaroslav@879: } jaroslav@879: jaroslav@879: private CharSequence wrapParams( jaroslav@879: ExecutableElement ee, String id, String className, String evName, String dataName jaroslav@879: ) { jaroslav@879: TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); jaroslav@879: StringBuilder params = new StringBuilder(); jaroslav@879: boolean first = true; jaroslav@879: for (VariableElement ve : ee.getParameters()) { jaroslav@879: if (!first) { jaroslav@879: params.append(", "); jaroslav@879: } jaroslav@879: first = false; jaroslav@879: String toCall = null; jaroslav@879: if (ve.asType() == stringType) { jaroslav@879: if (ve.getSimpleName().contentEquals("id")) { jaroslav@879: params.append('"').append(id).append('"'); jaroslav@879: continue; jaroslav@879: } jaroslav@906: toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString("; jaroslav@879: } jaroslav@879: if (ve.asType().getKind() == TypeKind.DOUBLE) { jaroslav@906: toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble("; jaroslav@879: } jaroslav@879: if (ve.asType().getKind() == TypeKind.INT) { jaroslav@906: toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt("; jaroslav@906: } jaroslav@906: if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) { jaroslav@906: toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, "; jaroslav@879: } jaroslav@879: jaroslav@879: if (toCall != null) { jaroslav@906: params.append(toCall); jaroslav@906: if (dataName != null && ve.getSimpleName().contentEquals(dataName)) { jaroslav@879: params.append(dataName); jaroslav@879: params.append(", null"); jaroslav@879: } else { jaroslav@879: params.append(evName); jaroslav@879: params.append(", \""); jaroslav@879: params.append(ve.getSimpleName().toString()); jaroslav@879: params.append("\""); jaroslav@879: } jaroslav@879: params.append(")"); jaroslav@879: continue; jaroslav@879: } jaroslav@929: String rn = fqn(ve.asType(), ee); jaroslav@879: int last = rn.lastIndexOf('.'); jaroslav@879: if (last >= 0) { jaroslav@879: rn = rn.substring(last + 1); jaroslav@879: } jaroslav@879: if (rn.equals(className)) { jaroslav@879: params.append(className).append(".this"); jaroslav@879: continue; jaroslav@879: } jaroslav@879: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, jaroslav@879: "@On method can only accept String named 'id' or " + className + " arguments", jaroslav@879: ee jaroslav@879: ); jaroslav@879: } jaroslav@879: return params; jaroslav@879: } jaroslav@906: jaroslav@906: private boolean isModel(TypeMirror tm) { jaroslav@906: final Element e = processingEnv.getTypeUtils().asElement(tm); jaroslav@907: if (e == null) { jaroslav@907: return false; jaroslav@907: } jaroslav@906: for (Element ch : e.getEnclosedElements()) { jaroslav@906: if (ch.getKind() == ElementKind.METHOD) { jaroslav@906: ExecutableElement ee = (ExecutableElement)ch; jaroslav@906: if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) { jaroslav@906: return true; jaroslav@906: } jaroslav@906: } jaroslav@906: } jaroslav@906: return models.values().contains(e.getSimpleName().toString()); jaroslav@906: } jaroslav@909: jaroslav@909: private void writeStringArray(List strings, Writer w) throws IOException { jaroslav@909: w.write("new String[] {\n"); jaroslav@909: String sep = ""; jaroslav@909: for (String n : strings) { jaroslav@909: w.write(sep); jaroslav@909: if (n == null) { jaroslav@909: w.write(" null"); jaroslav@909: } else { jaroslav@909: w.write(" \"" + n + "\""); jaroslav@909: } jaroslav@909: sep = ",\n"; jaroslav@909: } jaroslav@909: w.write("\n }"); jaroslav@909: } jaroslav@919: jaroslav@919: private void writeToString(Property[] props, Writer w) throws IOException { jaroslav@919: w.write(" public String toString() {\n"); jaroslav@919: w.write(" StringBuilder sb = new StringBuilder();\n"); jaroslav@919: w.write(" sb.append('{');\n"); jaroslav@919: String sep = ""; jaroslav@919: for (Property p : props) { jaroslav@919: w.write(sep); jaroslav@919: w.append(" sb.append(\"" + p.name() + ": \");\n"); jaroslav@920: w.append(" sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_"); jaroslav@920: w.append(p.name()).append("));\n"); jaroslav@919: sep = " sb.append(',');\n"; jaroslav@919: } jaroslav@919: w.write(" sb.append('}');\n"); jaroslav@919: w.write(" return sb.toString();\n"); jaroslav@919: w.write(" }\n"); jaroslav@919: } jaroslav@930: private void writeClone(String className, Property[] props, Writer w) throws IOException { jaroslav@930: w.write(" public " + className + " clone() {\n"); jaroslav@930: w.write(" " + className + " ret = new " + className + "();\n"); jaroslav@930: for (Property p : props) { jaroslav@930: if (!p.array()) { jaroslav@930: boolean isModel[] = { false }; jaroslav@930: boolean isEnum[] = { false }; jaroslav@930: checkType(p, isModel, isEnum); jaroslav@930: if (!isModel[0]) { jaroslav@930: w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n"); jaroslav@930: continue; jaroslav@930: } jaroslav@930: } jaroslav@930: w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n"); jaroslav@930: } jaroslav@930: jaroslav@930: w.write(" return ret;\n"); jaroslav@930: w.write(" }\n"); jaroslav@930: } jaroslav@925: jaroslav@925: private String inPckName(Element e) { jaroslav@925: StringBuilder sb = new StringBuilder(); jaroslav@925: while (e.getKind() != ElementKind.PACKAGE) { jaroslav@925: if (sb.length() == 0) { jaroslav@925: sb.append(e.getSimpleName()); jaroslav@925: } else { jaroslav@925: sb.insert(0, '.'); jaroslav@925: sb.insert(0, e.getSimpleName()); jaroslav@925: } jaroslav@925: e = e.getEnclosingElement(); jaroslav@925: } jaroslav@925: return sb.toString(); jaroslav@925: } jaroslav@929: jaroslav@929: private String fqn(TypeMirror pt, Element relative) { jaroslav@929: if (pt.getKind() == TypeKind.ERROR) { jaroslav@929: final Elements eu = processingEnv.getElementUtils(); jaroslav@929: PackageElement pckg = eu.getPackageOf(relative); jaroslav@929: return pckg.getQualifiedName() + "." + pt.toString(); jaroslav@929: } jaroslav@929: return pt.toString(); jaroslav@929: } jaroslav@930: jaroslav@930: private String checkType(Property p, boolean[] isModel, boolean[] isEnum) { jaroslav@930: String ret; jaroslav@930: try { jaroslav@930: ret = p.type().getName(); jaroslav@930: } catch (MirroredTypeException ex) { jaroslav@930: TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror()); jaroslav@930: final Element e = processingEnv.getTypeUtils().asElement(tm); jaroslav@930: final Model m = e == null ? null : e.getAnnotation(Model.class); jaroslav@930: if (m != null) { jaroslav@930: ret = findPkgName(e) + '.' + m.className(); jaroslav@930: isModel[0] = true; jaroslav@930: models.put(e, m.className()); jaroslav@930: } else { jaroslav@930: ret = tm.toString(); jaroslav@930: } jaroslav@930: TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType(); jaroslav@930: enm = processingEnv.getTypeUtils().erasure(enm); jaroslav@930: isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm); jaroslav@930: } jaroslav@930: return ret; jaroslav@930: } jaroslav@26: }