This is an automated email from the ASF dual-hosted git repository.

nizhikov pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new e8829158913 IGNITE-27775 Generate custom collection code (#12721)
e8829158913 is described below

commit e882915891346afeaaefbef9dc8a5794bf81e6f2
Author: Nikolay <[email protected]>
AuthorDate: Wed Feb 11 22:41:32 2026 +0300

    IGNITE-27775 Generate custom collection code (#12721)
---
 .../internal/idto/IDTOSerializerGenerator.java     | 188 +++++++++++++++++----
 1 file changed, 158 insertions(+), 30 deletions(-)

diff --git 
a/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java
 
b/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java
index 04957552927..bca86145da0 100644
--- 
a/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java
+++ 
b/modules/codegen/src/main/java/org/apache/ignite/internal/idto/IDTOSerializerGenerator.java
@@ -151,6 +151,15 @@ public class IDTOSerializerGenerator {
         ARRAY_TYPE_SERDES.put(long.class.getName(), F.t("U.writeLongArray(out, 
${var})", "U.readLongArray(in)"));
     }
 
+    /** Interface to implementations. */
+    private static final Map<String, String> COLL_IMPL = new HashMap<>();
+
+    {
+        COLL_IMPL.put(Collection.class.getName(), ArrayList.class.getName());
+        COLL_IMPL.put(List.class.getName(), ArrayList.class.getName());
+        COLL_IMPL.put(Set.class.getName(), HashSet.class.getName());
+    }
+
     /** Environment. */
     private final ProcessingEnvironment env;
 
@@ -163,6 +172,9 @@ public class IDTOSerializerGenerator {
     /** If {@code True} then write method generated now. */
     private boolean write;
 
+    /** Nesting level. */
+    private int level;
+
     /**
      * @param env Environment.
      * @param type Type to generate serializer for.
@@ -232,15 +244,13 @@ public class IDTOSerializerGenerator {
         if (type.getNestingKind() != NestingKind.TOP_LEVEL)
             imports.add(type.getQualifiedName().toString());
 
-        String simpleClsName = String.valueOf(type.getSimpleName());
-
         Collection<VariableElement> flds = fields(type);
 
-        List<String> write = generateWrite(simpleClsName, flds);
-        List<String> read = generateRead(simpleClsName, flds);
+        List<String> write = generateWrite(flds);
+        List<String> read = generateRead(flds);
 
         try (Writer writer = new StringWriter()) {
-            writeClassHeader(writer, simpleClsName);
+            writeClassHeader(writer);
 
             for (String line : write) {
                 writer.write(TAB);
@@ -264,10 +274,9 @@ public class IDTOSerializerGenerator {
 
     /**
      * @param writer Writer to write class to.
-     * @param simpleClsName Class name
      * @throws IOException  In case of error.
      */
-    private void writeClassHeader(Writer writer, String simpleClsName) throws 
IOException {
+    private void writeClassHeader(Writer writer) throws IOException {
         try (InputStream in = 
getClass().getClassLoader().getResourceAsStream("license.txt");
              BufferedReader reader = new BufferedReader(new 
InputStreamReader(in))) {
 
@@ -289,17 +298,17 @@ public class IDTOSerializerGenerator {
         writer.write(CLS_JAVADOC);
         writer.write(NL);
         writer.write("public class " + serializerName() + " implements " + 
simpleName(DTO_SERDES_INTERFACE) +
-            "<" + simpleClsName + "> {" + NL);
+            "<" + typeWithGeneric() + "> {" + NL);
     }
 
     /** @return Lines for generated {@code 
IgniteDataTransferObjectSerializer#writeExternal(T, ObjectOutput)} method. */
-    private List<String> generateWrite(String clsName, 
Collection<VariableElement> flds) {
+    private List<String> generateWrite(Collection<VariableElement> flds) {
         write = true;
 
         List<String> code = new ArrayList<>();
 
         code.add("/** {@inheritDoc} */");
-        code.add("@Override public void writeExternal(" + clsName + " obj, 
ObjectOutput out) throws IOException {");
+        code.add("@Override public void writeExternal(" + typeWithGeneric() + 
" obj, ObjectOutput out) throws IOException {");
 
         fieldsSerdes(flds).forEach(line -> code.add(TAB + line));
 
@@ -309,13 +318,14 @@ public class IDTOSerializerGenerator {
     }
 
     /** @return Lines for generated {@code 
IgniteDataTransferObjectSerializer#readExternal(T, ObjectInput)} method. */
-    private List<String> generateRead(String clsName, 
Collection<VariableElement> flds) {
+    private List<String> generateRead(Collection<VariableElement> flds) {
         write = false;
 
         List<String> code = new ArrayList<>();
 
         code.add("/** {@inheritDoc} */");
-        code.add("@Override public void readExternal(" + clsName + " obj, 
ObjectInput in) throws IOException, ClassNotFoundException {");
+        code.add("@Override public void readExternal(" + typeWithGeneric() + " 
obj, ObjectInput in) " +
+            "throws IOException, ClassNotFoundException {");
 
         fieldsSerdes(flds).forEach(line -> code.add(TAB + line));
 
@@ -343,12 +353,14 @@ public class IDTOSerializerGenerator {
         String var
     ) {
         TypeMirror dtoCls = 
env.getElementUtils().getTypeElement(DTO_CLASS).asType();
+        TypeMirror coll = 
env.getElementUtils().getTypeElement(Collection.class.getName()).asType();
 
         IgniteBiTuple<String, String> serDes = null;
 
         if (type.getKind() == TypeKind.ARRAY)
             return arrayCode(type, var);
-
+        else if 
(env.getTypeUtils().isAssignable(env.getTypeUtils().erasure(type), coll))
+            return collectionCode(type, var);
         else if (env.getTypeUtils().isAssignable(type, dtoCls))
             serDes = OBJECT_SERDES;
         else if (type.getKind() == TypeKind.TYPEVAR)
@@ -376,8 +388,7 @@ public class IDTOSerializerGenerator {
             }
         }
 
-        if (serDes == null)
-            throw new IllegalStateException("Unsupported type: " + type);
+        throwIfNull(type, serDes);
 
         boolean notNull = type.toString().startsWith("@" + 
NotNull.class.getName());
 
@@ -408,9 +419,82 @@ public class IDTOSerializerGenerator {
         return code.map(line -> replacePlaceholders(line, var, type));
     }
 
+    /**
+     * @param type Collection type
+     * @param var Variable to read(write) from(to).
+     * @return Serdes code for collection.
+     */
+    private Stream<String> collectionCode(TypeMirror type, String var) {
+        DeclaredType dt = (DeclaredType)type;
+
+        assert dt.getTypeArguments().size() == 1;
+
+        TypeMirror colEl = dt.getTypeArguments().get(0);
+
+        if (!TYPE_SERDES.containsKey(className(colEl)) && !enumType(env, 
colEl)) {
+            // Ignite can't serialize collections elements efficiently.
+            IgniteBiTuple<String, String> serDes = 
TYPE_SERDES.get(className(type));
+
+            throwIfNull(type, serDes);
+
+            return Stream.of((write ? serDes.get1() : simpleRead(serDes)) + 
";")
+                .map(line -> replacePlaceholders(line, var, type));
+        }
+
+        Stream<String> res;
+
+        String el = "el" + (level == 0 ? "" : level);
+
+        Map<String, String> params = Map.of(
+            "${var}", var,
+            "${Type}", colEl.toString(),
+            "${CollectionImpl}", simpleName(COLL_IMPL.get(className(type))),
+            "${len}", "len" + (level == 0 ? "" : level),
+            "${i}", "i" + (level == 0 ? "" : level),
+            "${iter}", "iter" + (level == 0 ? "" : level),
+            "${el}", el
+        );
+
+        level++;
+
+        if (write) {
+            res = Stream.of("{",
+                TAB + "int ${len} = ${var} == null ? -1 : ${var}.size();",
+                TAB + "out.writeInt(${len});",
+                TAB + "if (${len} > 0) {",
+                TAB + TAB + "for (${Type} ${el} : ${var}) {");
+
+            res = Stream.concat(res, variableCode(colEl, el).map(line -> TAB + 
TAB + TAB + line));
+            res = Stream.concat(res, Stream.of(TAB + TAB + "}", TAB + "}", 
"}"));
+        }
+        else {
+            String implCls = COLL_IMPL.get(className(type));
+
+            imports.add(implCls);
+
+            assert implCls != null;
+
+            res = Stream.of("{",
+                TAB + "int ${len} = in.readInt();",
+                TAB + "if (${len} >= 0) {",
+                TAB + TAB + "${var} = new ${CollectionImpl}<>();",
+                TAB + TAB + "for (int ${i} = 0; ${i} < ${len}; ${i}++) {",
+                TAB + TAB + TAB + "${Type} ${el} = null;");
+
+            res = Stream.concat(res, variableCode(colEl, el).map(line -> TAB + 
TAB + TAB + line));
+            res = Stream.concat(res, Stream.of(TAB + TAB + TAB + 
"${var}.add(${el});"));
+            res = Stream.concat(res, Stream.of(TAB + TAB + "}", TAB + "}", 
"}"));
+        }
+
+        level--;
+
+        return res.map(line -> replacePlaceholders(line, params));
+    }
+
     /**
      * @param type Array type.
-     * @return Serdes tuple for array.
+     * @param var Variable to read(write) from(to).
+     * @return Serdes code for array.
      */
     private Stream<String> arrayCode(TypeMirror type, String var) {
         TypeMirror comp = ((ArrayType)type).getComponentType();
@@ -431,26 +515,31 @@ public class IDTOSerializerGenerator {
 
         if (write) {
             res = Stream.of("{",
-                TAB + "int len = ${var} == null ? -1 : ${var}.length;",
-                TAB + "out.writeInt(len);",
-                TAB + "if (len > 0) {",
-                TAB + TAB + "for (int i=0; i<len; i++) {");
+                TAB + "int ${len} = ${var} == null ? -1 : ${var}.length;",
+                TAB + "out.writeInt(${len});",
+                TAB + "if (${len} > 0) {",
+                TAB + TAB + "for (int ${i} = 0; ${i} < ${len}; ${i}++) {");
 
-            res = Stream.concat(res, variableCode(comp, var + "[i]").map(line 
-> TAB + TAB + TAB + line));
+            res = Stream.concat(res, variableCode(comp, var + 
"[${i}]").map(line -> TAB + TAB + TAB + line));
             res = Stream.concat(res, Stream.of(TAB + TAB + "}", TAB + "}", 
"}"));
         }
         else {
             res = Stream.of("{",
-                TAB + "int len = in.readInt();",
-                TAB + "if (len >= 0) {",
-                TAB + TAB + "${var} = new ${Type}[len];",
-                TAB + TAB + "for (int i=0; i<len; i++) {");
+                TAB + "int ${len} = in.readInt();",
+                TAB + "if (${len} >= 0) {",
+                TAB + TAB + "${var} = new ${Type}[${len}];",
+                TAB + TAB + "for (int ${i} = 0; ${i} < ${len}; ${i}++) {");
 
-            res = Stream.concat(res, variableCode(comp, var + "[i]").map(line 
-> TAB + TAB + TAB + line));
+            res = Stream.concat(res, variableCode(comp, var + 
"[${i}]").map(line -> TAB + TAB + TAB + line));
             res = Stream.concat(res, Stream.of(TAB + TAB + "}", TAB + "}", 
"}"));
         }
 
-        return res.map(line -> replacePlaceholders(line, var, comp));
+        return res.map(line -> replacePlaceholders(line, Map.of(
+            "${var}", var,
+            "${Type}", simpleClassName(comp),
+            "${len}", "len" + (level == 0 ? "" : level),
+            "${i}", "i" + (level == 0 ? "" : level)
+        )));
     }
 
     /** @return List of non-static and non-transient field for given {@code 
type}. */
@@ -529,6 +618,28 @@ public class IDTOSerializerGenerator {
         return simpleName(fqn);
     }
 
+    /** @return Simple class name. */
+    private String typeWithGeneric() {
+        TypeMirror tp = type.asType();
+
+        if (!(tp instanceof DeclaredType))
+            return simpleClassName(tp);
+
+        DeclaredType dt = (DeclaredType)tp;
+
+        if (F.size(dt.getTypeArguments()) == 0)
+            return simpleClassName(tp);
+
+        StringBuilder generic = new StringBuilder("<Object");
+
+        for (int i = 1; i < F.size(dt.getTypeArguments()); i++)
+            generic.append(", Object");
+
+        generic.append(">");
+
+        return simpleClassName(tp) + generic;
+    }
+
     /** @return Simple class name. */
     public static String simpleName(String fqn) {
         return fqn.substring(fqn.lastIndexOf('.') + 1);
@@ -536,13 +647,30 @@ public class IDTOSerializerGenerator {
 
     /** Replaces placeholders to current values. */
     private String replacePlaceholders(String line, String var, TypeMirror 
type) {
-        return line
-            .replaceAll("\\$\\{var}", var)
-            .replaceAll("\\$\\{Type}", simpleClassName(type));
+        return replacePlaceholders(line, Map.of("${var}", var, "${Type}", 
simpleClassName(type)));
+    }
+
+    /** Replaces placeholders to current values. */
+    private String replacePlaceholders(String line, Map<String, String> subst) 
{
+        for (Map.Entry<String, String> e : subst.entrySet()) {
+            String line0 = line;
+            do {
+                line = line0;
+                line0 = line.replace(e.getKey(), e.getValue());
+            } while (!line0.equals(line));
+        }
+
+        return line;
     }
 
     /** */
     private static String simpleRead(IgniteBiTuple<String, String> serDes) {
         return "${var} = " + serDes.get2();
     }
+
+    /** */
+    private static void throwIfNull(TypeMirror type, IgniteBiTuple<String, 
String> serDes) {
+        if (serDes == null)
+            throw new IllegalStateException("Unsupported type: " + type);
+    }
 }

Reply via email to