private boolean processModel()

in json/src/main/java/org/netbeans/html/json/impl/ModelProcessor.java [161:620]


    private boolean processModel(Element e) {
        boolean ok = true;
        Model m = e.getAnnotation(Model.class);
        if (m == null) {
            return true;
        }
        String pkg = findPkgName(e);
        Writer w;
        String className = m.className();
        models.put(e, className);
        try {
            StringWriter body = new StringWriter();
            StringBuilder onReceiveType = new StringBuilder();
            List<GetSet> propsGetSet = new ArrayList<>();
            List<Object> functions = new ArrayList<>();
            Map<String, Collection<String[]>> propsDeps = new HashMap<>();
            Map<String, Collection<String>> functionDeps = new HashMap<>();
            Prprt[] props = createProps(e, m.properties());
            final String builderPrefix = findBuilderPrefix(e, m);

            if (!generateComputedProperties(className, body, props, e.getEnclosedElements(), propsGetSet, propsDeps)) {
                ok = false;
            }
            if (!generateOnChange(e, propsDeps, props, className, functionDeps)) {
                ok = false;
            }
            Set<String> propertyFQNs = new HashSet<>();
            if (!generateProperties(e, builderPrefix, body, className, props, propsGetSet, propsDeps, functionDeps, propertyFQNs)) {
                ok = false;
            }
            if (!generateFunctions(e, body, className, e.getEnclosedElements(), functions)) {
                ok = false;
            }
            int functionsCount = functions.size() / 2;
            for (int i = 0; i < functions.size(); i += 2) {
                for (Prprt p : props) {
                    if (p.name().equals(functions.get(i))) {
                        error("Function cannot have the name of an existing property", e);
                        ok = false;
                    }
                }
            }
            if (!generateReceive(e, body, className, e.getEnclosedElements(), onReceiveType, propertyFQNs)) {
                ok = false;
            }
            if (!generateOperation(e, body, className, e.getEnclosedElements(), functions)) {
                ok = false;
            }
            FileObject java = processingEnv.getFiler().createSourceFile(pkg + '.' + className, e);
            w = new OutputStreamWriter(java.openOutputStream());
            try {
                w.append("package " + pkg + ";\n");
                w.append("import net.java.html.json.*;\n");
                for (String propertyFqns : propertyFQNs) {
                    w.append("import "+propertyFqns+";\n");                   
                }
                final String inPckName = inPckName(e, false);
                w.append("/** Generated for {@link ").append(inPckName).append("}*/\n");
                w.append("@java.lang.SuppressWarnings(\"all\")\n");
                w.append("public final class ").append(className).append(" implements Cloneable {\n");
                w.append("  private static Class<").append(inPckName).append("> modelFor() { return ").append(inPckName).append(".class; }\n");
                w.append("  private static final Html4JavaType TYPE = new Html4JavaType();\n");
                if (m.instance()) {
                    int cCnt = 0;
                    for (Element c : e.getEnclosedElements()) {
                        if (c.getKind() != ElementKind.CONSTRUCTOR) {
                            continue;
                        }
                        cCnt++;
                        ExecutableElement ec = (ExecutableElement) c;
                        if (ec.getParameters().size() > 0) {
                            continue;
                        }
                        if (ec.getModifiers().contains(Modifier.PRIVATE)) {
                            continue;
                        }
                        cCnt = 0;
                        break;
                    }
                    if (cCnt > 0) {
                        ok = false;
                        error("Needs non-private default constructor when instance=true", e);
                        w.append("  private final ").append(inPckName).append(" instance = null;\n");
                    } else {
                        w.append("  private final ").append(inPckName).append(" instance = new ").append(inPckName).append("();\n");
                    }
                }
                w.append("  private final org.netbeans.html.json.spi.Proto proto;\n");
                w.append(body.toString());
                w.append("  private ").append(className).append("(net.java.html.BrwsrCtx context) {\n");
                w.append("    this.proto = TYPE.createProto(this, context);\n");
                for (Prprt p : props) {
                    if (p.array()) {
                        final String tn = typeName(p);
                        String[] gs = toGetSet(p.name(), tn, p.array());
                        w.write("    this.prop_" + p.name() + " = proto.createList(\""
                            + p.name() + "\"");
                        if (p.mutable()) {
                            if (functionDeps.containsKey(p.name())) {
                                int index = Arrays.asList(functionDeps.keySet().toArray()).indexOf(p.name());
                                w.write(", " + index);
                            } else {
                                w.write(", -1");
                            }
                        } else {
                            w.write(", java.lang.Integer.MIN_VALUE");
                        }
                        Collection<String[]> dependants = propsDeps.get(p.name());
                        if (dependants != null) {
                            for (String[] depProp : dependants) {
                                w.write(", ");
                                w.write('\"');
                                w.write(depProp[0]);
                                w.write('\"');
                            }
                        }
                        w.write(")");
                        w.write(";\n");
                    }
                }
                w.append("  };\n");
                w.append("  public ").append(className).append("() {\n");
                w.append("    this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n");
                for (Prprt p : props) {
                    if (!p.array()) {
                        boolean[] isModel = {false};
                        boolean[] isEnum = {false};
                        boolean isPrimitive[] = {false};
                        String tn = checkType(p, isModel, isEnum, isPrimitive);
                        if (isModel[0]) {
                            w.write("    prop_" + p.name() + " = TYPE; /* uninitialized */\n");
                        }
                    }
                }
                w.append("  };\n");
                if (props.length > 0 && builderPrefix == null) {
                    StringBuilder constructorWithArguments = new StringBuilder();
                    constructorWithArguments.append("  public ").append(className).append("(");
                    Prprt firstArray = null;
                    String sep = "";
                    int parameterCount = 0;
                    for (Prprt p : props) {
                        if (p.array()) {
                            if (firstArray == null) {
                                firstArray = p;
                            }
                            continue;
                        }
                        String tn = typeName(p);
                        constructorWithArguments.append(sep);
                        constructorWithArguments.append(tn);
                        String[] third = toGetSet(p.name(), tn, false);
                        constructorWithArguments.append(" ").append(third[2]);
                        sep = ", ";
                        parameterCount++;
                    }
                    if (firstArray != null) {
                        String tn;
                        boolean[] isModel = {false};
                        boolean[] isEnum = {false};
                        boolean isPrimitive[] = {false};
                        tn = checkType(firstArray, isModel, isEnum, isPrimitive);
                        constructorWithArguments.append(sep);
                        constructorWithArguments.append(tn);
                        String[] third = toGetSet(firstArray.name(), tn, true);
                        constructorWithArguments.append("... ").append(third[2]);
                        parameterCount++;
                    }
                    constructorWithArguments.append(") {\n");
                    constructorWithArguments.append("    this(net.java.html.BrwsrCtx.findDefault(").append(className).append(".class));\n");
                    for (Prprt p : props) {
                        if (p.array()) {
                            continue;
                        }
                        String[] third = toGetSet(p.name(), null, false);
                        constructorWithArguments.append("    this.prop_" + p.name() + " = " + third[2] + ";\n");
                    }
                    if (firstArray != null) {
                        String[] third = toGetSet(firstArray.name(), null, true);
                        constructorWithArguments.append("    proto.initTo(this.prop_" + firstArray.name() + ", " + third[2] + ");\n");
                    }
                    constructorWithArguments.append("  };\n");
                    if (parameterCount < 255) {
                        w.write(constructorWithArguments.toString());
                    }
                }
                w.append("  private static class Html4JavaType extends org.netbeans.html.json.spi.Proto.Type<").append(className).append("> {\n");
                w.append("    private Html4JavaType() {\n      super(").append(className).append(".class, ").
                    append(inPckName).append(".class, " + propsGetSet.size() + ", "
                    + functionsCount + ");\n");
                {
                    for (int i = 0; i < propsGetSet.size(); i++) {
                        w.append("      registerProperty(\"").append(propsGetSet.get(i).name).append("\", ");
                        w.append((i) + ", " + propsGetSet.get(i).readOnly + ", " + propsGetSet.get(i).constant + ");\n");
                    }
                }
                {
                    for (int i = 0; i < functionsCount; i++) {
                        w.append("      registerFunction(\"").append((String)functions.get(i * 2)).append("\", ");
                        w.append(i + ");\n");
                    }
                }
                w.append("    }\n");
                w.append("    @Override public void setValue(" + className + " data, int type, Object value) {\n");
                w.append("      switch (type) {\n");
                for (int i = 0; i < propsGetSet.size(); i++) {
                    final GetSet pgs = propsGetSet.get(i);
                    if (pgs.readOnly) {
                        continue;
                    }
                    final String set = pgs.setter;
                    String tn = pgs.type;
                    String btn = findBoxedType(tn);
                    if (btn != null) {
                        tn = btn;
                    }
                    w.append("        case " + i + ": ");
                    if (pgs.setter != null) {
                        w.append("data.").append(pgs.setter).append("(TYPE.extractValue(" + tn + ".class, value)); return;\n");
                    } else {
                        w.append("TYPE.replaceValue(data.").append(pgs.getter).append("(), " + tn + ".class, value); return;\n");
                    }
                }
                w.append("      }\n");
                w.append("      throw new UnsupportedOperationException();\n");
                w.append("    }\n");
                w.append("    @Override public Object getValue(" + className + " data, int type) {\n");
                w.append("      switch (type) {\n");
                for (int i = 0; i < propsGetSet.size(); i++) {
                    final String get = propsGetSet.get(i).getter;
                    if (get != null) {
                        w.append("        case " + i + ": return data." + get + "();\n");
                    }
                }
                w.append("      }\n");
                w.append("      throw new UnsupportedOperationException();\n");
                w.append("    }\n");
                w.append("    @Override public void call(" + className + " model, int type, Object data, Object ev) throws Exception {\n");
                w.append("      switch (type) {\n");
                for (int i = 0; i < functions.size(); i += 2) {
                    final String name = (String)functions.get(i);
                    final Object param = functions.get(i + 1);
                    if (param instanceof ExecutableElement) {
                        ExecutableElement ee = (ExecutableElement)param;
                        w.append("        case " + (i / 2) + ":\n");
                        w.append("          ");
                        if (m.instance()) {
                            w.append("model.instance");
                        } else {
                            w.append(((TypeElement)e).getQualifiedName());
                        }
                        w.append(".").append(name).append("(");
                        w.append(wrapParams(ee, null, className, "model", "ev", "data"));
                        w.append(");\n");
                        w.append("          return;\n");
                    } else {
                        String call = (String)param;
                        w.append("        case " + (i / 2) + ":\n"); // model." + name + "(data, ev); return;\n");
                        w.append("          ").append(call).append("\n");
                        w.append("          return;\n");

                    }
                }
                w.append("      }\n");
                w.append("      throw new UnsupportedOperationException();\n");
                w.append("    }\n");
                w.append("    @Override public org.netbeans.html.json.spi.Proto protoFor(Object obj) {\n");
                w.append("      return ((" + className + ")obj).proto;");
                w.append("    }\n");
                w.append("    @Override public void onChange(" + className + " model, int type) {\n");
                w.append("      switch (type) {\n");
                {
                    String[] arr = functionDeps.keySet().toArray(new String[0]);
                    for (int i = 0; i < arr.length; i++) {
                        Collection<String> onChange = functionDeps.get(arr[i]);
                        if (onChange != null) {
                            w.append("      case " + i + ":\n");
                            for (String call : onChange) {
                                w.append("      ").append(call).append("\n");
                            }
                            w.write("      return;\n");
                        }
                    }
                }
                w.append("    }\n");
                w.append("      throw new UnsupportedOperationException();\n");
                w.append("    }\n");
                w.append(onReceiveType);
                w.append("    @Override public " + className + " read(net.java.html.BrwsrCtx c, Object json) { return new " + className + "(c, json); }\n");
                w.append("    @Override public " + className + " cloneTo(" + className + " o, net.java.html.BrwsrCtx c) { return o.clone(c); }\n");
                w.append("  }\n");
                w.append("  private ").append(className).append("(net.java.html.BrwsrCtx c, Object json) {\n");
                w.append("    this(c);\n");
                int values = 0;
                for (int i = 0; i < propsGetSet.size(); i++) {
                    Prprt p = findPrprt(props, propsGetSet.get(i).name);
                    if (p == null) {
                        continue;
                    }
                    values++;
                }
                w.append("    Object[] ret = new Object[" + values + "];\n");
                w.append("    proto.extract(json, new String[] {\n");
                for (int i = 0; i < propsGetSet.size(); i++) {
                    Prprt p = findPrprt(props, propsGetSet.get(i).name);
                    if (p == null) {
                        continue;
                    }
                    w.append("      \"").append(propsGetSet.get(i).name).append("\",\n");
                }
                w.append("    }, ret);\n");
                for (int i = 0, cnt = 0, prop = 0; i < propsGetSet.size(); i++) {
                    final String pn = propsGetSet.get(i).name;
                    Prprt p = findPrprt(props, pn);
                    if (p == null || prop >= props.length) {
                        continue;
                    }
                    boolean[] isModel = { false };
                    boolean[] isEnum = { false };
                    boolean isPrimitive[] = { false };
                    String type = checkType(props[prop++], isModel, isEnum, isPrimitive);
                    if (p.array()) {
                        w.append("    for (Object e : useAsArray(ret[" + cnt + "])) {\n");
                        if (isModel[0]) {
                            w.append("      this.prop_").append(pn).append(".add(proto.read");
                            w.append("(" + type + ".class, e));\n");
                        } else if (isEnum[0]) {
                            w.append("        this.prop_").append(pn);
                            w.append(".add(e == null ? null : ");
                            w.append(type).append(".valueOf(TYPE.stringValue(e)));\n");
                        } else {
                            if (isPrimitive(type)) {
                                if (type.equals("char")) {
                                    w.append("        this.prop_").append(pn).append(".add(TYPE.charValue(e));\n");
                                } else if (type.equals("boolean")) {
                                    w.append("        this.prop_").append(pn).append(".add(TYPE.boolValue(e));\n");
                                } else {
                                    w.append("        this.prop_").append(pn).append(".add(TYPE.numberValue(e).");
                                    w.append(type).append("Value());\n");
                                }
                            } else {
                                w.append("        this.prop_").append(pn).append(".add((");
                                w.append(type).append(")e);\n");
                            }
                        }
                        w.append("    }\n");
                    } else {
                        if (isEnum[0]) {
                            w.append("    try {\n");
                            w.append("    this.prop_").append(pn);
                            w.append(" = ret[" + cnt + "] == null ? null : ");
                            w.append(type).append(".valueOf(TYPE.stringValue(ret[" + cnt + "]));\n");
                            w.append("    } catch (IllegalArgumentException ex) {\n");
                            w.append("      ex.printStackTrace();\n");
                            w.append("    }\n");
                        } else if (isPrimitive(type)) {
                            w.append("    this.prop_").append(pn);
                            w.append(" = ret[" + cnt + "] == null ? ");
                            if ("char".equals(type)) {
                                w.append("0 : (TYPE.charValue(");
                            } else if ("boolean".equals(type)) {
                                w.append("false : (TYPE.boolValue(");
                            } else {
                                w.append("0 : (TYPE.numberValue(");
                            }
                            w.append("ret[" + cnt + "])).");
                            w.append(type).append("Value();\n");
                        } else if (isModel[0]) {
                            w.append("    this.prop_").append(pn).append(" = proto.read");
                            w.append("(" + type + ".class, ");
                            w.append("ret[" + cnt + "]);\n");
                        }else {
                            w.append("    this.prop_").append(pn);
                            w.append(" = (").append(type).append(')');
                            w.append("ret[" + cnt + "];\n");
                        }
                    }
                    cnt++;
                }
                w.append("  }\n");
                w.append("  private static Object[] useAsArray(Object o) {\n");
                w.append("    return o instanceof Object[] ? ((Object[])o) : o == null ? new Object[0] : new Object[] { o };\n");
                w.append("  }\n");
                writeToString(props, w);
                writeClone(className, props, w);
                String targetId = findTargetId(e);
                if (targetId != null) {
                    w.write("""
                              /** Activates this model instance in the current {@link 
                            net.java.html.json.Models#bind(java.lang.Object, net.java.html.BrwsrCtx) browser context}. 
                            In case of using Knockout technology, this means to 
                            bind JSON like data in this model instance with Knockout tags in 
                            the surrounding HTML page.
                            """);
                    if (targetId != null) {
                        w.write("This method binds to element '" + targetId + "' on the page\n");
                    }
                    w.write("""
                            @return <code>this</code> object
                            */
                            """);
                    w.write("  public " + className + " applyBindings() {\n");
                    w.write("    proto.applyBindings();\n");
    //                w.write("    proto.applyBindings(id);\n");
                    w.write("    return this;\n");
                    w.write("  }\n");
                } else {
                    w.write("  private " + className + " applyBindings() {\n");
                    w.write("    throw new IllegalStateException(\"Please specify targetId=\\\"\\\" in your @Model annotation\");\n");
                    w.write("  }\n");
                }
                w.write("  public boolean equals(Object o) {\n");
                w.write("    if (o == this) return true;\n");
                w.write("    if (!(o instanceof " + className + ")) return false;\n");
                w.write("    " + className + " p = (" + className + ")o;\n");
                boolean thisToNull = false;
                for (Prprt p : props) {
                    boolean[] isModel = {false};
                    boolean[] isEnum = {false};
                    boolean isPrimitive[] = {false};
                    checkType(p, isModel, isEnum, isPrimitive);
                    if (isModel[0]) {
                        w.write("    if (!TYPE.isSame(thisToNull(prop_" + p.name() + "), p.thisToNull(p.prop_" + p.name() + "))) return false;\n");
                        thisToNull = true;
                    } else {
                        w.write("    if (!TYPE.isSame(prop_" + p.name() + ", p.prop_" + p.name() + ")) return false;\n");
                    }
                }
                w.write("    return true;\n");
                w.write("  }\n");
                w.write("  public int hashCode() {\n");
                w.write("    int h = " + className + ".class.getName().hashCode();\n");
                for (Prprt p : props) {
                    boolean[] isModel = {false};
                    boolean[] isEnum = {false};
                    boolean isPrimitive[] = {false};
                    checkType(p, isModel, isEnum, isPrimitive);
                    if (isModel[0]) {
                        w.write("    h = TYPE.hashPlus(thisToNull(prop_" + p.name() + "), h);\n");
                    } else {
                        w.write("    h = TYPE.hashPlus(prop_" + p.name() + ", h);\n");
                    }
                }
                w.write("    return h;\n");
                w.write("  }\n");
                if (thisToNull) {
                    w.write("  private Object thisToNull(Object value) {\n");
                    w.write("    return value == this || value == TYPE ? null : value;\n");
                    w.write("  }\n");
                }
                w.write("}\n");
            } finally {
                w.close();
            }
        } catch (IOException ex) {
            error("Can't create " + className + ".java", e);
            return false;
        }
        return ok;
    }