ibessonov commented on a change in pull request #366:
URL: https://github.com/apache/ignite-3/pull/366#discussion_r739135292



##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
##########
@@ -637,125 +453,431 @@ public static boolean hasDefault(Field field) {
     }
 
     /**
-     * Get the default value of a {@link Value}.
+     * Collect all configuration schemes with {@link ConfigurationRoot}, 
{@link Config} or {@link PolymorphicConfig}

Review comment:
       Please either use "schemes" or "schemas". I don't like inconsistencies 
that we have right now

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -555,46 +801,73 @@ else if (isNamedConfigValue(schemaField))
             returnType
         );
 
-        // result = this.field;
-        viewMtd.getBody().append(viewMtd.getThis().getField(fieldDef));
+        BytecodeBlock bytecodeBlock = new BytecodeBlock();
+
+        // result = this.field; OR this.field.field.
+        bytecodeBlock.append(getFieldCodeFun.apply(viewMtd));
 
-        // result = Box.boxValue(result); // Unboxing.
         if (schemaFieldType.isPrimitive()) {
-            viewMtd.getBody().invokeVirtual(
+            // result = Box.boxValue(result); // Unboxing.
+            bytecodeBlock.invokeVirtual(
                 box(schemaFieldType),
                 schemaFieldType.getSimpleName() + "Value",
                 schemaFieldType
             );
         }
-
-        // retuls = result.clone();
-        if (schemaFieldType.isArray())
-            viewMtd.getBody().invokeVirtual(schemaFieldType, "clone", 
Object.class).checkCast(schemaFieldType);
+        else if (schemaFieldType.isArray()) {
+            // result = result.clone();
+            bytecodeBlock.invokeVirtual(schemaFieldType, "clone", 
Object.class).checkCast(schemaFieldType);
+        }
+        else if (isPolymorphicConfig(schemaFieldType) && 
isConfigValue(schemaField)) {
+            // result = result.specificNode();
+            bytecodeBlock.invokeVirtual(SPECIFIC_NODE_MTD);
+        }
 
         // return result;
-        viewMtd.getBody().ret(schemaFieldType);
+        bytecodeBlock.ret(schemaFieldType);
+
+        if (getPolymorphicTypeIdFieldFun != null) {
+            assert 
isPolymorphicConfigInstance(schemaField.getDeclaringClass()) : schemaField;
+
+            // tmpVar = this.typeId; OR this.field.typeId.
+            BytecodeExpression getPolymorphicTypeIdField = 
getPolymorphicTypeIdFieldFun.apply(viewMtd);
+            String polymorphicInstanceId = 
polymorphicInstanceId(schemaField.getDeclaringClass());
+
+            // if ("first".equals(tmpVar)) return result;
+            // else throw Ex;
+            viewMtd.getBody().append(
+                new IfStatement()
+                    
.condition(constantString(polymorphicInstanceId).invoke(STRING_EQUALS_MTD, 
getPolymorphicTypeIdField))
+                    .ifTrue(bytecodeBlock)
+                    
.ifFalse(throwException(ConfigurationWrongPolymorphicTypeIdException.class, 
getPolymorphicTypeIdField))

Review comment:
       Shouldn't it be the value of the field, not the name? Name says nothing 
useful

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
##########
@@ -120,6 +126,11 @@
 
             if (oldNode == null)
                 visitAsymmetricInnerNode(newNode, false);
+            else if (oldNode.schemaType() != newNode.schemaType()) {
+                visitAsymmetricInnerNode(oldNode, true);

Review comment:
       I don't see a comment with an explanation here, maybe it should be 
extracted into a method?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
##########
@@ -150,6 +161,10 @@ else if (node == null)
                         throw new KeyNotFoundException(
                             "Configuration value '" + join(keys.subList(0, i)) 
+ "' has not been found"
                         );
+                    } catch (ConfigurationWrongPolymorphicTypeIdException e) {
+                        throw new WrongPolymorphicTypeIdException(
+                            "Polymorphic configuration type is not correct: " 
+ e.getMessage()

Review comment:
       should you also pass a cause?

##########
File path: 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
##########
@@ -185,15 +227,16 @@ public void before() throws Exception {
         configuration.change(cfg -> cfg
             .changePrimitivesList(list -> 
list.namedListKeys().forEach(list::delete))
             .changeArraysList(list -> 
list.namedListKeys().forEach(list::delete))
+            .changePolymorphicCfg(c -> {})
         ).get(1, SECONDS);
     }
 
     /** */
     @Test
     public void toHoconBasic() {
-        assertEquals("root{arraysList=[],primitivesList=[]}", 
asHoconStr(List.of()));
+        
assertEquals("root{arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]}",
 asHoconStr(List.of()));

Review comment:
       I wander if we could render these values in a proper order

##########
File path: 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
##########
@@ -614,34 +656,176 @@ void testCollectSchemas() {
 
         assertThrows(IllegalArgumentException.class, () -> 
collectSchemas(List.of(Object.class)));
 
+        Set<Class<?>> schemas = Set.of(
+            LocalFirstConfigurationSchema.class,
+            InternalConfigurationSchema.class,
+            PolymorphicConfigurationSchema.class
+        );
+
+        assertEquals(schemas, collectSchemas(schemas));
+
         assertEquals(
-            Set.of(LocalFirstConfigurationSchema.class, 
SimpleConfigurationSchema.class),
-            collectSchemas(List.of(LocalFirstConfigurationSchema.class, 
SimpleConfigurationSchema.class))
+            Set.of(
+                InternalRootConfigurationSchema.class,
+                PolymorphicRootConfigurationSchema.class,
+                InternalConfigurationSchema.class,
+                PolymorphicConfigurationSchema.class
+            ),
+            collectSchemas(List.of(InternalRootConfigurationSchema.class, 
PolymorphicRootConfigurationSchema.class))
+        );
+    }
+
+    /** */
+    @Test
+    void testPolymorphicSchemaExtensions() {
+        assertTrue(polymorphicSchemaExtensions(List.of()).isEmpty());
+
+        assertThrows(IllegalArgumentException.class, () -> 
polymorphicSchemaExtensions(List.of(Object.class)));
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> 
polymorphicSchemaExtensions(List.of(LocalFirstConfigurationSchema.class))
+        );
+
+        Set<Class<?>> extensions = Set.of(
+            FirstPolymorphicInstanceConfigurationSchema.class,
+            SecondPolymorphicInstanceConfigurationSchema.class
         );
 
         assertEquals(
-            Set.of(SimpleRootConfigurationSchema.class, 
SimpleConfigurationSchema.class),
-            collectSchemas(List.of(SimpleRootConfigurationSchema.class))
+            Map.of(PolymorphicConfigurationSchema.class, extensions),
+            polymorphicSchemaExtensions(extensions)
         );
     }
 
+    /** */
+    @Test
+    void testCompressDeletedEntries() {
+        Map<String, String> containsNullLeaf = new HashMap<>();
+
+        containsNullLeaf.put("first", "1");
+        containsNullLeaf.put("second", null);
+
+        Map<String, String> deletedNamedListElement = new HashMap<>();
+
+        deletedNamedListElement.put("third", null);
+        deletedNamedListElement.put(NAME, null);
+
+        Map<String, Object> regular = new HashMap<>();
+
+        regular.put("strVal", "foo");
+        regular.put("intVal", 10);
+
+        Map<String, Object> prefixMap = new HashMap<>();
+
+        prefixMap.put("0", containsNullLeaf);
+        prefixMap.put("1", deletedNamedListElement);
+        prefixMap.put("2", regular);
+
+        Map<String, Object> exp = new HashMap<>();
+
+        exp.put("0", Map.of("first", "1"));
+        exp.put("1", null);
+        exp.put("2", Map.of("strVal", "foo", "intVal", 10));
+
+        compressDeletedEntries(prefixMap);
+
+        assertEquals(exp, prefixMap);
+    }
+
+    /** */
+    @Test
+    void testFlattenedMapPolymorphicConfig() {
+        InnerNode polymorphicRootInnerNode = 
newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+        addDefaults(polymorphicRootInnerNode);
+
+        RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+        SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, 
polymorphicRootInnerNode));
+
+        Map<String, Serializable> act = flattenedMap(
+            superRoot,
+            rootKey,
+            node -> {
+                ((PolymorphicRootChange)node).changePolymorphicSubCfg(c -> 
c.convert(SecondPolymorphicInstanceChange.class));
+
+                addDefaults(node);

Review comment:
       Please remove this "addDefault", I believe it's not required

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
##########
@@ -150,6 +161,10 @@ else if (node == null)
                         throw new KeyNotFoundException(
                             "Configuration value '" + join(keys.subList(0, i)) 
+ "' has not been found"
                         );
+                    } catch (ConfigurationWrongPolymorphicTypeIdException e) {

Review comment:
       ```suggestion
                       }
                       catch (ConfigurationWrongPolymorphicTypeIdException e) {
   ```

##########
File path: 
modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
##########
@@ -51,7 +53,7 @@ void testConcatIterables() {
 
     /** */
     @Test
-    void testUnion() {
+    void testSetUnion() {

Review comment:
       Can you please explain, why you need explicit arrays allocations in this 
test if "union" is vararg?

##########
File path: 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
##########
@@ -50,7 +55,51 @@ void testValidationInternalConfigurationExtensions() {
             List.of(FirstRootConfiguration.KEY, SecondRootConfiguration.KEY),
             Map.of(),
             new TestConfigurationStorage(LOCAL),
-            List.of(ExtendedFirstRootConfigurationSchema.class)
+            List.of(ExtendedFirstRootConfigurationSchema.class),
+            List.of()
+        );
+    }
+
+    /** */
+    @Test
+    void testValidationPolymorphicConfigurationExtensions() {
+        // There is a polymorphic extension that is missing from the schema.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> new ConfigurationRegistry(
+                List.of(ThirdRootConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of(Second0PolymorphicConfigurationSchema.class)
+            )
+        );
+
+        // There are two polymorphic extensions with the same id.
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> new ConfigurationRegistry(
+                List.of(ThirdRootConfiguration.KEY),
+                Map.of(),
+                new TestConfigurationStorage(LOCAL),
+                List.of(),
+                List.of(First0PolymorphicConfigurationSchema.class, 
ErrorFirst0PolymorphicConfigurationSchema.class)
+            )
+        );
+
+        // Check that everything is fine.
+        new ConfigurationRegistry(

Review comment:
       You should probably stop this instance, because it has a thread pool in 
it

##########
File path: 
modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
##########
@@ -614,34 +656,176 @@ void testCollectSchemas() {
 
         assertThrows(IllegalArgumentException.class, () -> 
collectSchemas(List.of(Object.class)));
 
+        Set<Class<?>> schemas = Set.of(
+            LocalFirstConfigurationSchema.class,
+            InternalConfigurationSchema.class,
+            PolymorphicConfigurationSchema.class
+        );
+
+        assertEquals(schemas, collectSchemas(schemas));
+
         assertEquals(
-            Set.of(LocalFirstConfigurationSchema.class, 
SimpleConfigurationSchema.class),
-            collectSchemas(List.of(LocalFirstConfigurationSchema.class, 
SimpleConfigurationSchema.class))
+            Set.of(
+                InternalRootConfigurationSchema.class,
+                PolymorphicRootConfigurationSchema.class,
+                InternalConfigurationSchema.class,
+                PolymorphicConfigurationSchema.class
+            ),
+            collectSchemas(List.of(InternalRootConfigurationSchema.class, 
PolymorphicRootConfigurationSchema.class))
+        );
+    }
+
+    /** */
+    @Test
+    void testPolymorphicSchemaExtensions() {
+        assertTrue(polymorphicSchemaExtensions(List.of()).isEmpty());
+
+        assertThrows(IllegalArgumentException.class, () -> 
polymorphicSchemaExtensions(List.of(Object.class)));
+
+        assertThrows(
+            IllegalArgumentException.class,
+            () -> 
polymorphicSchemaExtensions(List.of(LocalFirstConfigurationSchema.class))
+        );
+
+        Set<Class<?>> extensions = Set.of(
+            FirstPolymorphicInstanceConfigurationSchema.class,
+            SecondPolymorphicInstanceConfigurationSchema.class
         );
 
         assertEquals(
-            Set.of(SimpleRootConfigurationSchema.class, 
SimpleConfigurationSchema.class),
-            collectSchemas(List.of(SimpleRootConfigurationSchema.class))
+            Map.of(PolymorphicConfigurationSchema.class, extensions),
+            polymorphicSchemaExtensions(extensions)
         );
     }
 
+    /** */
+    @Test
+    void testCompressDeletedEntries() {
+        Map<String, String> containsNullLeaf = new HashMap<>();
+
+        containsNullLeaf.put("first", "1");
+        containsNullLeaf.put("second", null);
+
+        Map<String, String> deletedNamedListElement = new HashMap<>();
+
+        deletedNamedListElement.put("third", null);
+        deletedNamedListElement.put(NAME, null);
+
+        Map<String, Object> regular = new HashMap<>();
+
+        regular.put("strVal", "foo");
+        regular.put("intVal", 10);
+
+        Map<String, Object> prefixMap = new HashMap<>();
+
+        prefixMap.put("0", containsNullLeaf);
+        prefixMap.put("1", deletedNamedListElement);
+        prefixMap.put("2", regular);
+
+        Map<String, Object> exp = new HashMap<>();
+
+        exp.put("0", Map.of("first", "1"));
+        exp.put("1", null);
+        exp.put("2", Map.of("strVal", "foo", "intVal", 10));
+
+        compressDeletedEntries(prefixMap);
+
+        assertEquals(exp, prefixMap);
+    }
+
+    /** */
+    @Test
+    void testFlattenedMapPolymorphicConfig() {
+        InnerNode polymorphicRootInnerNode = 
newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+        addDefaults(polymorphicRootInnerNode);
+
+        RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+        SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, 
polymorphicRootInnerNode));
+
+        Map<String, Serializable> act = flattenedMap(
+            superRoot,
+            rootKey,
+            node -> {
+                ((PolymorphicRootChange)node).changePolymorphicSubCfg(c -> 
c.convert(SecondPolymorphicInstanceChange.class));
+
+                addDefaults(node);

Review comment:
       Maybe others as well, please check them

##########
File path: 
modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicInstance.java
##########
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Marker interface for {@code *Node} classes whose schemas are marked with 
annotation {@link PolymorphicConfigInstance},

Review comment:
       Would it be better to rename it to PolymorphicNode?
   Anyway, this interface is designed for VIEW / CHANGE interfaces, not the 
node itself, right?

##########
File path: 
modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java
##########
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link 
ConfigurationRoot}.

Review comment:
       Why tho? Maybe we should revisit this option in the future

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1866,932 @@ private static String capitalize(String name) {
     }
 
     /**
-     * Create bytecode blocks that invokes of {@link ConfigurationVisitor}'s 
methods for
-     * {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)}.
+     * Get interfaces for {@link InnerNode} definition for a configuration 
schema.
      *
-     * @param schemaFields Fields of the schema.
-     * @param fieldDefs Definitions for all fields in {@code schemaFields}.
-     * @param traverseChildrenMtd Method definition {@link 
InnerNode#traverseChildren(ConfigurationVisitor, boolean)}
-     *      defined in {@code *Node} class.
-     * @return Created bytecode blocks that invokes of {@link 
ConfigurationVisitor}'s methods for fields.
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @return Interfaces for {@link InnerNode} definition for a configuration 
schema.
+     */
+    private static ParameterizedType[] nodeClassInterfaces(Class<?> 
schemaClass, Set<Class<?>> schemaExtensions) {
+        Collection<ParameterizedType> res = new ArrayList<>();
+
+        for (Class<?> cls : concat(List.of(schemaClass), schemaExtensions)) {
+            res.add(typeFromJavaClassName(viewClassName(cls)));
+            res.add(typeFromJavaClassName(changeClassName(cls)));
+        }
+
+        if (isPolymorphicConfigInstance(schemaClass))
+            res.add(type(PolymorphicInstance.class));
+
+        return res.toArray(ParameterizedType[]::new);
+    }
+
+    /**
+     * Get interfaces for {@link DynamicConfiguration} definition for a 
configuration schema.
+     *
+     * @param schemaClass Configuration schema class.
+     * @param schemaExtensions Internal extensions of the configuration schema.
+     * @return Interfaces for {@link DynamicConfiguration} definition for a 
configuration schema.
      */
-    private static Collection<BytecodeNode> invokeVisitForTraverseChildren(
+    private ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        List<ParameterizedType> result = Stream.concat(Stream.of(schemaClass), 
schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .collect(toCollection(ArrayList::new));
+
+        if (schemasInfo.get(schemaClass).direct)
+            result.add(type(DirectConfigurationProperty.class));
+
+        return result.toArray(new ParameterizedType[0]);
+    }
+
+    /**
+     * Add {@link DynamicConfiguration#configType} method implementation to 
the class. It looks like the following code:
+     * <pre><code>
+     * public Class configType() {
+     *     return RootConfiguration.class;
+     * }
+     * </code></pre>
+     * @param classDef Class definition.
+     * @param clazz Definition of the configuration interface, for example 
{@code RootConfiguration}.
+     */
+    private static void addCfgImplConfigTypeMethod(ClassDefinition classDef, 
ParameterizedType clazz) {
+        classDef.declareMethod(of(PUBLIC), "configType", type(Class.class))
+            .getBody()
+            .append(constantClass(clazz))
+            .retObject();
+    }
+
+    /**
+     * Create a {@code *Node} for the polymorphic configuration instance 
schema.
+     *
+     * @param schemaClass Polymorphic configuration schema (parent).
+     * @param polymorphicExtension Polymorphic configuration instance schema 
(child).
+     * @param schemaInnerNodeClassDef {@link InnerNode} definition for the 
polymorphic configuration schema {@code schemaClass}.
+     * @param schemaFields Schema fields of polymorphic configuration {@code 
schemaClass}.
+     * @param polymorphicFields Schema fields of a polymorphic configuration 
instance {@code polymorphicExtension}.
+     */
+    private ClassDefinition createPolymorphicExtensionNodeClass(
+        Class<?> schemaClass,
+        Class<?> polymorphicExtension,
+        ClassDefinition schemaInnerNodeClassDef,
         Collection<Field> schemaFields,
+        Collection<Field> polymorphicFields
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+        SchemaClassesInfo polymorphicExtensionClassInfo = 
schemasInfo.get(polymorphicExtension);
+
+        // Node class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(polymorphicExtensionClassInfo.nodeClassName),
+            type(Object.class),
+            nodeClassInterfaces(polymorphicExtension, Set.of())
+        );
+
+        // private final ParentNode this$0;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "this$0",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("delegate", 
typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable delegateVar = 
constructorMtd.getScope().getVariable("delegate");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .invokeConstructor(Object.class)
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                delegateVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = 
schemaInnerNodeClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        // Creates view and change methods for parent schema.
+        for (Field schemaField : schemaFields) {
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(
+                classDef,
+                schemaField,
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, 
schemaFieldDef),
+                null
+            );
+
+            // Read only.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        FieldDefinition polymorphicTypeIdFieldDef = 
fieldDefs.get(polymorphicIdField(schemaClass).getName());
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(
+                classDef,
+                polymorphicField,
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, 
polymorphicFieldDef),
+                viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, 
polymorphicTypeIdFieldDef)
+            );
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates Node#convert.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // return this.this$0.convert(changeClass);
+        convertMtd.getBody()
+            .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+            .append(convertMtd.getScope().getVariable("changeClass"))
+            .invokeVirtual(schemaInnerNodeClassDef.getType(), 
CONVERT_MTD_NAME, returnType, type(Class.class))
+            .retObject();
+
+        return classDef;
+    }
+
+    /**
+     * Create a {@code *CfgImpl} for the polymorphic configuration instance 
schema.
+     *
+     * @param schemaClass Polymorphic configuration schema (parent).
+     * @param polymorphicExtension Polymorphic configuration instance schema 
(child).
+     * @param schemaCfgImplClassDef {@link DynamicConfiguration} definition 
for the polymorphic configuration schema {@code schemaClass}.
+     * @param schemaFields Schema fields of polymorphic configuration {@code 
schemaClass}.
+     * @param polymorphicFields Schema fields of a polymorphic configuration 
instance {@code polymorphicExtension}.
+     */
+    private ClassDefinition createPolymorphicExtensionCfgImplClass(
+        Class<?> schemaClass,
+        Class<?> polymorphicExtension,
+        ClassDefinition schemaCfgImplClassDef,
+        Collection<Field> schemaFields,
+        Collection<Field> polymorphicFields
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+        SchemaClassesInfo polymorphicExtensionClassInfo = 
schemasInfo.get(polymorphicExtension);
+
+        Class<?> superClass = schemaClassInfo.direct || 
polymorphicExtensionClassInfo.direct
+            ? DirectConfigurationTreeWrapper.class : 
ConfigurationTreeWrapper.class;
+
+        // Configuration impl class definition.
+        ClassDefinition classDef = new ClassDefinition(
+            of(PUBLIC, FINAL),
+            internalName(polymorphicExtensionClassInfo.cfgImplClassName),
+            type(superClass),
+            configClassInterfaces(polymorphicExtension, Set.of())
+        );
+
+        // private final ParentCfgImpl this$0;
+        FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "this$0",
+            typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("delegate", 
typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+        );
+
+        Variable delegateVar = 
constructorMtd.getScope().getVariable("delegate");
+
+        // Constructor body.
+        // super(parent);
+        // this.this$0 = parent;
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(delegateVar)
+            .invokeConstructor(superClass, ConfigurationTree.class)
+            .append(constructorMtd.getThis().setField(
+                parentCfgImplFieldDef,
+                delegateVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = 
schemaCfgImplClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        for (Field schemaField : concat(schemaFields, polymorphicFields)) {
+            addConfigurationImplGetMethod(
+                classDef,
+                schemaField,
+                parentCfgImplFieldDef,
+                fieldDefs.get(fieldName(schemaField))
+            );
+        }
+
+        return classDef;
+    }
+
+    /**
+     * Adds a {@link InnerNode#specificNode} override for the polymorphic 
configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic 
configuration class (parent).
+     * @param polymorphicExtensions     Polymorphic configuration instance 
schemas (children).
+     * @param polymorphicTypeIdFieldDef Identification field for the 
polymorphic configuration instance.
+     */
+    private void addNodeSpecificNodeMethod(
+        ClassDefinition classDef,
+        Set<Class<?>> polymorphicExtensions,
+        FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition specificNodeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            SPECIFIC_NODE_MTD.getName(),
+            type(Object.class)
+        );
+
+        StringSwitchBuilder switchBuilder = 
typeIdSwitchBuilder(specificNodeMtd, polymorphicTypeIdFieldDef);
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            SchemaClassesInfo polymorphicExtensionClassInfo = 
schemasInfo.get(polymorphicExtension);
+
+            switchBuilder.addCase(
+                polymorphicInstanceId(polymorphicExtension),
+                newInstance(
+                    
typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+                    specificNodeMtd.getThis()
+                ).ret()
+            );
+        }
+
+        specificNodeMtd.getBody().append(switchBuilder.build());
+    }
+
+    /**
+     * Adds a {@code *Node#convert} for the polymorphic configuration case.
+     *
+     * @param classDef                   Definition of a polymorphic 
configuration class {@code schemaClass}.
+     * @param schemaClass                Polymorphic configuration schema 
(parent).
+     * @param polymorphicExtensions      Polymorphic configuration instance 
schemas (children).
+     * @param changePolymorphicTypeIdMtd Method for changing the type of 
polymorphic configuration.
+     */
+    private void addNodeConvertMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        MethodDefinition changePolymorphicTypeIdMtd
+    ) {
+        SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            typeFromJavaClassName(schemaClassInfo.changeClassName),
+            arg("changeClass", Class.class)
+        );
+
+        // changeClass.getName();
+        BytecodeExpression changeClassName = convertMtd.getScope()
+            .getVariable("changeClass")
+            .invoke(CLASS_GET_NAME_MTD);
+
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(convertMtd.getScope())
+            .expression(changeClassName)
+            
.defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, 
changeClassName));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            SchemaClassesInfo polymorphicExtensionClassInfo = 
schemasInfo.get(polymorphicExtension);
+
+            // this.changePolymorphicTypeId(polymorphicTypeId);
+            // return new ChildNode(this);
+            switchBuilder.addCase(
+                polymorphicExtensionClassInfo.changeClassName,
+                new BytecodeBlock()
+                    
.append(constantString(polymorphicInstanceId(polymorphicExtension)))
+                    .invokeVirtual(changePolymorphicTypeIdMtd)
+                    .append(newInstance(
+                        
typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+                        convertMtd.getThis()
+                    ))
+                    .retObject()
+            );
+        }
+
+        convertMtd.getBody()
+            .append(convertMtd.getThis())
+            .append(switchBuilder.build())
+            .ret();
+    }
+
+    /**
+     * Adds a {@code Node#changeTypeId} for the polymorphic configuration case.
+     *
+     * @param classDef                  Definition of a polymorphic 
configuration class (parent).
+     * @param fieldDefs                 Definitions for all fields in {@code 
classDef}.
+     * @param polymorphicExtensions     Polymorphic configuration instance 
schemas (children).
+     * @param polymorphicFields         Fields of polymorphic extensions.
+     * @param polymorphicTypeIdFieldDef Identification field for the 
polymorphic configuration instance.
+     * @return Method definition.
+     */
+    private MethodDefinition addNodeChangePolymorphicTypeIdMethod(
+        ClassDefinition classDef,
         Map<String, FieldDefinition> fieldDefs,
-        MethodDefinition traverseChildrenMtd
+        Set<Class<?>> polymorphicExtensions,
+        Collection<Field> polymorphicFields,
+        FieldDefinition polymorphicTypeIdFieldDef
     ) {
-        if (schemaFields.isEmpty())
-            return List.of();
-        else {
-            return schemaFields.stream()
-                .map(field -> invokeVisit(traverseChildrenMtd, field, 
fieldDefs.get(field.getName())).pop())
+        MethodDefinition changePolymorphicTypeIdMtd = classDef.declareMethod(
+            of(PUBLIC),
+            changeMethodName(polymorphicTypeIdFieldDef.getName()),
+            type(void.class),
+            arg("typeId", String.class)
+        );
+
+        Variable typeIdVar = 
changePolymorphicTypeIdMtd.getScope().getVariable("typeId");
+
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(changePolymorphicTypeIdMtd.getScope())
+            .expression(typeIdVar)
+            
.defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, 
typeIdVar));
+
+        for (Class<?> polymorphicExtension : polymorphicExtensions) {
+            // Fields that need to be cleared when changing the type of the 
polymorphic configuration instance.
+            Collection<Field> resetFields = polymorphicFields.stream()
+                .filter(f -> 
!polymorphicExtension.equals(f.getDeclaringClass()))
                 .collect(toList());
+
+            // this.typeId = typeId;
+            BytecodeBlock codeBlock = new BytecodeBlock()
+                .append(setThisFieldCode(changePolymorphicTypeIdMtd, 
typeIdVar, polymorphicTypeIdFieldDef));
+
+            // Reset fields.
+            for (Field resetField : resetFields) {
+                FieldDefinition fieldDef = 
fieldDefs.get(fieldName(resetField));
+
+                if (isValue(resetField) || isConfigValue(resetField)) {
+                    // this.field = null;
+                    codeBlock.append(setThisFieldCode(
+                        changePolymorphicTypeIdMtd,
+                        constantNull(fieldDef.getType()),
+                        fieldDef
+                    ));
+                }
+                else {
+                    // this.field = new NamedListNode<>(key, ValueNode::new, 
"polymorphicIdFieldName");
+                    
codeBlock.append(setThisFieldCode(changePolymorphicTypeIdMtd, 
newNamedListNode(resetField), fieldDef));
+                }
+            }
+
+            // ConfigurationUtil.addDefaults(this);
+            codeBlock
+                .append(changePolymorphicTypeIdMtd.getThis())
+                .invokeStatic(ADD_DEFAULTS_MTD);
+
+            switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), 
codeBlock);
         }
+
+        // if(typeId.equals(this.typeId)) return;
+        // else switch(typeId)...
+        changePolymorphicTypeIdMtd.getBody()
+            .append(typeIdVar)
+            .append(getThisFieldCode(changePolymorphicTypeIdMtd, 
polymorphicTypeIdFieldDef))
+            .append(
+                new IfStatement()
+                    .condition(new 
BytecodeBlock().invokeVirtual(STRING_EQUALS_MTD))
+                    .ifTrue(new BytecodeBlock().ret())
+                    .ifFalse(switchBuilder.build().ret())
+            )
+            .ret();

Review comment:
       Is this extra ret() really required?




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


Reply via email to