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