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@961: import java.lang.annotation.AnnotationTypeMismatchException; jaroslav@1001: import java.lang.annotation.IncompleteAnnotationException; jaroslav@961: import java.lang.reflect.Method; 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@949: import java.util.HashSet; 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@961: import javax.annotation.processing.ProcessingEnvironment; 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@961: import javax.lang.model.element.AnnotationValue; 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@936: import javax.lang.model.type.ArrayType; 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@949: import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange; jaroslav@934: import org.apidesign.bck2brwsr.htmlpage.api.OnReceive; 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@934: "org.apidesign.bck2brwsr.htmlpage.api.OnReceive", jaroslav@949: "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange", jaroslav@961: "org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty", 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@961: private final Map verify = 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@961: for (Map.Entry entry : verify.entrySet()) { jaroslav@961: TypeElement te = (TypeElement)entry.getKey(); jaroslav@961: String fqn = processingEnv.getElementUtils().getBinaryName(te).toString(); jaroslav@961: Element finalElem = processingEnv.getElementUtils().getTypeElement(fqn); jaroslav@961: if (finalElem == null) { jaroslav@961: continue; jaroslav@961: } jaroslav@961: Prprt[] props; jaroslav@961: Model m = finalElem.getAnnotation(Model.class); jaroslav@961: if (m != null) { jaroslav@961: props = Prprt.wrap(processingEnv, finalElem, m.properties()); jaroslav@961: } else { jaroslav@961: Page p = finalElem.getAnnotation(Page.class); jaroslav@961: props = Prprt.wrap(processingEnv, finalElem, p.properties()); jaroslav@961: } jaroslav@961: for (Prprt p : props) { jaroslav@961: boolean[] isModel = { false }; jaroslav@961: boolean[] isEnum = { false }; jaroslav@961: boolean[] isPrimitive = { false }; jaroslav@961: String t = checkType(p, isModel, isEnum, isPrimitive); jaroslav@961: if (isEnum[0]) { jaroslav@961: continue; jaroslav@961: } jaroslav@961: if (isPrimitive[0]) { jaroslav@961: continue; jaroslav@961: } jaroslav@961: if (isModel[0]) { jaroslav@961: continue; jaroslav@961: } jaroslav@961: if ("java.lang.String".equals(t)) { jaroslav@961: continue; jaroslav@961: } jaroslav@961: error("The type " + t + " should be defined by @Model annotation", entry.getKey()); jaroslav@961: } jaroslav@961: } jaroslav@961: verify.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@934: jaroslav@961: private void error(String msg, Element e) { jaroslav@961: processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, e); jaroslav@934: } 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@941: models.put(e, 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@949: Map> functionDeps = new HashMap<>(); jaroslav@961: Prprt[] props = createProps(e, m.properties()); jaroslav@961: jaroslav@961: if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@961: if (!generateOnChange(e, propsDeps, props, className, functionDeps)) { jaroslav@949: ok = false; jaroslav@949: } jaroslav@961: if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) { 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 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@934: w.append(" intKnckt();\n"); jaroslav@934: w.append(" };\n"); jaroslav@934: w.append(" private void intKnckt() {\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@934: w.append(" ").append(className).append("(Object json) {\n"); jaroslav@934: int values = 0; jaroslav@934: for (int i = 0; i < propsGetSet.size(); i += 4) { jaroslav@961: Prprt p = findPrprt(props, propsGetSet.get(i)); jaroslav@943: if (p == null) { jaroslav@934: continue; jaroslav@934: } jaroslav@934: values++; jaroslav@934: } jaroslav@934: w.append(" Object[] ret = new Object[" + values + "];\n"); jaroslav@934: w.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n"); jaroslav@934: for (int i = 0; i < propsGetSet.size(); i += 4) { jaroslav@961: Prprt p = findPrprt(props, propsGetSet.get(i)); jaroslav@943: if (p == null) { jaroslav@934: continue; jaroslav@934: } jaroslav@934: w.append(" \"").append(propsGetSet.get(i)).append("\",\n"); jaroslav@934: } jaroslav@934: w.append(" }, ret);\n"); jaroslav@934: for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) { jaroslav@943: final String pn = propsGetSet.get(i); jaroslav@961: Prprt p = findPrprt(props, pn); jaroslav@943: if (p == null) { jaroslav@934: continue; jaroslav@934: } jaroslav@934: boolean[] isModel = { false }; jaroslav@934: boolean[] isEnum = { false }; jaroslav@961: boolean isPrimitive[] = { false }; jaroslav@961: String type = checkType(props[prop++], isModel, isEnum, isPrimitive); jaroslav@960: if (p.array()) { jaroslav@943: w.append("if (ret[" + cnt + "] instanceof Object[]) {\n"); jaroslav@943: w.append(" for (Object e : ((Object[])ret[" + cnt + "])) {\n"); jaroslav@943: if (isModel[0]) { jaroslav@943: w.append(" this.prop_").append(pn).append(".add(new "); jaroslav@943: w.append(type).append("(e));\n"); jaroslav@960: } else if (isEnum[0]) { jaroslav@960: w.append(" this.prop_").append(pn); jaroslav@960: w.append(".add("); jaroslav@960: w.append(type).append(".valueOf((String)e));\n"); jaroslav@943: } else { jaroslav@944: if (isPrimitive(type)) { jaroslav@944: w.append(" this.prop_").append(pn).append(".add(((Number)e)."); jaroslav@944: w.append(type).append("Value());\n"); jaroslav@944: } else { jaroslav@944: w.append(" this.prop_").append(pn).append(".add(("); jaroslav@944: w.append(type).append(")e);\n"); jaroslav@944: } jaroslav@943: } jaroslav@943: w.append(" }\n"); jaroslav@943: w.append("}\n"); jaroslav@934: } else { jaroslav@960: if (isEnum[0]) { jaroslav@960: w.append(" this.prop_").append(pn); jaroslav@960: w.append(" = "); jaroslav@960: w.append(type).append(".valueOf((String)ret[" + cnt + "]);\n"); jaroslav@960: } else if (isPrimitive(type)) { jaroslav@947: w.append(" this.prop_").append(pn); jaroslav@947: w.append(" = ((Number)").append("ret[" + cnt + "])."); jaroslav@947: w.append(type).append("Value();\n"); jaroslav@947: } else { jaroslav@947: w.append(" this.prop_").append(pn); jaroslav@947: w.append(" = (").append(type).append(')'); jaroslav@947: w.append("ret[" + cnt + "];\n"); jaroslav@947: } jaroslav@934: } jaroslav@943: cnt++; jaroslav@934: } jaroslav@934: w.append(" intKnckt();\n"); jaroslav@934: w.append(" };\n"); jaroslav@961: writeToString(props, w); jaroslav@961: writeClone(className, props, w); jaroslav@770: w.append("}\n"); jaroslav@770: } finally { jaroslav@770: w.close(); jaroslav@770: } jaroslav@770: } catch (IOException ex) { jaroslav@961: 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@961: 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@949: Map> functionDeps = new HashMap<>(); jaroslav@961: jaroslav@961: Prprt[] props = createProps(e, p.properties()); jaroslav@961: if (!generateComputedProperties(body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@961: if (!generateOnChange(e, propsDeps, props, className, functionDeps)) { jaroslav@949: ok = false; jaroslav@949: } jaroslav@961: if (!generateProperties(e, body, props, propsGetSet, propsDeps, functionDeps)) { jaroslav@770: ok = false; jaroslav@770: } jaroslav@879: if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) { jaroslav@879: ok = false; jaroslav@879: } jaroslav@934: if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) { jaroslav@934: ok = false; jaroslav@934: } 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@961: 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@961: error("id = " + id + " not found in HTML page.", method); jaroslav@765: ok = false; jaroslav@765: continue; jaroslav@765: } jaroslav@124: if (pp.tagNameForId(id) == null) { jaroslav@961: 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@961: 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@961: 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@961: Writer w, Prprt[] properties, jaroslav@949: Collection props, jaroslav@949: Map> deps, jaroslav@949: Map> functionDeps jaroslav@492: ) throws IOException { jaroslav@770: boolean ok = true; jaroslav@961: for (Prprt 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@950: 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@950: w.write(")"); jaroslav@950: jaroslav@950: dependants = functionDeps.get(p.name()); jaroslav@950: if (dependants != null) { jaroslav@950: w.write(".onChange(new Runnable() { public void run() {\n"); jaroslav@950: for (String call : dependants) { jaroslav@950: w.append(call); jaroslav@950: } jaroslav@950: w.write("}})"); jaroslav@950: } jaroslav@950: w.write(";\n"); jaroslav@950: 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@949: 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@949: dependants = functionDeps.get(p.name()); jaroslav@949: if (dependants != null) { jaroslav@949: for (String call : dependants) { jaroslav@949: w.append(call); jaroslav@949: } jaroslav@949: } 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@961: Writer w, Prprt[] 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@956: w.write(" return " + fqn(ee.getEnclosingElement().asType(), ee) + '.' + 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@961: private String typeName(Element where, Prprt p) { jaroslav@764: String ret; jaroslav@930: boolean[] isModel = { false }; jaroslav@930: boolean[] isEnum = { false }; jaroslav@961: boolean isPrimitive[] = { false }; jaroslav@961: ret = checkType(p, isModel, isEnum, isPrimitive); 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@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@961: private boolean verifyPropName(Element e, String propName, Prprt[] existingProps) { jaroslav@765: StringBuilder sb = new StringBuilder(); jaroslav@765: String sep = ""; jaroslav@961: for (Prprt Prprt : existingProps) { jaroslav@961: if (Prprt.name().equals(propName)) { jaroslav@765: return true; jaroslav@765: } jaroslav@765: sb.append(sep); jaroslav@765: sb.append('"'); jaroslav@961: sb.append(Prprt.name()); jaroslav@765: sb.append('"'); jaroslav@765: sep = ", "; jaroslav@765: } jaroslav@961: 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@961: error("@OnFunction method needs to be static", e); jaroslav@879: return false; jaroslav@879: } jaroslav@879: if (e.getModifiers().contains(Modifier.PRIVATE)) { jaroslav@961: error("@OnFunction method cannot be private", e); jaroslav@879: return false; jaroslav@879: } jaroslav@879: if (e.getReturnType().getKind() != TypeKind.VOID) { jaroslav@961: error("@OnFunction method should return void", e); 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@949: private boolean generateOnChange(Element clazz, Map> propDeps, jaroslav@961: Prprt[] properties, String className, jaroslav@949: Map> functionDeps jaroslav@949: ) { jaroslav@949: for (Element m : clazz.getEnclosedElements()) { jaroslav@949: if (m.getKind() != ElementKind.METHOD) { jaroslav@949: continue; jaroslav@949: } jaroslav@949: ExecutableElement e = (ExecutableElement) m; jaroslav@949: OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class); jaroslav@949: if (onPC == null) { jaroslav@949: continue; jaroslav@949: } jaroslav@949: for (String pn : onPC.value()) { jaroslav@961: if (findPrprt(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) { jaroslav@961: error("No Prprt named '" + pn + "' in the model", clazz); jaroslav@949: return false; jaroslav@949: } jaroslav@949: } jaroslav@949: if (!e.getModifiers().contains(Modifier.STATIC)) { jaroslav@961: error("@OnPrprtChange method needs to be static", e); jaroslav@949: return false; jaroslav@949: } jaroslav@949: if (e.getModifiers().contains(Modifier.PRIVATE)) { jaroslav@961: error("@OnPrprtChange method cannot be private", e); jaroslav@949: return false; jaroslav@949: } jaroslav@949: if (e.getReturnType().getKind() != TypeKind.VOID) { jaroslav@961: error("@OnPrprtChange method should return void", e); jaroslav@949: return false; jaroslav@949: } jaroslav@949: String n = e.getSimpleName().toString(); jaroslav@949: jaroslav@949: jaroslav@949: for (String pn : onPC.value()) { jaroslav@949: StringBuilder call = new StringBuilder(); jaroslav@949: call.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); jaroslav@949: call.append(wrapPropName(e, className, "name", pn)); jaroslav@949: call.append(");\n"); jaroslav@949: jaroslav@949: Collection change = functionDeps.get(pn); jaroslav@949: if (change == null) { jaroslav@949: change = new ArrayList<>(); jaroslav@949: functionDeps.put(pn, change); jaroslav@949: } jaroslav@949: change.add(call.toString()); jaroslav@949: for (String dpn : findDerivedFrom(propDeps, pn)) { jaroslav@949: change = functionDeps.get(dpn); jaroslav@949: if (change == null) { jaroslav@949: change = new ArrayList<>(); jaroslav@949: functionDeps.put(dpn, change); jaroslav@949: } jaroslav@949: change.add(call.toString()); jaroslav@949: } jaroslav@949: } jaroslav@949: } jaroslav@949: return true; jaroslav@949: } jaroslav@949: jaroslav@934: private boolean generateReceive( jaroslav@934: Element clazz, StringWriter body, String className, jaroslav@934: List enclosedElements, List functions jaroslav@934: ) { jaroslav@934: for (Element m : enclosedElements) { jaroslav@934: if (m.getKind() != ElementKind.METHOD) { jaroslav@934: continue; jaroslav@934: } jaroslav@934: ExecutableElement e = (ExecutableElement)m; jaroslav@934: OnReceive onR = e.getAnnotation(OnReceive.class); jaroslav@934: if (onR == null) { jaroslav@934: continue; jaroslav@934: } jaroslav@934: if (!e.getModifiers().contains(Modifier.STATIC)) { jaroslav@961: error("@OnReceive method needs to be static", e); jaroslav@934: return false; jaroslav@934: } jaroslav@934: if (e.getModifiers().contains(Modifier.PRIVATE)) { jaroslav@961: error("@OnReceive method cannot be private", e); jaroslav@934: return false; jaroslav@934: } jaroslav@934: if (e.getReturnType().getKind() != TypeKind.VOID) { jaroslav@961: error("@OnReceive method should return void", e); jaroslav@934: return false; jaroslav@934: } jaroslav@934: String modelClass = null; jaroslav@936: boolean expectsList = false; jaroslav@934: List args = new ArrayList<>(); jaroslav@934: { jaroslav@934: for (VariableElement ve : e.getParameters()) { jaroslav@936: TypeMirror modelType = null; jaroslav@934: if (ve.asType().toString().equals(className)) { jaroslav@934: args.add(className + ".this"); jaroslav@934: } else if (isModel(ve.asType())) { jaroslav@936: modelType = ve.asType(); jaroslav@936: } else if (ve.asType().getKind() == TypeKind.ARRAY) { jaroslav@936: modelType = ((ArrayType)ve.asType()).getComponentType(); jaroslav@936: expectsList = true; jaroslav@936: } jaroslav@936: if (modelType != null) { jaroslav@934: if (modelClass != null) { jaroslav@961: error("There can be only one model class among arguments", e); jaroslav@934: } else { jaroslav@936: modelClass = modelType.toString(); jaroslav@936: if (expectsList) { jaroslav@936: args.add("arr"); jaroslav@936: } else { jaroslav@936: args.add("arr[0]"); jaroslav@936: } jaroslav@934: } jaroslav@934: } jaroslav@934: } jaroslav@934: } jaroslav@936: if (modelClass == null) { jaroslav@961: error("The method needs to have one @Model class as parameter", e); jaroslav@936: } jaroslav@934: String n = e.getSimpleName().toString(); jaroslav@934: body.append("public void ").append(n).append("("); jaroslav@934: StringBuilder assembleURL = new StringBuilder(); jaroslav@954: String jsonpVarName = null; jaroslav@934: { jaroslav@934: String sep = ""; jaroslav@954: boolean skipJSONP = onR.jsonp().isEmpty(); jaroslav@934: for (String p : findParamNames(e, onR.url(), assembleURL)) { jaroslav@954: if (!skipJSONP && p.equals(onR.jsonp())) { jaroslav@954: skipJSONP = true; jaroslav@954: jsonpVarName = p; jaroslav@954: continue; jaroslav@954: } jaroslav@934: body.append(sep); jaroslav@934: body.append("String ").append(p); jaroslav@934: sep = ", "; jaroslav@934: } jaroslav@954: if (!skipJSONP) { jaroslav@961: error( jaroslav@954: "Name of jsonp attribute ('" + onR.jsonp() + jaroslav@961: "') is not used in url attribute '" + onR.url() + "'", e jaroslav@954: ); jaroslav@954: } jaroslav@934: } jaroslav@934: body.append(") {\n"); jaroslav@934: body.append(" final Object[] result = { null };\n"); jaroslav@934: body.append( jaroslav@934: " class ProcessResult implements Runnable {\n" + jaroslav@934: " @Override\n" + jaroslav@934: " public void run() {\n" + jaroslav@936: " Object value = result[0];\n"); jaroslav@936: body.append( jaroslav@936: " " + modelClass + "[] arr;\n"); jaroslav@936: body.append( jaroslav@934: " if (value instanceof Object[]) {\n" + jaroslav@936: " Object[] data = ((Object[])value);\n" + jaroslav@936: " arr = new " + modelClass + "[data.length];\n" + jaroslav@936: " for (int i = 0; i < data.length; i++) {\n" + jaroslav@941: " arr[i] = new " + modelClass + "(data[i]);\n" + jaroslav@936: " }\n" + jaroslav@936: " } else {\n" + jaroslav@936: " arr = new " + modelClass + "[1];\n" + jaroslav@936: " arr[0] = new " + modelClass + "(value);\n" + jaroslav@936: " }\n" jaroslav@936: ); jaroslav@934: { jaroslav@934: body.append(clazz.getSimpleName()).append(".").append(n).append("("); jaroslav@934: String sep = ""; jaroslav@934: for (String arg : args) { jaroslav@934: body.append(sep); jaroslav@934: body.append(arg); jaroslav@934: sep = ", "; jaroslav@934: } jaroslav@934: body.append(");\n"); jaroslav@934: } jaroslav@934: body.append( jaroslav@934: " }\n" + jaroslav@934: " }\n" jaroslav@934: ); jaroslav@954: body.append(" ProcessResult pr = new ProcessResult();\n"); jaroslav@954: if (jsonpVarName != null) { jaroslav@954: body.append(" String ").append(jsonpVarName). jaroslav@954: append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n"); jaroslav@954: } jaroslav@934: body.append(" org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n "); jaroslav@934: body.append(assembleURL); jaroslav@954: body.append(", result, pr, ").append(jsonpVarName).append("\n );\n"); jaroslav@934: // body.append(" ").append(clazz.getSimpleName()).append(".").append(n).append("("); jaroslav@934: // body.append(wrapParams(e, null, className, "ev", "data")); jaroslav@934: // body.append(");\n"); jaroslav@934: body.append("}\n"); jaroslav@934: } jaroslav@934: return true; jaroslav@934: } jaroslav@934: 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@949: if (evName == null) { jaroslav@949: final StringBuilder sb = new StringBuilder(); jaroslav@949: sb.append("Unexpected string parameter name."); jaroslav@949: if (dataName != null) { jaroslav@949: sb.append(" Try \"").append(dataName).append("\""); jaroslav@949: } jaroslav@961: error(sb.toString(), ee); jaroslav@949: } 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@961: 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@949: jaroslav@949: private CharSequence wrapPropName( jaroslav@949: ExecutableElement ee, String className, String propName, String propValue jaroslav@949: ) { jaroslav@949: TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType(); jaroslav@949: StringBuilder params = new StringBuilder(); jaroslav@949: boolean first = true; jaroslav@949: for (VariableElement ve : ee.getParameters()) { jaroslav@949: if (!first) { jaroslav@949: params.append(", "); jaroslav@949: } jaroslav@949: first = false; jaroslav@949: if (ve.asType() == stringType) { jaroslav@949: if (propName != null && ve.getSimpleName().contentEquals(propName)) { jaroslav@949: params.append('"').append(propValue).append('"'); jaroslav@949: } else { jaroslav@961: error("Unexpected string parameter name. Try \"" + propName + "\".", ee); jaroslav@949: } jaroslav@949: continue; jaroslav@949: } jaroslav@949: String rn = fqn(ve.asType(), ee); jaroslav@949: int last = rn.lastIndexOf('.'); jaroslav@949: if (last >= 0) { jaroslav@949: rn = rn.substring(last + 1); jaroslav@949: } jaroslav@949: if (rn.equals(className)) { jaroslav@949: params.append(className).append(".this"); jaroslav@949: continue; jaroslav@949: } jaroslav@961: error( jaroslav@961: "@OnPrprtChange method can only accept String or " + className + " arguments", jaroslav@949: ee); jaroslav@949: } jaroslav@949: return params; jaroslav@949: } jaroslav@949: 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@961: private void writeToString(Prprt[] 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@961: for (Prprt 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@961: private void writeClone(String className, Prprt[] props, Writer w) throws IOException { jaroslav@930: w.write(" public " + className + " clone() {\n"); jaroslav@930: w.write(" " + className + " ret = new " + className + "();\n"); jaroslav@961: for (Prprt p : props) { jaroslav@930: if (!p.array()) { jaroslav@930: boolean isModel[] = { false }; jaroslav@930: boolean isEnum[] = { false }; jaroslav@961: boolean isPrimitive[] = { false }; jaroslav@961: checkType(p, isModel, isEnum, isPrimitive); jaroslav@930: if (!isModel[0]) { jaroslav@930: w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ";\n"); jaroslav@930: continue; jaroslav@930: } jaroslav@955: w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n"); jaroslav@955: } else { jaroslav@955: w.write(" ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n"); jaroslav@930: } 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@961: private String checkType(Prprt p, boolean[] isModel, boolean[] isEnum, boolean[] isPrimitive) { jaroslav@961: TypeMirror tm; jaroslav@961: try { jaroslav@961: String ret = p.typeName(processingEnv); jaroslav@961: TypeElement e = processingEnv.getElementUtils().getTypeElement(ret); jaroslav@961: if (e == null) { jaroslav@961: isModel[0] = true; jaroslav@961: isEnum[0] = false; jaroslav@961: isPrimitive[0] = false; jaroslav@961: return ret; jaroslav@961: } jaroslav@961: tm = e.asType(); jaroslav@961: } catch (MirroredTypeException ex) { jaroslav@961: tm = ex.getTypeMirror(); jaroslav@961: } jaroslav@961: tm = processingEnv.getTypeUtils().erasure(tm); jaroslav@961: isPrimitive[0] = tm.getKind().isPrimitive(); jaroslav@961: final Element e = processingEnv.getTypeUtils().asElement(tm); jaroslav@961: final Model m = e == null ? null : e.getAnnotation(Model.class); jaroslav@961: jaroslav@930: String ret; jaroslav@961: if (m != null) { jaroslav@961: ret = findPkgName(e) + '.' + m.className(); jaroslav@961: isModel[0] = true; jaroslav@961: models.put(e, m.className()); jaroslav@961: } else if (findModelForMthd(e)) { jaroslav@961: ret = ((TypeElement)e).getQualifiedName().toString(); jaroslav@961: isModel[0] = true; jaroslav@961: } else { jaroslav@961: ret = tm.toString(); jaroslav@961: } jaroslav@961: TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType(); jaroslav@961: enm = processingEnv.getTypeUtils().erasure(enm); jaroslav@961: isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm); jaroslav@961: return ret; jaroslav@961: } jaroslav@961: jaroslav@961: private static boolean findModelForMthd(Element clazz) { jaroslav@961: if (clazz == null) { jaroslav@961: return false; jaroslav@961: } jaroslav@961: for (Element e : clazz.getEnclosedElements()) { jaroslav@961: if (e.getKind() == ElementKind.METHOD) { jaroslav@961: ExecutableElement ee = (ExecutableElement)e; jaroslav@961: if ( jaroslav@961: ee.getSimpleName().contentEquals("modelFor") && jaroslav@961: ee.getParameters().isEmpty() jaroslav@961: ) { jaroslav@961: return true; jaroslav@961: } jaroslav@930: } jaroslav@930: } jaroslav@961: return false; jaroslav@930: } jaroslav@934: jaroslav@934: private Iterable findParamNames(Element e, String url, StringBuilder assembleURL) { jaroslav@934: List params = new ArrayList<>(); jaroslav@934: jaroslav@934: for (int pos = 0; ;) { jaroslav@934: int next = url.indexOf('{', pos); jaroslav@934: if (next == -1) { jaroslav@934: assembleURL.append('"') jaroslav@934: .append(url.substring(pos)) jaroslav@934: .append('"'); jaroslav@934: return params; jaroslav@934: } jaroslav@934: int close = url.indexOf('}', next); jaroslav@934: if (close == -1) { jaroslav@961: error("Unbalanced '{' and '}' in " + url, e); jaroslav@934: return params; jaroslav@934: } jaroslav@934: final String paramName = url.substring(next + 1, close); jaroslav@934: params.add(paramName); jaroslav@934: assembleURL.append('"') jaroslav@934: .append(url.substring(pos, next)) jaroslav@934: .append("\" + ").append(paramName).append(" + "); jaroslav@934: pos = close + 1; jaroslav@934: } jaroslav@934: } jaroslav@943: jaroslav@961: private static Prprt findPrprt(Prprt[] properties, String propName) { jaroslav@961: for (Prprt p : properties) { jaroslav@943: if (propName.equals(p.name())) { jaroslav@943: return p; jaroslav@943: } jaroslav@943: } jaroslav@943: return null; jaroslav@943: } jaroslav@944: jaroslav@944: private boolean isPrimitive(String type) { jaroslav@944: return jaroslav@944: "int".equals(type) || jaroslav@944: "double".equals(type) || jaroslav@944: "long".equals(type) || jaroslav@944: "short".equals(type) || jaroslav@944: "byte".equals(type) || jaroslav@944: "float".equals(type); jaroslav@944: } jaroslav@949: jaroslav@949: private static Collection findDerivedFrom(Map> propsDeps, String derivedProp) { jaroslav@949: Set names = new HashSet<>(); jaroslav@949: for (Map.Entry> e : propsDeps.entrySet()) { jaroslav@949: if (e.getValue().contains(derivedProp)) { jaroslav@949: names.add(e.getKey()); jaroslav@949: } jaroslav@949: } jaroslav@949: return names; jaroslav@949: } jaroslav@961: jaroslav@961: private Prprt[] createProps(Element e, Property[] arr) { jaroslav@961: Prprt[] ret = Prprt.wrap(processingEnv, e, arr); jaroslav@961: Prprt[] prev = verify.put(e, ret); jaroslav@961: if (prev != null) { jaroslav@961: error("Two sets of properties for ", e); jaroslav@961: } jaroslav@961: return ret; jaroslav@961: } jaroslav@961: jaroslav@961: private static class Prprt { jaroslav@961: private final Element e; jaroslav@961: private final AnnotationMirror tm; jaroslav@961: private final Property p; jaroslav@961: jaroslav@961: public Prprt(Element e, AnnotationMirror tm, Property p) { jaroslav@961: this.e = e; jaroslav@961: this.tm = tm; jaroslav@961: this.p = p; jaroslav@961: } jaroslav@961: jaroslav@961: String name() { jaroslav@961: return p.name(); jaroslav@961: } jaroslav@961: jaroslav@961: boolean array() { jaroslav@961: return p.array(); jaroslav@961: } jaroslav@961: jaroslav@961: String typeName(ProcessingEnvironment env) { jaroslav@961: try { jaroslav@961: return p.type().getName(); jaroslav@1001: } catch (IncompleteAnnotationException | AnnotationTypeMismatchException ex) { jaroslav@961: for (Object v : getAnnoValues(env)) { jaroslav@961: String s = v.toString().replace(" ", ""); jaroslav@961: if (s.startsWith("type=") && s.endsWith(".class")) { jaroslav@961: return s.substring(5, s.length() - 6); jaroslav@961: } jaroslav@961: } jaroslav@961: throw ex; jaroslav@961: } jaroslav@961: } jaroslav@961: jaroslav@961: jaroslav@961: static Prprt[] wrap(ProcessingEnvironment pe, Element e, Property[] arr) { jaroslav@961: if (arr.length == 0) { jaroslav@961: return new Prprt[0]; jaroslav@961: } jaroslav@961: jaroslav@961: if (e.getKind() != ElementKind.CLASS) { jaroslav@961: throw new IllegalStateException("" + e.getKind()); jaroslav@961: } jaroslav@961: TypeElement te = (TypeElement)e; jaroslav@961: List val = null; jaroslav@961: for (AnnotationMirror an : te.getAnnotationMirrors()) { jaroslav@961: for (Map.Entry entry : an.getElementValues().entrySet()) { jaroslav@961: if (entry.getKey().getSimpleName().contentEquals("properties")) { jaroslav@961: val = (List)entry.getValue().getValue(); jaroslav@961: break; jaroslav@961: } jaroslav@961: } jaroslav@961: } jaroslav@961: if (val == null || val.size() != arr.length) { jaroslav@961: pe.getMessager().printMessage(Diagnostic.Kind.ERROR, "" + val, e); jaroslav@961: return new Prprt[0]; jaroslav@961: } jaroslav@961: Prprt[] ret = new Prprt[arr.length]; jaroslav@961: BIG: for (int i = 0; i < ret.length; i++) { jaroslav@961: AnnotationMirror am = (AnnotationMirror)val.get(i).getValue(); jaroslav@961: ret[i] = new Prprt(e, am, arr[i]); jaroslav@961: jaroslav@961: } jaroslav@961: return ret; jaroslav@961: } jaroslav@961: jaroslav@961: private List getAnnoValues(ProcessingEnvironment pe) { jaroslav@961: try { jaroslav@961: Class trees = Class.forName("com.sun.tools.javac.api.JavacTrees"); jaroslav@961: Method m = trees.getMethod("instance", ProcessingEnvironment.class); jaroslav@961: Object instance = m.invoke(null, pe); jaroslav@961: m = instance.getClass().getMethod("getPath", Element.class, AnnotationMirror.class); jaroslav@961: Object path = m.invoke(instance, e, tm); jaroslav@961: m = path.getClass().getMethod("getLeaf"); jaroslav@961: Object leaf = m.invoke(path); jaroslav@961: m = leaf.getClass().getMethod("getArguments"); jaroslav@961: return (List)m.invoke(leaf); jaroslav@961: } catch (Exception ex) { jaroslav@961: return Collections.emptyList(); jaroslav@961: } jaroslav@961: } jaroslav@961: } jaroslav@961: jaroslav@26: }