private JavaClass generateWriter()

in tooling/maven/camel-package-maven-plugin/src/main/java/org/apache/camel/maven/packaging/ModelWriterGeneratorMojo.java [182:600]


    private JavaClass generateWriter(List<Class<?>> model, ClassLoader classLoader) {
        JavaClass writer = new JavaClass(classLoader);
        writer.setMaxImportPerPackage(4);
        writer.setPackage(getWriterPackage());
        writer.setName("ModelWriter");
        writer.extendSuperType("BaseWriter");
        writer.addImport(XmlModelWriterGeneratorMojo.MODEL_PACKAGE + ".OptionalIdentifiedDefinition");
        writer.addImport(IOException.class);
        writer.addImport(Array.class);
        writer.addImport(List.class);
        writer.addImport(ArrayList.class);
        writer.addImport("org.apache.camel.Expression");
        writer.addAnnotation(SuppressWarnings.class).setLiteralValue("\"all\"");

        writer.addMethod()
                .setConstructor(true)
                .setPublic()
                .setName("ModelWriter")
                .addParameter(Writer.class, "writer")
                .addParameter(String.class, "namespace")
                .addThrows(IOException.class)
                .setBody(
                        "super(writer, namespace);");
        writer.addMethod()
                .setConstructor(true)
                .setPublic()
                .setName("ModelWriter")
                .addParameter(Writer.class, "writer")
                .addThrows(IOException.class)
                .setBody("super(writer, null);");

        List<Class<?>> rootElements = model.stream().filter(this::isRootElement).toList();

        for (Class<?> clazz : rootElements) {
            String element = clazz.getAnnotation(XmlRootElement.class).name();
            String name = clazz.getSimpleName();
            writer.addMethod().setPublic()
                    .addParameter(clazz, "def")
                    .setReturnType(Void.TYPE)
                    .setName("write" + name)
                    .addThrows(IOException.class)
                    .setBody(
                            "doWrite" + name + "(\"" + element + "\", def);");

        }

        Set<Class<?>> elementRefs = new TreeSet<>(Comparator.comparing(Class::getName));

        // Special case for OptionalIdentifiedDefinition
        model.stream().filter(cl -> "OptionalIdentifiedDefinition".equals(cl.getSimpleName()))
                .forEach(elementRefs::add);
        writer.addMethod()
                .setSignature(
                        "public void writeOptionalIdentifiedDefinitionRef(OptionalIdentifiedDefinition def) throws IOException")
                .setBody("doWriteOptionalIdentifiedDefinitionRef(null, def);");

        for (Class<?> clazz : model) {
            if (clazz.getAnnotation(XmlEnum.class) != null || clazz.isInterface()) {
                continue;
            }
            String name = clazz.getSimpleName();
            String qname;
            if (clazz.getDeclaringClass() != null) {
                writer.addImport(clazz.getDeclaringClass());
                qname = clazz.getDeclaringClass().getSimpleName() + "." + name;
            } else {
                writer.addImport(clazz);
                qname = name;
            }
            boolean hasDerived = model.stream().anyMatch(cl -> cl.getSuperclass() == clazz);

            List<Property> members = getProperties(clazz).toList();

            // XmlAttribute
            List<String> attributes = new ArrayList<>();
            // call super class attributes writer
            getClassAndSuper(clazz.getSuperclass())
                    .filter(c -> getProperties(c).anyMatch(Property::isAttribute))
                    .findFirst()
                    .ifPresent(cl -> attributes.add("doWrite" + cl.getSimpleName() + "Attributes(def);"));
            // Add attributes
            List<Property> attributeMembers = members.stream().filter(Property::isAttribute).toList();
            attributeMembers.forEach(member -> {
                Type pt = member.getType();
                GenericType type = new GenericType(pt);
                String an = member.getAnnotation(XmlAttribute.class).name();
                if ("##default".equals(an)) {
                    an = member.getName();
                }
                String gn = member.getGetter();
                attributes.add("doWriteAttribute(\"" + an + "\", "
                               + conversion(writer, type, "def." + gn + "()", clazz.getName()) + ");");
            });

            // @XmlAnyAttribute
            members.stream().filter(Property::isAnyAttribute).forEach(member -> {
                throw new UnsupportedOperationException(
                        "Class " + clazz.getName() + " / member " + member + ": unsupported @XmlAnyAttribute");
            });

            List<String> elements = new ArrayList<>();
            // Add super class element writer
            getClassAndSuper(clazz.getSuperclass())
                    .filter(c -> getProperties(c).anyMatch(Property::isElement))
                    .findFirst()
                    .ifPresent(cl -> {
                        // special for namespace
                        if ("NamespaceAwareExpression".equals(cl.getSimpleName())) {
                            attributes.add("doWriteNamespaces(def);");
                        } else {
                            elements.add("doWrite" + cl.getSimpleName() + "Elements(def);");
                        }
                    });
            // Loop through elements
            List<Property> elementMembers = members.stream().filter(Property::isElement).toList();
            elementMembers.forEach(member -> {
                Type pt = member.getType();
                GenericType type = new GenericType(pt);
                boolean list = type.getRawClass() == List.class;
                String gn = member.getGetter();
                String pn = member.getName();
                Class<?> root = list ? type.getActualTypeArgument(0).getRawClass() : type.getRawClass();
                if (member.getAnnotation(XmlElementRefs.class) != null) {
                    elements.add("// TODO: @XmlElementRefs: " + member);
                } else if (member.getAnnotation(XmlElementRef.class) != null) {
                    //elements.add("// @XmlElementRef: " + member);
                    if (list) {
                        Class<?> parent = new GenericType(member.getType()).getActualTypeArgument(0).getRawClass();
                        elementRefs.add(parent);
                        elements.add(
                                "doWriteList(null, null, def." + gn + "(), this::doWrite" + parent.getSimpleName() + "Ref);");
                    } else {
                        Class<?> parent = new GenericType(member.getType()).getRawClass();
                        elementRefs.add(parent);
                        elements.add("doWriteElement(null, def." + gn + "(), this::doWrite" + parent.getSimpleName() + "Ref);");
                    }
                } else if (member.getAnnotation(XmlElements.class) != null) {
                    if (list) {
                        // elements.add("// @XmlElements: " + member);
                        elements.add("doWriteList(null, null, def." + gn + "(), (n, v) -> {");
                        elements.add("    switch (v.getClass().getSimpleName()) {");
                        for (XmlElement elem : member.getAnnotation(XmlElements.class).value()) {
                            String t = elem.type().getSimpleName();
                            String n = elem.name();
                            elements.add("        case \"" + t + "\" -> doWrite" + t + "(\"" + n + "\", (" + t + ") v);");
                        }
                        elements.add("    }");
                        elements.add("});");
                    } else {
                        // elements.add("// @XmlElements: " + member);
                        elements.add("doWriteElement(null, def." + gn + "(), (n, v) -> {");
                        elements.add("    switch (v.getClass().getSimpleName()) {");
                        for (XmlElement elem : member.getAnnotation(XmlElements.class).value()) {
                            String t = elem.type().getSimpleName();
                            String n = elem.name();
                            elements.add("        case \"" + t + "\" -> doWrite" + t + "(\"" + n + "\", (" + t + ") def." + gn
                                         + "());");
                        }
                        elements.add("    }");
                        elements.add("});");
                    }
                } else if (member.getAnnotation(XmlElement.class) != null) {
                    String t = root.getSimpleName();
                    String n = member.getAnnotation(XmlElement.class).name();
                    if ("##default".equals(n)) {
                        n = pn;
                    }
                    // elements.add("// @XmlElement: " + member);
                    if (list) {
                        String w = member.getAnnotation(XmlElementWrapper.class) != null
                                ? member.getAnnotation(XmlElementWrapper.class).name() : null;
                        elements.add("doWriteList(" + (w != null ? "\"" + w + "\"" : "null") + ", " +
                                     "\"" + n + "\", def." + gn + "(), this::doWrite" + t + ");");
                    } else {
                        XmlJavaTypeAdapter adapter = member.getAnnotation(XmlJavaTypeAdapter.class);
                        if (adapter != null) {
                            Class<? extends XmlAdapter> cl = adapter.value();
                            Class<?> actualType = null;
                            for (Method m : cl.getDeclaredMethods()) {
                                if (m.getName().equals("marshal") && m.getReturnType() != Object.class) {
                                    actualType = m.getReturnType();
                                    break;
                                }
                            }
                            if (actualType == null) {
                                throw new IllegalArgumentException(
                                        "Unable to determine property name for JAXB" +
                                                                   " adapted member: " + member);
                            }
                            elements.add("doWriteElement(" +
                                         "\"" + n + "\", new " + cl.getSimpleName() + "().marshal(def." + gn
                                         + "()), this::doWrite" + actualType.getSimpleName() + ");");
                        } else {
                            elements.add("doWriteElement(" +
                                         "\"" + n + "\", def." + gn + "(), this::doWrite" + t + ");");
                        }
                    }
                } else if (member.getAnnotation(XmlAnyElement.class) != null) {
                    elements.add("domElements(def." + gn + "());");
                } else {
                    String t = root.getSimpleName();
                    String n = root.getAnnotation(XmlRootElement.class) != null
                            ? root.getAnnotation(XmlRootElement.class).name() : "##default";
                    if ("##default".equals(n)) {
                        // TODO: handle default name
                    }
                    // elements.add("// " + member);
                    if (list) {
                        elements.add("doWriteList(\"" + pn + "\", " +
                                     "\"" + n + "\", def." + gn + "(), this::doWrite" + t + ");");
                    } else {
                        elements.add("doWriteElement(\"" + pn + "\", def." + gn + "(), this::doWrite" + t + ");");
                    }
                }
            });

            // @XmlValue
            List<String> value = getClassAndSuper(clazz).flatMap(this::getProperties)
                    .filter(Property::isValue).findFirst()
                    .map(member -> "doWriteValue(def." + member.getGetter() + "());")
                    .stream().toList();

            String qgname = qname;
            if (clazz.getTypeParameters().length > 0) {
                qgname = qname + "<" + Stream.of(clazz.getTypeParameters()).map(t -> "?")
                        .collect(Collectors.joining(", ")) + ">";
            }
            if (!attributeMembers.isEmpty() || !elementMembers.isEmpty() || isRootElement(clazz)
                    || isReferenced(clazz, model)) {
                List<String> statements = new ArrayList<>();
                if ("ExpressionSubElementDefinition".equals(name)) {
                    statements.add("startExpressionElement(name);");
                } else {
                    statements.add("startElement(name);");
                }
                // Attributes
                if (hasDerived && !attributes.isEmpty()) {
                    writer.addMethod()
                            .setProtected()
                            .setReturnType(Void.TYPE)
                            .setName("doWrite" + name + "Attributes")
                            .addParameter(qgname, "def")
                            .addThrows(IOException.class)
                            .setBody(String.join("\n", attributes));
                    statements.add("doWrite" + name + "Attributes(def);");
                } else {
                    statements.addAll(attributes);
                }
                // Value
                statements.addAll(value);
                // Elements
                elements.sort((e1, e2) -> {
                    // sort so output is last
                    boolean l1 = e1.startsWith("doWriteList(null, null, def.getOutputs()");
                    boolean l2 = e2.startsWith("doWriteList(null, null, def.getOutputs()");
                    return Boolean.compare(l1, l2);
                });
                if (hasDerived && !elements.isEmpty()) {
                    writer.addMethod()
                            .setProtected()
                            .setReturnType(Void.TYPE)
                            .setName("doWrite" + name + "Elements")
                            .addParameter(qgname, "def")
                            .addThrows(IOException.class)
                            .setBody(String.join("\n", elements));
                    statements.add("doWrite" + name + "Elements(def);");
                } else {
                    statements.addAll(elements);
                }
                if ("ExpressionSubElementDefinition".equals(name)) {
                    statements.add("endExpressionElement(name);");
                } else {
                    statements.add("endElement(name);");
                }
                writer.addMethod()
                        .setProtected()
                        .setReturnType(Void.TYPE)
                        .setName("doWrite" + name)
                        .addParameter(String.class, "name")
                        .addParameter(qgname, "def")
                        .addThrows(IOException.class)
                        .setBody(String.join("\n", statements));
            }
        }

        elementRefs.forEach(clazz -> {
            List<String> elements = new ArrayList<>();
            elements.add("if (v != null) {");
            elements.add("    switch (v.getClass().getSimpleName()) {");
            model.stream()
                    .filter(c -> c.getAnnotation(XmlRootElement.class) != null)
                    .filter(c -> getClassAndSuper(c).anyMatch(cl -> cl == clazz))
                    .forEach(cl -> {
                        String t = cl.getSimpleName();
                        String n = cl.getAnnotation(XmlRootElement.class).name();
                        if ("##default".equals(n)) {
                            n = lowercase(t);
                        }
                        elements.add("        case \"" + t + "\" -> doWrite" + t + "(\"" + n + "\", (" + t + ") v);");
                    });
            elements.add("    }");
            elements.add("}");
            String qname = clazz.getSimpleName();
            if (clazz.getTypeParameters().length > 0) {
                qname = qname + "<" + Stream.of(clazz.getTypeParameters()).map(t -> "?")
                        .collect(Collectors.joining(", ")) + ">";
            }
            writer.addMethod()
                    .setProtected()
                    .setReturnType(Void.TYPE)
                    .setName("doWrite" + clazz.getSimpleName() + "Ref")
                    .addParameter("String", "n")
                    .addParameter(qname, "v")
                    .addThrows(IOException.class)
                    .setBody(String.join("\n", elements));
        });

        writer.addMethod()
                .setProtected()
                .setReturnType(Void.TYPE)
                .setName("doWriteAttribute")
                .addParameter(String.class, "attribute")
                .addParameter(Object.class, "value")
                .addThrows(IOException.class)
                .setBody("if (value != null) {",
                        "    attribute(attribute, value);",
                        "}");
        writer.addMethod()
                .setProtected()
                .setReturnType(Void.TYPE)
                .setName("doWriteValue")
                .addParameter(String.class, "value")
                .addThrows(IOException.class)
                .setBody("if (value != null) {",
                        "    value(value);",
                        "}");
        writer.addMethod()
                .setSignature(
                        "protected <T> void doWriteList(String wrapperName, String name, List<T> list, ElementSerializer<T> elementSerializer) throws IOException")
                .setBody("""
                        if (list != null) {
                            if (wrapperName != null) {
                                startElement(wrapperName);
                            }
                            for (T v : list) {
                                elementSerializer.doWriteElement(name, v);
                            }
                            if (wrapperName != null) {
                                endElement(wrapperName);
                            }
                        }""");
        writer.addMethod()
                .setSignature(
                        "protected <T> void doWriteElement(String name, T v, ElementSerializer<T> elementSerializer) throws IOException")
                .setBody("""
                        if (v != null) {
                            elementSerializer.doWriteElement(name, v);
                        }""");
        writer.addNestedType()
                .setClass(false)
                .setAbstract(true)
                .setName("ElementSerializer<T>")
                .addMethod()
                .setAbstract()
                .setSignature("void doWriteElement(String name, T value) throws IOException");

        writer.addMethod()
                .setProtected()
                .setReturnType(String.class)
                .setName("toString")
                .addParameter("Boolean", "b")
                .setBody("return b != null ? b.toString() : null;");
        writer.addMethod()
                .setProtected()
                .setReturnType(String.class)
                .setName("toString")
                .addParameter("Enum<?>", "e")
                .setBody("return e != null ? e.name() : null;");
        writer.addMethod()
                .setProtected()
                .setReturnType(String.class)
                .setName("toString")
                .addParameter("Number", "n")
                .setBody("return n != null ? n.toString() : null;");
        writer.addImport("java.util.Base64");
        writer.addMethod()
                .setProtected()
                .setReturnType(String.class)
                .setName("toString")
                .addParameter("byte[]", "b")
                .setBody("return b != null ? Base64.getEncoder().encodeToString(b) : null;");

        writer.addMethod()
                .setProtected()
                .setReturnType(Void.TYPE)
                .setName("doWriteString")
                .addParameter(String.class, "name")
                .addParameter(String.class, "value")
                .addThrows(IOException.class)
                .setBody("if (value != null) {",
                        "    startElement(name);",
                        "    text(name, value);",
                        "    endElement(name);",
                        "}");

        writer.addMethod()
                .setProtected()
                .setReturnType(Void.TYPE)
                .setName("doWriteNamespaces")
                .addParameter("NamespaceAwareExpression", "def")
                .addThrows(IOException.class)
                .setBody("if (def.getNamespaceAsMap() != null) {",
                        "    for (var e : def.getNamespaceAsMap().entrySet()) {",
                        "        doWriteAttribute(\"xmlns:\" + e.getKey(), e.getValue());",
                        "    }",
                        "}");

        return writer;
    }