javaquery/api/src/main/java/org/apidesign/bck2brwsr/htmlpage/PageProcessor.java
author Jaroslav Tulach <jaroslav.tulach@apidesign.org>
Mon, 08 Apr 2013 16:51:30 +0200
branchmodel
changeset 954 6448c284fe21
parent 950 445d5f1d4177
child 955 dad881565d0a
permissions -rw-r--r--
Support for JSONP
     1 /**
     2  * Back 2 Browser Bytecode Translator
     3  * Copyright (C) 2012 Jaroslav Tulach <jaroslav.tulach@apidesign.org>
     4  *
     5  * This program is free software: you can redistribute it and/or modify
     6  * it under the terms of the GNU General Public License as published by
     7  * the Free Software Foundation, version 2 of the License.
     8  *
     9  * This program is distributed in the hope that it will be useful,
    10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  * GNU General Public License for more details.
    13  *
    14  * You should have received a copy of the GNU General Public License
    15  * along with this program. Look for COPYING file in the top folder.
    16  * If not, see http://opensource.org/licenses/GPL-2.0.
    17  */
    18 package org.apidesign.bck2brwsr.htmlpage;
    19 
    20 import java.io.IOException;
    21 import java.io.InputStream;
    22 import java.io.OutputStreamWriter;
    23 import java.io.StringWriter;
    24 import java.io.Writer;
    25 import java.util.ArrayList;
    26 import java.util.Collection;
    27 import java.util.Collections;
    28 import java.util.HashMap;
    29 import java.util.HashSet;
    30 import java.util.LinkedHashSet;
    31 import java.util.List;
    32 import java.util.Map;
    33 import java.util.Set;
    34 import java.util.WeakHashMap;
    35 import javax.annotation.processing.AbstractProcessor;
    36 import javax.annotation.processing.Completion;
    37 import javax.annotation.processing.Completions;
    38 import javax.annotation.processing.Messager;
    39 import javax.annotation.processing.Processor;
    40 import javax.annotation.processing.RoundEnvironment;
    41 import javax.annotation.processing.SupportedAnnotationTypes;
    42 import javax.lang.model.element.AnnotationMirror;
    43 import javax.lang.model.element.Element;
    44 import javax.lang.model.element.ElementKind;
    45 import javax.lang.model.element.ExecutableElement;
    46 import javax.lang.model.element.Modifier;
    47 import javax.lang.model.element.PackageElement;
    48 import javax.lang.model.element.TypeElement;
    49 import javax.lang.model.element.VariableElement;
    50 import javax.lang.model.type.ArrayType;
    51 import javax.lang.model.type.DeclaredType;
    52 import javax.lang.model.type.MirroredTypeException;
    53 import javax.lang.model.type.TypeKind;
    54 import javax.lang.model.type.TypeMirror;
    55 import javax.lang.model.util.Elements;
    56 import javax.lang.model.util.Types;
    57 import javax.tools.Diagnostic;
    58 import javax.tools.FileObject;
    59 import javax.tools.StandardLocation;
    60 import org.apidesign.bck2brwsr.htmlpage.api.ComputedProperty;
    61 import org.apidesign.bck2brwsr.htmlpage.api.Model;
    62 import org.apidesign.bck2brwsr.htmlpage.api.On;
    63 import org.apidesign.bck2brwsr.htmlpage.api.OnFunction;
    64 import org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange;
    65 import org.apidesign.bck2brwsr.htmlpage.api.OnReceive;
    66 import org.apidesign.bck2brwsr.htmlpage.api.Page;
    67 import org.apidesign.bck2brwsr.htmlpage.api.Property;
    68 import org.openide.util.lookup.ServiceProvider;
    69 
    70 /** Annotation processor to process an XHTML page and generate appropriate 
    71  * "id" file.
    72  *
    73  * @author Jaroslav Tulach <jtulach@netbeans.org>
    74  */
    75 @ServiceProvider(service=Processor.class)
    76 @SupportedAnnotationTypes({
    77     "org.apidesign.bck2brwsr.htmlpage.api.Model",
    78     "org.apidesign.bck2brwsr.htmlpage.api.Page",
    79     "org.apidesign.bck2brwsr.htmlpage.api.OnFunction",
    80     "org.apidesign.bck2brwsr.htmlpage.api.OnReceive",
    81     "org.apidesign.bck2brwsr.htmlpage.api.OnPropertyChange",
    82     "org.apidesign.bck2brwsr.htmlpage.api.On"
    83 })
    84 public final class PageProcessor extends AbstractProcessor {
    85     private final Map<Element,String> models = new WeakHashMap<>();
    86     @Override
    87     public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
    88         boolean ok = true;
    89         for (Element e : roundEnv.getElementsAnnotatedWith(Model.class)) {
    90             if (!processModel(e)) {
    91                 ok = false;
    92             }
    93         }
    94         for (Element e : roundEnv.getElementsAnnotatedWith(Page.class)) {
    95             if (!processPage(e)) {
    96                 ok = false;
    97             }
    98         }
    99         if (roundEnv.processingOver()) {
   100             models.clear();
   101         }
   102         return ok;
   103     }
   104 
   105     private InputStream openStream(String pkg, String name) throws IOException {
   106         try {
   107             FileObject fo = processingEnv.getFiler().getResource(
   108                 StandardLocation.SOURCE_PATH, pkg, name);
   109             return fo.openInputStream();
   110         } catch (IOException ex) {
   111             return processingEnv.getFiler().getResource(StandardLocation.CLASS_OUTPUT, pkg, name).openInputStream();
   112         }
   113     }
   114 
   115     private  Messager err() {
   116         return processingEnv.getMessager();
   117     }
   118     
   119     private boolean processModel(Element e) {
   120         boolean ok = true;
   121         Model m = e.getAnnotation(Model.class);
   122         if (m == null) {
   123             return true;
   124         }
   125         String pkg = findPkgName(e);
   126         Writer w;
   127         String className = m.className();
   128         models.put(e, className);
   129         try {
   130             StringWriter body = new StringWriter();
   131             List<String> propsGetSet = new ArrayList<>();
   132             List<String> functions = new ArrayList<>();
   133             Map<String, Collection<String>> propsDeps = new HashMap<>();
   134             Map<String, Collection<String>> functionDeps = new HashMap<>();
   135             if (!generateComputedProperties(body, m.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   136                 ok = false;
   137             }
   138             if (!generateOnChange(e, propsDeps, m.properties(), className, functionDeps)) {
   139                 ok = false;
   140             }
   141             if (!generateProperties(e, body, m.properties(), propsGetSet, propsDeps, functionDeps)) {
   142                 ok = false;
   143             }
   144             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   145                 ok = false;
   146             }
   147             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   148             w = new OutputStreamWriter(java.openOutputStream());
   149             try {
   150                 w.append("package " + pkg + ";\n");
   151                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   152                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   153                 w.append("import org.apidesign.bck2brwsr.core.JavaScriptOnly;\n");
   154                 w.append("final class ").append(className).append(" implements Cloneable {\n");
   155                 w.append("  private boolean locked;\n");
   156                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   157                 w.append(body.toString());
   158                 w.append("  private static Class<" + inPckName(e) + "> modelFor() { return null; }\n");
   159                 w.append("  public ").append(className).append("() {\n");
   160                 w.append("    intKnckt();\n");
   161                 w.append("  };\n");
   162                 w.append("  private void intKnckt() {\n");
   163                 w.append("    ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(this, ");
   164                 writeStringArray(propsGetSet, w);
   165                 w.append(", ");
   166                 writeStringArray(functions, w);
   167                 w.append("    );\n");
   168                 w.append("  };\n");
   169                 w.append("  ").append(className).append("(Object json) {\n");
   170                 int values = 0;
   171                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   172                     Property p = findProperty(m.properties(), propsGetSet.get(i));
   173                     if (p == null) {
   174                         continue;
   175                     }
   176                     values++;
   177                 }
   178                 w.append("    Object[] ret = new Object[" + values + "];\n");
   179                 w.append("    org.apidesign.bck2brwsr.htmlpage.ConvertTypes.extractJSON(json, new String[] {\n");
   180                 for (int i = 0; i < propsGetSet.size(); i += 4) {
   181                     Property p = findProperty(m.properties(), propsGetSet.get(i));
   182                     if (p == null) {
   183                         continue;
   184                     }
   185                     w.append("      \"").append(propsGetSet.get(i)).append("\",\n");
   186                 }
   187                 w.append("    }, ret);\n");
   188                 for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i += 4) {
   189                     final String pn = propsGetSet.get(i);
   190                     Property p = findProperty(m.properties(), pn);
   191                     if (p == null) {
   192                         continue;
   193                     }
   194                     boolean[] isModel = { false };
   195                     boolean[] isEnum = { false };
   196                     String type = checkType(m.properties()[prop++], isModel, isEnum);
   197                     if (isEnum[0]) {
   198 //                        w.append(type).append(".valueOf((String)");
   199 //                        close = true;
   200                         w.append("    this.prop_").append(pn);
   201                         w.append(" = null;\n");
   202                     } else if (p.array()) {
   203                         w.append("if (ret[" + cnt + "] instanceof Object[]) {\n");
   204                         w.append("  for (Object e : ((Object[])ret[" + cnt + "])) {\n");
   205                         if (isModel[0]) {
   206                             w.append("    this.prop_").append(pn).append(".add(new ");
   207                             w.append(type).append("(e));\n");
   208                         } else {
   209                             if (isPrimitive(type)) {
   210                                 w.append("    this.prop_").append(pn).append(".add(((Number)e).");
   211                                 w.append(type).append("Value());\n");
   212                             } else {
   213                                 w.append("    this.prop_").append(pn).append(".add((");
   214                                 w.append(type).append(")e);\n");
   215                             }
   216                         }
   217                         w.append("  }\n");
   218                         w.append("}\n");
   219                     } else {
   220                         if (isPrimitive(type)) {
   221                             w.append("    this.prop_").append(pn);
   222                             w.append(" = ((Number)").append("ret[" + cnt + "]).");
   223                             w.append(type).append("Value();\n");
   224                         } else {
   225                             w.append("    this.prop_").append(pn);
   226                             w.append(" = (").append(type).append(')');
   227                             w.append("ret[" + cnt + "];\n");
   228                         }
   229                     }
   230                     cnt++;
   231                 }
   232                 w.append("    intKnckt();\n");
   233                 w.append("  };\n");
   234                 writeToString(m.properties(), w);
   235                 writeClone(className, m.properties(), w);
   236                 w.append("}\n");
   237             } finally {
   238                 w.close();
   239             }
   240         } catch (IOException ex) {
   241             err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   242             return false;
   243         }
   244         return ok;
   245     }
   246     
   247     private boolean processPage(Element e) {
   248         boolean ok = true;
   249         Page p = e.getAnnotation(Page.class);
   250         if (p == null) {
   251             return true;
   252         }
   253         String pkg = findPkgName(e);
   254 
   255         ProcessPage pp;
   256         try (InputStream is = openStream(pkg, p.xhtml())) {
   257             pp = ProcessPage.readPage(is);
   258             is.close();
   259         } catch (IOException iOException) {
   260             err().printMessage(Diagnostic.Kind.ERROR, "Can't read " + p.xhtml() + " as " + iOException.getMessage(), e);
   261             ok = false;
   262             pp = null;
   263         }
   264         Writer w;
   265         String className = p.className();
   266         if (className.isEmpty()) {
   267             int indx = p.xhtml().indexOf('.');
   268             className = p.xhtml().substring(0, indx);
   269         }
   270         try {
   271             StringWriter body = new StringWriter();
   272             List<String> propsGetSet = new ArrayList<>();
   273             List<String> functions = new ArrayList<>();
   274             Map<String, Collection<String>> propsDeps = new HashMap<>();
   275             Map<String, Collection<String>> functionDeps = new HashMap<>();
   276             if (!generateComputedProperties(body, p.properties(), e.getEnclosedElements(), propsGetSet, propsDeps)) {
   277                 ok = false;
   278             }
   279             if (!generateOnChange(e, propsDeps, p.properties(), className, functionDeps)) {
   280                 ok = false;
   281             }
   282             if (!generateProperties(e, body, p.properties(), propsGetSet, propsDeps, functionDeps)) {
   283                 ok = false;
   284             }
   285             if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
   286                 ok = false;
   287             }
   288             if (!generateReceive(e, body, className, e.getEnclosedElements(), functions)) {
   289                 ok = false;
   290             }
   291             
   292             FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
   293             w = new OutputStreamWriter(java.openOutputStream());
   294             try {
   295                 w.append("package " + pkg + ";\n");
   296                 w.append("import org.apidesign.bck2brwsr.htmlpage.api.*;\n");
   297                 w.append("import org.apidesign.bck2brwsr.htmlpage.KOList;\n");
   298                 w.append("final class ").append(className).append(" {\n");
   299                 w.append("  private boolean locked;\n");
   300                 if (!initializeOnClick(className, (TypeElement) e, w, pp)) {
   301                     ok = false;
   302                 } else {
   303                     if (pp != null) for (String id : pp.ids()) {
   304                         String tag = pp.tagNameForId(id);
   305                         String type = type(tag);
   306                         w.append("  ").append("public final ").
   307                             append(type).append(' ').append(cnstnt(id)).append(" = new ").
   308                             append(type).append("(\"").append(id).append("\");\n");
   309                     }
   310                 }
   311                 w.append("  private org.apidesign.bck2brwsr.htmlpage.Knockout ko;\n");
   312                 w.append(body.toString());
   313                 if (!propsGetSet.isEmpty()) {
   314                     w.write("public " + className + " applyBindings() {\n");
   315                     w.write("  ko = org.apidesign.bck2brwsr.htmlpage.Knockout.applyBindings(");
   316                     w.write(className + ".class, this, ");
   317                     writeStringArray(propsGetSet, w);
   318                     w.append(", ");
   319                     writeStringArray(functions, w);
   320                     w.write(");\n  return this;\n}\n");
   321 
   322                     w.write("public void triggerEvent(Element e, OnEvent ev) {\n");
   323                     w.write("  org.apidesign.bck2brwsr.htmlpage.Knockout.triggerEvent(e.getId(), ev.getElementPropertyName());\n");
   324                     w.write("}\n");
   325                 }
   326                 w.append("}\n");
   327             } finally {
   328                 w.close();
   329             }
   330         } catch (IOException ex) {
   331             err().printMessage(Diagnostic.Kind.ERROR, "Can't create " + className + ".java", e);
   332             return false;
   333         }
   334         return ok;
   335     }
   336 
   337     private static String type(String tag) {
   338         if (tag.equals("title")) {
   339             return "Title";
   340         }
   341         if (tag.equals("button")) {
   342             return "Button";
   343         }
   344         if (tag.equals("input")) {
   345             return "Input";
   346         }
   347         if (tag.equals("canvas")) {
   348             return "Canvas";
   349         }
   350         if (tag.equals("img")) {
   351             return "Image";
   352         }
   353         return "Element";
   354     }
   355 
   356     private static String cnstnt(String id) {
   357         return id.replace('.', '_').replace('-', '_');
   358     }
   359 
   360     private boolean initializeOnClick(
   361         String className, TypeElement type, Writer w, ProcessPage pp
   362     ) throws IOException {
   363         boolean ok = true;
   364         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   365         { //for (Element clazz : pe.getEnclosedElements()) {
   366           //  if (clazz.getKind() != ElementKind.CLASS) {
   367             //    continue;
   368            // }
   369             w.append("  public ").append(className).append("() {\n");
   370             StringBuilder dispatch = new StringBuilder();
   371             int dispatchCnt = 0;
   372             for (Element method : type.getEnclosedElements()) {
   373                 On oc = method.getAnnotation(On.class);
   374                 if (oc != null) {
   375                     for (String id : oc.id()) {
   376                         if (pp == null) {
   377                             err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " not found in HTML page.");
   378                             ok = false;
   379                             continue;
   380                         }
   381                         if (pp.tagNameForId(id) == null) {
   382                             err().printMessage(Diagnostic.Kind.ERROR, "id = " + id + " does not exist in the HTML page. Found only " + pp.ids(), method);
   383                             ok = false;
   384                             continue;
   385                         }
   386                         ExecutableElement ee = (ExecutableElement)method;
   387                         CharSequence params = wrapParams(ee, id, className, "ev", null);
   388                         if (!ee.getModifiers().contains(Modifier.STATIC)) {
   389                             err().printMessage(Diagnostic.Kind.ERROR, "@On method has to be static", ee);
   390                             ok = false;
   391                             continue;
   392                         }
   393                         if (ee.getModifiers().contains(Modifier.PRIVATE)) {
   394                             err().printMessage(Diagnostic.Kind.ERROR, "@On method can't be private", ee);
   395                             ok = false;
   396                             continue;
   397                         }
   398                         w.append("  OnEvent." + oc.event()).append(".of(").append(cnstnt(id)).
   399                             append(").perform(new OnDispatch(" + dispatchCnt + "));\n");
   400 
   401                         dispatch.
   402                             append("      case ").append(dispatchCnt).append(": ").
   403                             append(type.getSimpleName().toString()).
   404                             append('.').append(ee.getSimpleName()).append("(").
   405                             append(params).
   406                             append("); break;\n");
   407                         
   408                         dispatchCnt++;
   409                     }
   410                 }
   411             }
   412             w.append("  }\n");
   413             if (dispatchCnt > 0) {
   414                 w.append("class OnDispatch implements OnHandler {\n");
   415                 w.append("  private final int dispatch;\n");
   416                 w.append("  OnDispatch(int d) { dispatch = d; }\n");
   417                 w.append("  public void onEvent(Object ev) {\n");
   418                 w.append("    switch (dispatch) {\n");
   419                 w.append(dispatch);
   420                 w.append("    }\n");
   421                 w.append("  }\n");
   422                 w.append("}\n");
   423             }
   424             
   425 
   426         }
   427         return ok;
   428     }
   429 
   430     @Override
   431     public Iterable<? extends Completion> getCompletions(
   432         Element element, AnnotationMirror annotation, 
   433         ExecutableElement member, String userText
   434     ) {
   435         if (!userText.startsWith("\"")) {
   436             return Collections.emptyList();
   437         }
   438         
   439         Element cls = findClass(element);
   440         Page p = cls.getAnnotation(Page.class);
   441         String pkg = findPkgName(cls);
   442         ProcessPage pp;
   443         try {
   444             InputStream is = openStream(pkg, p.xhtml());
   445             pp = ProcessPage.readPage(is);
   446             is.close();
   447         } catch (IOException iOException) {
   448             return Collections.emptyList();
   449         }
   450         
   451         List<Completion> cc = new ArrayList<>();
   452         userText = userText.substring(1);
   453         for (String id : pp.ids()) {
   454             if (id.startsWith(userText)) {
   455                 cc.add(Completions.of("\"" + id + "\"", id));
   456             }
   457         }
   458         return cc;
   459     }
   460     
   461     private static Element findClass(Element e) {
   462         if (e == null) {
   463             return null;
   464         }
   465         Page p = e.getAnnotation(Page.class);
   466         if (p != null) {
   467             return e;
   468         }
   469         return e.getEnclosingElement();
   470     }
   471 
   472     private boolean generateProperties(
   473         Element where,
   474         Writer w, Property[] properties,
   475         Collection<String> props, 
   476         Map<String,Collection<String>> deps,
   477         Map<String,Collection<String>> functionDeps
   478     ) throws IOException {
   479         boolean ok = true;
   480         for (Property p : properties) {
   481             final String tn;
   482             tn = typeName(where, p);
   483             String[] gs = toGetSet(p.name(), tn, p.array());
   484 
   485             if (p.array()) {
   486                 w.write("private KOList<" + tn + "> prop_" + p.name() + " = new KOList<" + tn + ">(\""
   487                     + p.name() + "\"");
   488                 Collection<String> dependants = deps.get(p.name());
   489                 if (dependants != null) {
   490                     for (String depProp : dependants) {
   491                         w.write(", ");
   492                         w.write('\"');
   493                         w.write(depProp);
   494                         w.write('\"');
   495                     }
   496                 }
   497                 w.write(")");
   498                 
   499                 dependants = functionDeps.get(p.name());
   500                 if (dependants != null) {
   501                     w.write(".onChange(new Runnable() { public void run() {\n");
   502                     for (String call : dependants) {
   503                         w.append(call);
   504                     }
   505                     w.write("}})");
   506                 }
   507                 w.write(";\n");
   508                 
   509                 w.write("public java.util.List<" + tn + "> " + gs[0] + "() {\n");
   510                 w.write("  if (locked) throw new IllegalStateException();\n");
   511                 w.write("  prop_" + p.name() + ".assign(ko);\n");
   512                 w.write("  return prop_" + p.name() + ";\n");
   513                 w.write("}\n");
   514             } else {
   515                 w.write("private " + tn + " prop_" + p.name() + ";\n");
   516                 w.write("public " + tn + " " + gs[0] + "() {\n");
   517                 w.write("  if (locked) throw new IllegalStateException();\n");
   518                 w.write("  return prop_" + p.name() + ";\n");
   519                 w.write("}\n");
   520                 w.write("public void " + gs[1] + "(" + tn + " v) {\n");
   521                 w.write("  if (locked) throw new IllegalStateException();\n");
   522                 w.write("  prop_" + p.name() + " = v;\n");
   523                 w.write("  if (ko != null) {\n");
   524                 w.write("    ko.valueHasMutated(\"" + p.name() + "\");\n");
   525                 Collection<String> dependants = deps.get(p.name());
   526                 if (dependants != null) {
   527                     for (String depProp : dependants) {
   528                         w.write("    ko.valueHasMutated(\"" + depProp + "\");\n");
   529                     }
   530                 }
   531                 w.write("  }\n");
   532                 dependants = functionDeps.get(p.name());
   533                 if (dependants != null) {
   534                     for (String call : dependants) {
   535                         w.append(call);
   536                     }
   537                 }
   538                 w.write("}\n");
   539             }
   540             
   541             props.add(p.name());
   542             props.add(gs[2]);
   543             props.add(gs[3]);
   544             props.add(gs[0]);
   545         }
   546         return ok;
   547     }
   548 
   549     private boolean generateComputedProperties(
   550         Writer w, Property[] fixedProps,
   551         Collection<? extends Element> arr, Collection<String> props,
   552         Map<String,Collection<String>> deps
   553     ) throws IOException {
   554         boolean ok = true;
   555         for (Element e : arr) {
   556             if (e.getKind() != ElementKind.METHOD) {
   557                 continue;
   558             }
   559             if (e.getAnnotation(ComputedProperty.class) == null) {
   560                 continue;
   561             }
   562             ExecutableElement ee = (ExecutableElement)e;
   563             final TypeMirror rt = ee.getReturnType();
   564             final Types tu = processingEnv.getTypeUtils();
   565             TypeMirror ert = tu.erasure(rt);
   566             String tn = fqn(ert, ee);
   567             boolean array = false;
   568             if (tn.equals("java.util.List")) {
   569                 array = true;
   570             }
   571             
   572             final String sn = ee.getSimpleName().toString();
   573             String[] gs = toGetSet(sn, tn, array);
   574             
   575             w.write("public " + tn + " " + gs[0] + "() {\n");
   576             w.write("  if (locked) throw new IllegalStateException();\n");
   577             int arg = 0;
   578             for (VariableElement pe : ee.getParameters()) {
   579                 final String dn = pe.getSimpleName().toString();
   580                 
   581                 if (!verifyPropName(pe, dn, fixedProps)) {
   582                     ok = false;
   583                 }
   584                 
   585                 final String dt = fqn(pe.asType(), ee);
   586                 String[] call = toGetSet(dn, dt, false);
   587                 w.write("  " + dt + " arg" + (++arg) + " = ");
   588                 w.write(call[0] + "();\n");
   589                 
   590                 Collection<String> depends = deps.get(dn);
   591                 if (depends == null) {
   592                     depends = new LinkedHashSet<>();
   593                     deps.put(dn, depends);
   594                 }
   595                 depends.add(sn);
   596             }
   597             w.write("  try {\n");
   598             w.write("    locked = true;\n");
   599             w.write("    return " + e.getEnclosingElement().getSimpleName() + '.' + e.getSimpleName() + "(");
   600             String sep = "";
   601             for (int i = 1; i <= arg; i++) {
   602                 w.write(sep);
   603                 w.write("arg" + i);
   604                 sep = ", ";
   605             }
   606             w.write(");\n");
   607             w.write("  } finally {\n");
   608             w.write("    locked = false;\n");
   609             w.write("  }\n");
   610             w.write("}\n");
   611 
   612             props.add(e.getSimpleName().toString());
   613             props.add(gs[2]);
   614             props.add(null);
   615             props.add(gs[0]);
   616         }
   617         
   618         return ok;
   619     }
   620 
   621     private static String[] toGetSet(String name, String type, boolean array) {
   622         String n = Character.toUpperCase(name.charAt(0)) + name.substring(1);
   623         String bck2brwsrType = "L" + type.replace('.', '_') + "_2";
   624         if ("int".equals(type)) {
   625             bck2brwsrType = "I";
   626         }
   627         if ("double".equals(type)) {
   628             bck2brwsrType = "D";
   629         }
   630         String pref = "get";
   631         if ("boolean".equals(type)) {
   632             pref = "is";
   633             bck2brwsrType = "Z";
   634         }
   635         final String nu = n.replace('.', '_');
   636         if (array) {
   637             return new String[] { 
   638                 "get" + n,
   639                 null,
   640                 "get" + nu + "__Ljava_util_List_2",
   641                 null
   642             };
   643         }
   644         return new String[]{
   645             pref + n, 
   646             "set" + n, 
   647             pref + nu + "__" + bck2brwsrType,
   648             "set" + nu + "__V" + bck2brwsrType
   649         };
   650     }
   651 
   652     private String typeName(Element where, Property p) {
   653         String ret;
   654         boolean[] isModel = { false };
   655         boolean[] isEnum = { false };
   656         ret = checkType(p, isModel, isEnum);
   657         if (p.array()) {
   658             String bt = findBoxedType(ret);
   659             if (bt != null) {
   660                 return bt;
   661             }
   662         }
   663         if (!isModel[0] && !"java.lang.String".equals(ret) && !isEnum[0]) {
   664             String bt = findBoxedType(ret);
   665             if (bt == null) {
   666                 err().printMessage(
   667                     Diagnostic.Kind.ERROR, 
   668                     "Only primitive types supported in the mapping. Not " + ret,
   669                     where
   670                 );
   671             }
   672         }
   673         return ret;
   674     }
   675     
   676     private static String findBoxedType(String ret) {
   677         if (ret.equals("boolean")) {
   678             return Boolean.class.getName();
   679         }
   680         if (ret.equals("byte")) {
   681             return Byte.class.getName();
   682         }
   683         if (ret.equals("short")) {
   684             return Short.class.getName();
   685         }
   686         if (ret.equals("char")) {
   687             return Character.class.getName();
   688         }
   689         if (ret.equals("int")) {
   690             return Integer.class.getName();
   691         }
   692         if (ret.equals("long")) {
   693             return Long.class.getName();
   694         }
   695         if (ret.equals("float")) {
   696             return Float.class.getName();
   697         }
   698         if (ret.equals("double")) {
   699             return Double.class.getName();
   700         }
   701         return null;
   702     }
   703 
   704     private boolean verifyPropName(Element e, String propName, Property[] existingProps) {
   705         StringBuilder sb = new StringBuilder();
   706         String sep = "";
   707         for (Property property : existingProps) {
   708             if (property.name().equals(propName)) {
   709                 return true;
   710             }
   711             sb.append(sep);
   712             sb.append('"');
   713             sb.append(property.name());
   714             sb.append('"');
   715             sep = ", ";
   716         }
   717         err().printMessage(Diagnostic.Kind.ERROR,
   718             propName + " is not one of known properties: " + sb
   719             , e
   720         );
   721         return false;
   722     }
   723 
   724     private static String findPkgName(Element e) {
   725         for (;;) {
   726             if (e.getKind() == ElementKind.PACKAGE) {
   727                 return ((PackageElement)e).getQualifiedName().toString();
   728             }
   729             e = e.getEnclosingElement();
   730         }
   731     }
   732 
   733     private boolean generateFunctions(
   734         Element clazz, StringWriter body, String className, 
   735         List<? extends Element> enclosedElements, List<String> functions
   736     ) {
   737         for (Element m : enclosedElements) {
   738             if (m.getKind() != ElementKind.METHOD) {
   739                 continue;
   740             }
   741             ExecutableElement e = (ExecutableElement)m;
   742             OnFunction onF = e.getAnnotation(OnFunction.class);
   743             if (onF == null) {
   744                 continue;
   745             }
   746             if (!e.getModifiers().contains(Modifier.STATIC)) {
   747                 err().printMessage(
   748                     Diagnostic.Kind.ERROR, "@OnFunction method needs to be static", e
   749                 );
   750                 return false;
   751             }
   752             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   753                 err().printMessage(
   754                     Diagnostic.Kind.ERROR, "@OnFunction method cannot be private", e
   755                 );
   756                 return false;
   757             }
   758             if (e.getReturnType().getKind() != TypeKind.VOID) {
   759                 err().printMessage(
   760                     Diagnostic.Kind.ERROR, "@OnFunction method should return void", e
   761                 );
   762                 return false;
   763             }
   764             String n = e.getSimpleName().toString();
   765             body.append("private void ").append(n).append("(Object data, Object ev) {\n");
   766             body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   767             body.append(wrapParams(e, null, className, "ev", "data"));
   768             body.append(");\n");
   769             body.append("}\n");
   770             
   771             functions.add(n);
   772             functions.add(n + "__VLjava_lang_Object_2Ljava_lang_Object_2");
   773         }
   774         return true;
   775     }
   776 
   777     private boolean generateOnChange(Element clazz, Map<String,Collection<String>> propDeps,
   778         Property[] properties, String className, 
   779         Map<String, Collection<String>> functionDeps
   780     ) {
   781         for (Element m : clazz.getEnclosedElements()) {
   782             if (m.getKind() != ElementKind.METHOD) {
   783                 continue;
   784             }
   785             ExecutableElement e = (ExecutableElement) m;
   786             OnPropertyChange onPC = e.getAnnotation(OnPropertyChange.class);
   787             if (onPC == null) {
   788                 continue;
   789             }
   790             for (String pn : onPC.value()) {
   791                 if (findProperty(properties, pn) == null && findDerivedFrom(propDeps, pn).isEmpty()) {
   792                     err().printMessage(Diagnostic.Kind.ERROR, "No property named '" + pn + "' in the model");
   793                     return false;
   794                 }
   795             }
   796             if (!e.getModifiers().contains(Modifier.STATIC)) {
   797                 err().printMessage(
   798                     Diagnostic.Kind.ERROR, "@OnPropertyChange method needs to be static", e);
   799                 return false;
   800             }
   801             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   802                 err().printMessage(
   803                     Diagnostic.Kind.ERROR, "@OnPropertyChange method cannot be private", e);
   804                 return false;
   805             }
   806             if (e.getReturnType().getKind() != TypeKind.VOID) {
   807                 err().printMessage(
   808                     Diagnostic.Kind.ERROR, "@OnPropertyChange method should return void", e);
   809                 return false;
   810             }
   811             String n = e.getSimpleName().toString();
   812             
   813             
   814             for (String pn : onPC.value()) {
   815                 StringBuilder call = new StringBuilder();
   816                 call.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   817                 call.append(wrapPropName(e, className, "name", pn));
   818                 call.append(");\n");
   819                 
   820                 Collection<String> change = functionDeps.get(pn);
   821                 if (change == null) {
   822                     change = new ArrayList<>();
   823                     functionDeps.put(pn, change);
   824                 }
   825                 change.add(call.toString());
   826                 for (String dpn : findDerivedFrom(propDeps, pn)) {
   827                     change = functionDeps.get(dpn);
   828                     if (change == null) {
   829                         change = new ArrayList<>();
   830                         functionDeps.put(dpn, change);
   831                     }
   832                     change.add(call.toString());
   833                 }
   834             }
   835         }
   836         return true;
   837     }
   838     
   839     private boolean generateReceive(
   840         Element clazz, StringWriter body, String className, 
   841         List<? extends Element> enclosedElements, List<String> functions
   842     ) {
   843         for (Element m : enclosedElements) {
   844             if (m.getKind() != ElementKind.METHOD) {
   845                 continue;
   846             }
   847             ExecutableElement e = (ExecutableElement)m;
   848             OnReceive onR = e.getAnnotation(OnReceive.class);
   849             if (onR == null) {
   850                 continue;
   851             }
   852             if (!e.getModifiers().contains(Modifier.STATIC)) {
   853                 err().printMessage(
   854                     Diagnostic.Kind.ERROR, "@OnReceive method needs to be static", e
   855                 );
   856                 return false;
   857             }
   858             if (e.getModifiers().contains(Modifier.PRIVATE)) {
   859                 err().printMessage(
   860                     Diagnostic.Kind.ERROR, "@OnReceive method cannot be private", e
   861                 );
   862                 return false;
   863             }
   864             if (e.getReturnType().getKind() != TypeKind.VOID) {
   865                 err().printMessage(
   866                     Diagnostic.Kind.ERROR, "@OnReceive method should return void", e
   867                 );
   868                 return false;
   869             }
   870             String modelClass = null;
   871             boolean expectsList = false;
   872             List<String> args = new ArrayList<>();
   873             {
   874                 for (VariableElement ve : e.getParameters()) {
   875                     TypeMirror modelType = null;
   876                     if (ve.asType().toString().equals(className)) {
   877                         args.add(className + ".this");
   878                     } else if (isModel(ve.asType())) {
   879                         modelType = ve.asType();
   880                     } else if (ve.asType().getKind() == TypeKind.ARRAY) {
   881                         modelType = ((ArrayType)ve.asType()).getComponentType();
   882                         expectsList = true;
   883                     }
   884                     if (modelType != null) {
   885                         if (modelClass != null) {
   886                             err().printMessage(Diagnostic.Kind.ERROR, "There can be only one model class among arguments", e);
   887                         } else {
   888                             modelClass = modelType.toString();
   889                             if (expectsList) {
   890                                 args.add("arr");
   891                             } else {
   892                                 args.add("arr[0]");
   893                             }
   894                         }
   895                     }
   896                 }
   897             }
   898             if (modelClass == null) {
   899                 err().printMessage(Diagnostic.Kind.ERROR, "The method needs to have one @Model class as parameter", e);
   900             }
   901             String n = e.getSimpleName().toString();
   902             body.append("public void ").append(n).append("(");
   903             StringBuilder assembleURL = new StringBuilder();
   904             String jsonpVarName = null;
   905             {
   906                 String sep = "";
   907                 boolean skipJSONP = onR.jsonp().isEmpty();
   908                 for (String p : findParamNames(e, onR.url(), assembleURL)) {
   909                     if (!skipJSONP && p.equals(onR.jsonp())) {
   910                         skipJSONP = true;
   911                         jsonpVarName = p;
   912                         continue;
   913                     }
   914                     body.append(sep);
   915                     body.append("String ").append(p);
   916                     sep = ", ";
   917                 }
   918                 if (!skipJSONP) {
   919                     err().printMessage(Diagnostic.Kind.ERROR, 
   920                         "Name of jsonp attribute ('" + onR.jsonp() + 
   921                         "') is not used in url attribute '" + onR.url() + "'"
   922                     );
   923                 }
   924             }
   925             body.append(") {\n");
   926             body.append("  final Object[] result = { null };\n");
   927             body.append(
   928                 "  class ProcessResult implements Runnable {\n" +
   929                 "    @Override\n" +
   930                 "    public void run() {\n" +
   931                 "      Object value = result[0];\n");
   932             body.append(
   933                 "      " + modelClass + "[] arr;\n");
   934             body.append(
   935                 "      if (value instanceof Object[]) {\n" +
   936                 "        Object[] data = ((Object[])value);\n" +
   937                 "        arr = new " + modelClass + "[data.length];\n" +
   938                 "        for (int i = 0; i < data.length; i++) {\n" +
   939                 "          arr[i] = new " + modelClass + "(data[i]);\n" +
   940                 "        }\n" +
   941                 "      } else {\n" +
   942                 "        arr = new " + modelClass + "[1];\n" +
   943                 "        arr[0] = new " + modelClass + "(value);\n" +
   944                 "      }\n"
   945             );
   946             {
   947                 body.append(clazz.getSimpleName()).append(".").append(n).append("(");
   948                 String sep = "";
   949                 for (String arg : args) {
   950                     body.append(sep);
   951                     body.append(arg);
   952                     sep = ", ";
   953                 }
   954                 body.append(");\n");
   955             }
   956             body.append(
   957                 "    }\n" +
   958                 "  }\n"
   959             );
   960             body.append("  ProcessResult pr = new ProcessResult();\n");
   961             if (jsonpVarName != null) {
   962                 body.append("  String ").append(jsonpVarName).
   963                     append(" = org.apidesign.bck2brwsr.htmlpage.ConvertTypes.createJSONP(result, pr);\n");
   964             }
   965             body.append("  org.apidesign.bck2brwsr.htmlpage.ConvertTypes.loadJSON(\n      ");
   966             body.append(assembleURL);
   967             body.append(", result, pr, ").append(jsonpVarName).append("\n  );\n");
   968 //            body.append("  ").append(clazz.getSimpleName()).append(".").append(n).append("(");
   969 //            body.append(wrapParams(e, null, className, "ev", "data"));
   970 //            body.append(");\n");
   971             body.append("}\n");
   972         }
   973         return true;
   974     }
   975 
   976     private CharSequence wrapParams(
   977         ExecutableElement ee, String id, String className, String evName, String dataName
   978     ) {
   979         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
   980         StringBuilder params = new StringBuilder();
   981         boolean first = true;
   982         for (VariableElement ve : ee.getParameters()) {
   983             if (!first) {
   984                 params.append(", ");
   985             }
   986             first = false;
   987             String toCall = null;
   988             if (ve.asType() == stringType) {
   989                 if (ve.getSimpleName().contentEquals("id")) {
   990                     params.append('"').append(id).append('"');
   991                     continue;
   992                 }
   993                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toString(";
   994             }
   995             if (ve.asType().getKind() == TypeKind.DOUBLE) {
   996                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toDouble(";
   997             }
   998             if (ve.asType().getKind() == TypeKind.INT) {
   999                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toInt(";
  1000             }
  1001             if (dataName != null && ve.getSimpleName().contentEquals(dataName) && isModel(ve.asType())) {
  1002                 toCall = "org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toModel(" + ve.asType() + ".class, ";
  1003             }
  1004 
  1005             if (toCall != null) {
  1006                 params.append(toCall);
  1007                 if (dataName != null && ve.getSimpleName().contentEquals(dataName)) {
  1008                     params.append(dataName);
  1009                     params.append(", null");
  1010                 } else {
  1011                     if (evName == null) {
  1012                         final StringBuilder sb = new StringBuilder();
  1013                         sb.append("Unexpected string parameter name.");
  1014                         if (dataName != null) {
  1015                             sb.append(" Try \"").append(dataName).append("\"");
  1016                         }
  1017                         err().printMessage(Diagnostic.Kind.ERROR, sb.toString(), ee);
  1018                     }
  1019                     params.append(evName);
  1020                     params.append(", \"");
  1021                     params.append(ve.getSimpleName().toString());
  1022                     params.append("\"");
  1023                 }
  1024                 params.append(")");
  1025                 continue;
  1026             }
  1027             String rn = fqn(ve.asType(), ee);
  1028             int last = rn.lastIndexOf('.');
  1029             if (last >= 0) {
  1030                 rn = rn.substring(last + 1);
  1031             }
  1032             if (rn.equals(className)) {
  1033                 params.append(className).append(".this");
  1034                 continue;
  1035             }
  1036             err().printMessage(Diagnostic.Kind.ERROR, 
  1037                 "@On method can only accept String named 'id' or " + className + " arguments",
  1038                 ee
  1039             );
  1040         }
  1041         return params;
  1042     }
  1043     
  1044     
  1045     private CharSequence wrapPropName(
  1046         ExecutableElement ee, String className, String propName, String propValue
  1047     ) {
  1048         TypeMirror stringType = processingEnv.getElementUtils().getTypeElement("java.lang.String").asType();
  1049         StringBuilder params = new StringBuilder();
  1050         boolean first = true;
  1051         for (VariableElement ve : ee.getParameters()) {
  1052             if (!first) {
  1053                 params.append(", ");
  1054             }
  1055             first = false;
  1056             if (ve.asType() == stringType) {
  1057                 if (propName != null && ve.getSimpleName().contentEquals(propName)) {
  1058                     params.append('"').append(propValue).append('"');
  1059                 } else {
  1060                     err().printMessage(Diagnostic.Kind.ERROR, "Unexpected string parameter name. Try \"" + propName + "\".");
  1061                 }
  1062                 continue;
  1063             }
  1064             String rn = fqn(ve.asType(), ee);
  1065             int last = rn.lastIndexOf('.');
  1066             if (last >= 0) {
  1067                 rn = rn.substring(last + 1);
  1068             }
  1069             if (rn.equals(className)) {
  1070                 params.append(className).append(".this");
  1071                 continue;
  1072             }
  1073             err().printMessage(Diagnostic.Kind.ERROR,
  1074                 "@OnPropertyChange method can only accept String or " + className + " arguments",
  1075                 ee);
  1076         }
  1077         return params;
  1078     }
  1079     
  1080     private boolean isModel(TypeMirror tm) {
  1081         final Element e = processingEnv.getTypeUtils().asElement(tm);
  1082         if (e == null) {
  1083             return false;
  1084         }
  1085         for (Element ch : e.getEnclosedElements()) {
  1086             if (ch.getKind() == ElementKind.METHOD) {
  1087                 ExecutableElement ee = (ExecutableElement)ch;
  1088                 if (ee.getParameters().isEmpty() && ee.getSimpleName().contentEquals("modelFor")) {
  1089                     return true;
  1090                 }
  1091             }
  1092         }
  1093         return models.values().contains(e.getSimpleName().toString());
  1094     }
  1095 
  1096     private void writeStringArray(List<String> strings, Writer w) throws IOException {
  1097         w.write("new String[] {\n");
  1098         String sep = "";
  1099         for (String n : strings) {
  1100             w.write(sep);
  1101             if (n == null) {
  1102                 w.write("    null");
  1103             } else {
  1104                 w.write("    \"" + n + "\"");
  1105             }
  1106             sep = ",\n";
  1107         }
  1108         w.write("\n  }");
  1109     }
  1110     
  1111     private void writeToString(Property[] props, Writer w) throws IOException {
  1112         w.write("  public String toString() {\n");
  1113         w.write("    StringBuilder sb = new StringBuilder();\n");
  1114         w.write("    sb.append('{');\n");
  1115         String sep = "";
  1116         for (Property p : props) {
  1117             w.write(sep);
  1118             w.append("    sb.append(\"" + p.name() + ": \");\n");
  1119             w.append("    sb.append(org.apidesign.bck2brwsr.htmlpage.ConvertTypes.toJSON(prop_");
  1120             w.append(p.name()).append("));\n");
  1121             sep =    "    sb.append(',');\n";
  1122         }
  1123         w.write("    sb.append('}');\n");
  1124         w.write("    return sb.toString();\n");
  1125         w.write("  }\n");
  1126     }
  1127     private void writeClone(String className, Property[] props, Writer w) throws IOException {
  1128         w.write("  public " + className + " clone() {\n");
  1129         w.write("    " + className + " ret = new " + className + "();\n");
  1130         for (Property p : props) {
  1131             if (!p.array()) {
  1132                 boolean isModel[] = { false };
  1133                 boolean isEnum[] = { false };
  1134                 checkType(p, isModel, isEnum);
  1135                 if (!isModel[0]) {
  1136                     w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ";\n");
  1137                     continue;
  1138                 }
  1139             }
  1140             w.write("    ret.prop_" + p.name() + " = prop_" + p.name() + ".clone();\n");
  1141         }
  1142         
  1143         w.write("    return ret;\n");
  1144         w.write("  }\n");
  1145     }
  1146 
  1147     private String inPckName(Element e) {
  1148         StringBuilder sb = new StringBuilder();
  1149         while (e.getKind() != ElementKind.PACKAGE) {
  1150             if (sb.length() == 0) {
  1151                 sb.append(e.getSimpleName());
  1152             } else {
  1153                 sb.insert(0, '.');
  1154                 sb.insert(0, e.getSimpleName());
  1155             }
  1156             e = e.getEnclosingElement();
  1157         }
  1158         return sb.toString();
  1159     }
  1160 
  1161     private String fqn(TypeMirror pt, Element relative) {
  1162         if (pt.getKind() == TypeKind.ERROR) {
  1163             final Elements eu = processingEnv.getElementUtils();
  1164             PackageElement pckg = eu.getPackageOf(relative);
  1165             return pckg.getQualifiedName() + "." + pt.toString();
  1166         }
  1167         return pt.toString();
  1168     }
  1169 
  1170     private String checkType(Property p, boolean[] isModel, boolean[] isEnum) {
  1171         String ret;
  1172         try {
  1173             ret = p.type().getName();
  1174         } catch (MirroredTypeException ex) {
  1175             TypeMirror tm = processingEnv.getTypeUtils().erasure(ex.getTypeMirror());
  1176             final Element e = processingEnv.getTypeUtils().asElement(tm);
  1177             final Model m = e == null ? null : e.getAnnotation(Model.class);
  1178             if (m != null) {
  1179                 ret = findPkgName(e) + '.' + m.className();
  1180                 isModel[0] = true;
  1181                 models.put(e, m.className());
  1182             } else {
  1183                 ret = tm.toString();
  1184             }
  1185             TypeMirror enm = processingEnv.getElementUtils().getTypeElement("java.lang.Enum").asType();
  1186             enm = processingEnv.getTypeUtils().erasure(enm);
  1187             isEnum[0] = processingEnv.getTypeUtils().isSubtype(tm, enm);
  1188         }
  1189         return ret;
  1190     }
  1191 
  1192     private Iterable<String> findParamNames(Element e, String url, StringBuilder assembleURL) {
  1193         List<String> params = new ArrayList<>();
  1194 
  1195         for (int pos = 0; ;) {
  1196             int next = url.indexOf('{', pos);
  1197             if (next == -1) {
  1198                 assembleURL.append('"')
  1199                     .append(url.substring(pos))
  1200                     .append('"');
  1201                 return params;
  1202             }
  1203             int close = url.indexOf('}', next);
  1204             if (close == -1) {
  1205                 err().printMessage(Diagnostic.Kind.ERROR, "Unbalanced '{' and '}' in " + url, e);
  1206                 return params;
  1207             }
  1208             final String paramName = url.substring(next + 1, close);
  1209             params.add(paramName);
  1210             assembleURL.append('"')
  1211                 .append(url.substring(pos, next))
  1212                 .append("\" + ").append(paramName).append(" + ");
  1213             pos = close + 1;
  1214         }
  1215     }
  1216 
  1217     private static Property findProperty(Property[] properties, String propName) {
  1218         for (Property p : properties) {
  1219             if (propName.equals(p.name())) {
  1220                 return p;
  1221             }
  1222         }
  1223         return null;
  1224     }
  1225 
  1226     private boolean isPrimitive(String type) {
  1227         return 
  1228             "int".equals(type) ||
  1229             "double".equals(type) ||
  1230             "long".equals(type) ||
  1231             "short".equals(type) ||
  1232             "byte".equals(type) ||
  1233             "float".equals(type);
  1234     }
  1235 
  1236     private static Collection<String> findDerivedFrom(Map<String, Collection<String>> propsDeps, String derivedProp) {
  1237         Set<String> names = new HashSet<>();
  1238         for (Map.Entry<String, Collection<String>> e : propsDeps.entrySet()) {
  1239             if (e.getValue().contains(derivedProp)) {
  1240                 names.add(e.getKey());
  1241             }
  1242         }
  1243         return names;
  1244     }
  1245 }