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



##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -148,18 +181,48 @@
     /** {@link ConstructableTreeNode#copy()} */
     private static final Method COPY;
 
-    /** {@link DynamicConfiguration#DynamicConfiguration} */
-    private static final Constructor<?> DYNAMIC_CONFIGURATION_CTOR;
-
-    /** {@link DirectDynamicConfiguration#DirectDynamicConfiguration} */
-    private static final Constructor<?> DIRECT_DYNAMIC_CONFIGURATION_CTOR;
-
-    /** {@link DynamicConfiguration#add(ConfigurationProperty)} */
-    private static final Method DYNAMIC_CONFIGURATION_ADD;
+    /** {@code DynamicConfiguration#add} method. */
+    private static final Method DYNAMIC_CONFIGURATION_ADD_MTD;
 
     /** {@link Objects#requireNonNull(Object, String)} */
     private static final Method REQUIRE_NON_NULL;
 
+    /** {@link Class#getName} method. */
+    private static final Method CLASS_GET_NAME_MTD;
+
+    /** {@link String#equals} method. */
+    private static final Method STRING_EQUALS_MTD;
+
+    /** {@link ConfigurationSource#polymorphicTypeId} method. */
+    private static final Method POLYMORPHIC_TYPE_ID_MTD;
+
+    /** {@link InnerNode#constructDefault} method. */
+    private static final Method CONSTRUCT_DEFAULT_MTD;
+
+    /** {@code ConfigurationNode#refreshValue} method. */
+    private static final Method REFRESH_VALUE_MTD;
+
+    /** {@code PolymorphicDynamicConfiguration#addMember} method. */
+    private static final Method ADD_MEMBER_MTD;
+
+    /** {@code PolymorphicDynamicConfiguration#removeMember} method. */
+    private static final Method REMOVE_MEMBER_MTD;
+
+    /** {@link PolymorphicInnerNode#specificView} method. */
+    private static final Method SPECIFIC_VIEW_MTD;
+
+    /** {@link PolymorphicInnerNode#specificChange} method. */
+    private static final Method SPECIFIC_CHANGE_MTD;
+
+    /** {@link PolymorphicInnerNode#setPolymorphicTypeId} method. */
+    private static final Method SET_POLYMORPHIC_TYPE_ID_MTD;
+
+    /** {@link PolymorphicDynamicConfiguration#specificConfigTree} method. */
+    private static final Method SPECIFIC_CONFIG_TREE_MTD;
+
+    /** {@code Node#convert} method name. */
+    private static final String CONVERT_MTD_NAME = "convert";

Review comment:
       This is, like, the only constant for method name. Can you inline it? :)

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -298,25 +405,29 @@ public synchronized void compileRootSchema(
         schemasInfo.put(rootSchemaClass, new 
SchemaClassesInfo(rootSchemaClass));
 
         Set<Class<?>> schemas = new HashSet<>();
-        List<ClassDefinition> definitions = new ArrayList<>();
+        List<ClassDefinition> classDefs = new ArrayList<>();
 
         while (!compileQueue.isEmpty()) {
             Class<?> schemaClass = compileQueue.poll();
 
             assert schemaClass.isAnnotationPresent(ConfigurationRoot.class)
                 || schemaClass.isAnnotationPresent(Config.class)
+                || isPolymorphicConfig(schemaClass)
                 : schemaClass + " is not properly annotated";
 
             assert schemasInfo.containsKey(schemaClass) : schemaClass;
 
-            Field[] schemaFields = 
Arrays.stream(schemaClass.getDeclaredFields()).filter(
-                field -> isValue(field) || isConfigValue(field) || 
isNamedConfigValue(field)
-            ).toArray(Field[]::new);
+            Set<Class<?>> internalExtensions = 
internalSchemaExtensions.getOrDefault(schemaClass, Set.of());
+            Set<Class<?>> polymorphicExtensions = 
polymorphicSchemaExtensions.getOrDefault(schemaClass, Set.of());
+
+            assert internalExtensions.isEmpty() || 
polymorphicExtensions.isEmpty() :

Review comment:
       Do we have plans to support such scenario?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
##########
@@ -84,8 +87,16 @@ public DynamicConfiguration(
 
             /** {@inheritDoc} */
             @Override public void descend(ConstructableTreeNode node) {
-                if (level == keys.size())
-                    change.accept((CHANGE)node);
+                if (level == keys.size()) {
+                    if (node instanceof InnerNode) {

Review comment:
       Isn't this always true?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
##########
@@ -142,8 +168,68 @@ public DynamicConfiguration(
     }
 
     /**
+     * Returns configuration interface.
+     *
      * @return Configuration interface, for example {@code RootConfiguration}.
      * @throws UnsupportedOperationException In the case of a named list.
      */
     public abstract Class<? extends ConfigurationProperty<VIEW>> configType();
+
+    /**
+     * Returns specific configuration tree.
+     *
+     * @return Specific configuration tree.
+     */
+    public ConfigurationTree<VIEW, CHANGE> specificConfigTree() {
+        // To work with polymorphic configuration.
+        return this;
+    }
+
+    /**
+     * Removes members of the previous instance of polymorphic configuration.
+     *
+     * @param oldValue Old configuration value.
+     * @param members Configuration members (leaves and nodes).
+     */
+    protected void removeMembers(VIEW oldValue, Map<String, 
ConfigurationProperty<?>> members) {
+        // No-op.
+    }
+
+    /**
+     * Adds members of the previous instance of polymorphic configuration.
+     *
+     * @param newValue New configuration value.
+     * @param members Configuration members (leaves and nodes).
+     */
+    protected void addMembers(VIEW newValue, Map<String, 
ConfigurationProperty<?>> members) {
+        // No-op.
+    }
+
+    /**
+     * Add configuration member.
+     *
+     * @param members Configuration members (leaves and nodes).
+     * @param member Configuration member (leaf or node).
+     * @param <P> Type of member.
+     */
+    protected <P extends ConfigurationProperty<?>> void addMember(
+        Map<String, ConfigurationProperty<?>> members,
+        P member
+    ) {
+        members.put(member.key(), member);
+    }
+
+    /**
+     * Remove configuration member.
+     *
+     * @param members Configuration members (leaves and nodes).
+     * @param member Configuration member (leaf or node).
+     * @param <P> Type of member.
+     */
+    protected <P extends ConfigurationProperty<?>> void removeMember(

Review comment:
       Is this method used anywhere?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -796,38 +1143,120 @@ private void addNodeConstructMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = 
constructMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, 
schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // String tmpStr;
+                Variable tmpStrVar = 
constructMtd.getScope().createTempVariable(String.class);
+
+                // src == null ? src == null ? null : 
src.unwrap(FieldType.class);
+                BytecodeExpression getTypeIdFromSrcVar = inlineIf(
+                    isNull(srcVar),
+                    constantNull(fieldDef.getType()),
+                    srcVar.invoke(UNWRAP, 
constantClass(fieldDef.getType())).cast(fieldDef.getType())
+                );
+
+                // String tmpStr = src == null ? src == null ? null : 
src.unwrap(FieldType.class);

Review comment:
       This comment is broken too

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -796,38 +1143,120 @@ private void addNodeConstructMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = 
constructMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, 
schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // String tmpStr;
+                Variable tmpStrVar = 
constructMtd.getScope().createTempVariable(String.class);

Review comment:
       What's the point of this tamp variable if you immediately pass it into 
changePolymorphicTypeIdMtd?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -796,38 +1143,120 @@ private void addNodeConstructMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = 
constructMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, 
schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // String tmpStr;
+                Variable tmpStrVar = 
constructMtd.getScope().createTempVariable(String.class);
+
+                // src == null ? src == null ? null : 
src.unwrap(FieldType.class);
+                BytecodeExpression getTypeIdFromSrcVar = inlineIf(
+                    isNull(srcVar),
+                    constantNull(fieldDef.getType()),
+                    srcVar.invoke(UNWRAP, 
constantClass(fieldDef.getType())).cast(fieldDef.getType())
+                );
+
+                // String tmpStr = src == null ? src == null ? null : 
src.unwrap(FieldType.class);
+                // this.changePolymorphicTypeId(tmpStr);
+                switchBuilder.addCase(
+                    fieldName,
+                    new BytecodeBlock()
+                        .append(constructMtd.getThis())
+                        .append(tmpStrVar.set(getTypeIdFromSrcVar))

Review comment:
       Swapping these two lines would be more convenient

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)
+            .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) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(classDef, schemaField, parentInnerNodeFieldDef, 
schemaFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(classDef, polymorphicField, 
parentInnerNodeFieldDef, polymorphicFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates {@code Node#convert}.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // Find parent {@code Node#convert}.
+        MethodDefinition parentConvertMtd = 
schemaInnerNodeClassDef.getMethods().stream()

Review comment:
       Why do you search for this method if you know everything about it? 
Please fix this code

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);

Review comment:
       ".toCollection(() -> result)" would be a "more correct" way to use 
streams, it should work

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)
+            .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) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(classDef, schemaField, parentInnerNodeFieldDef, 
schemaFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(classDef, polymorphicField, 
parentInnerNodeFieldDef, polymorphicFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates {@code Node#convert}.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // Find parent {@code Node#convert}.

Review comment:
       There's no point in having `{@code...` inside of single-line comments

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
##########
@@ -173,4 +176,24 @@
             throw new IllegalStateException(e);
         }
     }
+
+    /**
+     * Returns specific {@code Node} of the value.
+     *
+     * @param <NODE> Type of the {@code Node}.
+     * @return Specific {@code Node} of the value.
+     */
+    public <NODE> NODE specificNode() {
+        return (NODE)this;
+    }
+
+    /**
+     * Checks if a node is polymorphic.
+     *
+     * @return {@code true} if the node is polymorphic.
+     * @see PolymorphicConfig
+     */
+    public boolean isPolymorphicNode() {

Review comment:
       Is this necessary? What's the use of this method?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
##########
@@ -173,4 +176,24 @@
             throw new IllegalStateException(e);
         }
     }
+
+    /**
+     * Returns specific {@code Node} of the value.

Review comment:
       Not even a word about polymorphic instances. Please expand this 
desription.

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
##########
@@ -124,13 +140,16 @@ private NamedListNode(NamedListNode<N> node) {
 
         checkNewKey(key);
 
-        ElementDescriptor<N> element = newElementDescriptor();
+        ElementDescriptor element = newElementDescriptor();
 
         map.put(key, element);
 
         reverseIdMap.put(element.internalId, key);
 
-        valConsumer.accept(element.value);
+        valConsumer.accept((N)element.value);
+
+        if (element.value.isPolymorphicNode())

Review comment:
       This part is wrong, addDefaults() should be inside of "convert" method

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
##########
@@ -28,47 +28,54 @@
 import java.util.function.Supplier;
 import org.apache.ignite.configuration.NamedListChange;
 import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
 import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
+import 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.LeafConfigurationSource;
+import org.jetbrains.annotations.Nullable;
 
 import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
+import static 
org.apache.ignite.internal.configuration.util.ConfigurationUtil.leafNodeVisitor;
 
 /**
  * Configuration node implementation for the collection of named {@link 
InnerNode}s. Unlike implementations of
  * {@link InnerNode}, this class is used for every named list in configuration.
  *
- * @param <N> Type of the {@link InnerNode} that is stored in named list node 
object.
+ * @param <N> Type of the that is stored in named list node object.

Review comment:
       "Type of the that..." - this is grammatically incorrect) I know that you 
just removed `{@link InnerNode}` without correcting the rest

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -731,35 +1022,85 @@ private static void addNodeTraverseChildMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = traverseChildMtd.getBody();
+        Variable keyVar = traverseChildMtd.getScope().getVariable("key");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(invokeVisitForTraverseChild(asList(schemaFields), 
fieldDefs, traverseChildMtd));
-        else {
-            Variable includeInternalVar = 
traverseChildMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(traverseChildMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+
+            switchBuilder.addCase(
+                fieldName,
+                invokeVisit(traverseChildMtd, schemaField, 
fieldDefs.get(fieldName)).retObject()
+            );
+        }
+
+        if (!internalFields.isEmpty()) {

Review comment:
       Absolute lack of comments makes new code difficult to understand. Can 
you add something here and there? The only places that have a description of 
what's going on are in our heads right now :(

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -796,38 +1143,120 @@ private void addNodeConstructMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = 
constructMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, 
schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // String tmpStr;
+                Variable tmpStrVar = 
constructMtd.getScope().createTempVariable(String.class);
+
+                // src == null ? src == null ? null : 
src.unwrap(FieldType.class);

Review comment:
       Type if String BTW

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
##########
@@ -637,102 +447,72 @@ public static boolean hasDefault(Field field) {
     }
 
     /**
-     * Get the default value of a {@link Value}.
+     * Returns the default value for the configuration schema field
+     * by instantiating the schema and retrieving the field value.
      *
-     * @param field Configuration Schema class field.
+     * @param schemaField Configuration schema field for which we want to get 
the default value.
+     * @param <T> Default value type.
      * @return Default value.
+     * @throws IllegalStateException If there were problems creating schema 
instances.
+     * @see Value#hasDefault
+     * @see PolymorphicId#hasDefault
      */
-    public static Object defaultValue(Field field) {
-        assert hasDefault(field) : field;
-
+    public static <T> T defaultValue(Field schemaField) {

Review comment:
       I don't like this method in general, can we rely on later runtime checks 
and tests instead of validating default polymorphic type during compilation?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -796,38 +1143,120 @@ private void addNodeConstructMethod(
             arg("includeInternal", type(boolean.class))
         ).addException(NoSuchElementException.class);
 
-        BytecodeBlock mtdBody = constructMtd.getBody();
+        Variable keyVar = constructMtd.getScope().getVariable("key");
+        Variable srcVar = constructMtd.getScope().getVariable("src");
 
-        if (extensionsFields.isEmpty())
-            mtdBody.append(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd)).ret();
-        else {
-            Variable includeInternalVar = 
constructMtd.getScope().getVariable("includeInternal");
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
 
-            mtdBody.append(
-                new IfStatement()
-                    .condition(includeInternalVar)
-                    .ifTrue(treatSourceForConstruct(union(extensionsFields, 
schemaFields), fieldDefs, constructMtd))
-                    .ifFalse(treatSourceForConstruct(asList(schemaFields), 
fieldDefs, constructMtd))
+        for (Field schemaField : schemaFields) {
+            String fieldName = fieldName(schemaField);
+            FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+            if (isPolymorphicId(schemaField)) {
+                // String tmpStr;
+                Variable tmpStrVar = 
constructMtd.getScope().createTempVariable(String.class);
+
+                // src == null ? src == null ? null : 
src.unwrap(FieldType.class);

Review comment:
       Something's clearly not right in this comment

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java
##########
@@ -0,0 +1,46 @@
+/*
+ * 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;
+
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.DirectConfigurationProperty;
+
+/**
+ * {@link ConfigurationTree} wrapper with {@link DirectConfigurationProperty}.
+ *
+ * @param <VIEW> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public abstract class DirectConfigurationTreeWrapper<VIEW, CHANGE> extends 
ConfigurationTreeWrapper<VIEW, CHANGE>
+    implements DirectConfigurationProperty<VIEW> {
+    /**
+     * Constructor.
+     *
+     * @param configTree Configuration tree.
+     */
+    public DirectConfigurationTreeWrapper(ConfigurationTree<VIEW, CHANGE> 
configTree) {
+        super(configTree);
+
+        assert configTree instanceof DirectConfigurationProperty : configTree;

Review comment:
       I don't understand your response, I see `assert configTree instanceof 
DirectConfigurationProperty`, why not make it a type of the argument? There 
must be a reason

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -401,122 +539,212 @@ private ClassDefinition createNodeClass(
 
             // Add change methods.
             MethodDefinition changeMtd =
-                addNodeChangeMethod(classDef, schemaField, fieldDef, 
schemaClassInfo.nodeClassName);
+                addNodeChangeMethod(classDef, schemaField, 
schemaClassInfo.nodeClassName, fieldDef);
+
             addNodeChangeBridgeMethod(classDef, 
changeClassName(schemaField.getDeclaringClass()), changeMtd);
         }
 
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension = Map.of();
+
+        MethodDefinition changePolymorphicTypeIdMtd = null;
+
+        if (!polymorphicExtensions.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+
+            addNodeSpecificNodeMethod(classDef, polymorphicExtensions, 
polymorphicTypeIdFieldDef);
+
+            changePolymorphicTypeIdMtd = addNodeChangePolymorphicTypeIdMethod(
+                classDef,
+                fieldDefs,
+                polymorphicExtensions,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+
+            addNodeConvertMethod(classDef, schemaClass, polymorphicExtensions, 
changePolymorphicTypeIdMtd);
+
+            addNodeIsPolymorphicNodeMethod(classDef);
+
+            polymorphicFieldsByExtension = new LinkedHashMap<>();
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                polymorphicFieldsByExtension.put(
+                    polymorphicExtension,
+                    polymorphicFields.stream()
+                        .filter(f -> 
polymorphicExtension.equals(f.getDeclaringClass()))
+                        .collect(toList())
+                );
+            }
+        }
+
         // traverseChildren
-        addNodeTraverseChildrenMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildrenMethod(
+            classDef,
+            schemaClass,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // traverseChild
-        addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // construct
-        addNodeConstructMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeConstructMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef,
+            changePolymorphicTypeIdMtd
+        );
 
         // constructDefault
-        addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, 
schemaFields, extensionsFields);
+        addNodeConstructDefaultMethod(
+            classDef,
+            specFields,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         return classDef;
     }
 
     /**
-     * Add {@link InnerNode#schemaType()} method implementation to the class. 
It looks like the following code:
-     * <pre>{@code
-     * public Class schemaType() {
-     *     return this._spec.getClass();
-     * }
-     * }</pre>
-     * @param classDef Class definition.
-     * @param specField Field definition of the {@code _spec} field.
+     * Add {@link InnerNode#schemaType} method implementation to the class.
+     *
+     * @param classDef                  Class definition.
+     * @param schemaClass               Configuration schema class.
+     * @param polymorphicExtensions     Polymorphic extensions of the 
configuration schema.
+     * @param specFields                Field definitions for the schema and 
its extensions: {@code _spec#}.
+     * @param polymorphicTypeIdFieldDef Identification field for the 
polymorphic configuration instance.
      */
-    private static void addNodeSchemaTypeMethod(ClassDefinition classDef, 
FieldDefinition specField) {
-        MethodDefinition schemaTypeMtd = classDef.declareMethod(of(PUBLIC), 
"schemaType", type(Class.class));
+    private static void addNodeSchemaTypeMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        Map<Class<?>, FieldDefinition> specFields,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition schemaTypeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "schemaType",
+            type(Class.class)
+        );
+
+        BytecodeBlock mtdBody = schemaTypeMtd.getBody();
+
+        if (polymorphicExtensions.isEmpty())
+            mtdBody.append(invokeGetClass(getThisFieldCode(schemaTypeMtd, 
specFields.get(schemaClass))).ret());

Review comment:
       I'm not necessarily asking to change anything, but I think that 
"invokeGetClass" and "getThisFieldCode" make code harder to read

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -612,25 +851,33 @@ private static MethodDefinition addNodeChangeMethod(
 
         if (isValue(schemaField)) {
             // newValue = Box.valueOf(newValue); // Boxing.
-            if (schemaFieldType.isPrimitive())
-                newValue = invokeStatic(fieldDef.getType(), "valueOf", 
fieldDef.getType(), singleton(newValue));
+            if (schemaFieldType.isPrimitive()) {
+                ParameterizedType type = type(box(schemaFieldType));
+
+                newValue = invokeStatic(type, "valueOf", type, 
singleton(newValue));
+            }
 
             // newValue = newValue.clone();
             if (schemaFieldType.isArray())
                 newValue = newValue.invoke("clone", 
Object.class).cast(schemaFieldType);
 
             // this.field = newValue;
-            changeBody.append(changeMtd.getThis().setField(fieldDef, 
newValue));
+            changeBody.append(setThisFieldCode(changeMtd, newValue, 
fieldDefs));
         }
         else {
             // this.field = (this.field == null) ? new ValueNode() : 
(ValueNode)this.field.copy();
-            changeBody.append(copyNodeField(changeMtd, fieldDef));
+            changeBody.append(copyNodeField(changeMtd, fieldDefs));
+
+            // this.field;
+            BytecodeExpression getFieldCode = getThisFieldCode(changeMtd, 
fieldDefs);
+
+            if (isPolymorphicConfig(schemaFieldType) && 
isConfigValue(schemaField)) {
+                // this.field.specificNode();
+                getFieldCode = getFieldCode.invoke(SPECIFIC_NODE_MTD);
+            }
 
             // change.accept(this.field);
-            
changeBody.append(changeMtd.getScope().getVariable("change").invoke(
-                ACCEPT,
-                changeMtd.getThis().getField(fieldDef)
-            ));
+            
changeBody.append(changeMtd.getScope().getVariable("change").invoke(ACCEPT, 
getFieldCode));

Review comment:
       Comment above is wrong now

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -401,122 +539,212 @@ private ClassDefinition createNodeClass(
 
             // Add change methods.
             MethodDefinition changeMtd =
-                addNodeChangeMethod(classDef, schemaField, fieldDef, 
schemaClassInfo.nodeClassName);
+                addNodeChangeMethod(classDef, schemaField, 
schemaClassInfo.nodeClassName, fieldDef);
+
             addNodeChangeBridgeMethod(classDef, 
changeClassName(schemaField.getDeclaringClass()), changeMtd);
         }
 
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension = Map.of();
+
+        MethodDefinition changePolymorphicTypeIdMtd = null;
+
+        if (!polymorphicExtensions.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+
+            addNodeSpecificNodeMethod(classDef, polymorphicExtensions, 
polymorphicTypeIdFieldDef);
+
+            changePolymorphicTypeIdMtd = addNodeChangePolymorphicTypeIdMethod(
+                classDef,
+                fieldDefs,
+                polymorphicExtensions,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+
+            addNodeConvertMethod(classDef, schemaClass, polymorphicExtensions, 
changePolymorphicTypeIdMtd);
+
+            addNodeIsPolymorphicNodeMethod(classDef);
+
+            polymorphicFieldsByExtension = new LinkedHashMap<>();
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                polymorphicFieldsByExtension.put(
+                    polymorphicExtension,
+                    polymorphicFields.stream()
+                        .filter(f -> 
polymorphicExtension.equals(f.getDeclaringClass()))
+                        .collect(toList())
+                );
+            }
+        }
+
         // traverseChildren
-        addNodeTraverseChildrenMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildrenMethod(
+            classDef,
+            schemaClass,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // traverseChild
-        addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // construct
-        addNodeConstructMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeConstructMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef,
+            changePolymorphicTypeIdMtd
+        );
 
         // constructDefault
-        addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, 
schemaFields, extensionsFields);
+        addNodeConstructDefaultMethod(
+            classDef,
+            specFields,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         return classDef;
     }
 
     /**
-     * Add {@link InnerNode#schemaType()} method implementation to the class. 
It looks like the following code:
-     * <pre>{@code
-     * public Class schemaType() {
-     *     return this._spec.getClass();
-     * }
-     * }</pre>
-     * @param classDef Class definition.
-     * @param specField Field definition of the {@code _spec} field.
+     * Add {@link InnerNode#schemaType} method implementation to the class.
+     *
+     * @param classDef                  Class definition.
+     * @param schemaClass               Configuration schema class.
+     * @param polymorphicExtensions     Polymorphic extensions of the 
configuration schema.
+     * @param specFields                Field definitions for the schema and 
its extensions: {@code _spec#}.
+     * @param polymorphicTypeIdFieldDef Identification field for the 
polymorphic configuration instance.
      */
-    private static void addNodeSchemaTypeMethod(ClassDefinition classDef, 
FieldDefinition specField) {
-        MethodDefinition schemaTypeMtd = classDef.declareMethod(of(PUBLIC), 
"schemaType", type(Class.class));
+    private static void addNodeSchemaTypeMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        Map<Class<?>, FieldDefinition> specFields,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition schemaTypeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "schemaType",
+            type(Class.class)
+        );
+
+        BytecodeBlock mtdBody = schemaTypeMtd.getBody();
+
+        if (polymorphicExtensions.isEmpty())
+            mtdBody.append(invokeGetClass(getThisFieldCode(schemaTypeMtd, 
specFields.get(schemaClass))).ret());
+        else {
+            assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+            StringSwitchBuilder switchBuilderTypeId = 
typeIdSwitchBuilder(schemaTypeMtd, polymorphicTypeIdFieldDef);
 
-        schemaTypeMtd.getBody().append(
-            schemaTypeMtd.getThis().getField(specField).invoke("getClass", 
Class.class)
-        ).retObject();
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                switchBuilderTypeId.addCase(
+                    polymorphicInstanceId(polymorphicExtension),
+                    invokeGetClass(getThisFieldCode(schemaTypeMtd, 
specFields.get(polymorphicExtension))).ret()

Review comment:
       Constants could be inlined here as well

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)

Review comment:
       Why do you invoke "super()" after you initialized fields? Don't do that 
please :)

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -580,21 +817,23 @@ else if (isNamedConfigValue(schemaField))
      *
      * @param classDef Node class definition.
      * @param schemaField Configuration Schema class field.
-     * @param fieldDef Field definition.
      * @param nodeClassName Class name for the Node class.
+     * @param fieldDefs Field definitions.
      * @return Definition of change method.
      */
     private static MethodDefinition addNodeChangeMethod(
         ClassDefinition classDef,
         Field schemaField,
-        FieldDefinition fieldDef,
-        String nodeClassName
+        String nodeClassName,
+        FieldDefinition... fieldDefs
     ) {
+        assert !nullOrEmpty(fieldDefs);
+
         Class<?> schemaFieldType = schemaField.getType();

Review comment:
       You should add check that "change" for the field of polymorphic subclass 
cannot be called if actual polymorphic type doesn't match the expected type.

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1233,7 +1768,8 @@ private static String capitalize(String name) {
 
     /**
      * Creates boxed version of the class. Types that it can box: {@code 
boolean}, {@code int}, {@code long} and
-     *      {@code double}. Other primitive types are not supported by 
configuration framework.
+     * {@code double}. Other primitive types are not supported by 
configuration framework.

Review comment:
       Can you please remove list of types? All primitives are supported now, 
comment is obsolete

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)
+            .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) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(classDef, schemaField, parentInnerNodeFieldDef, 
schemaFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(classDef, polymorphicField, 
parentInnerNodeFieldDef, polymorphicFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates {@code Node#convert}.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // Find parent {@code Node#convert}.
+        MethodDefinition parentConvertMtd = 
schemaInnerNodeClassDef.getMethods().stream()
+            .filter(mtd -> CONVERT_MTD_NAME.equals(mtd.getName()))
+            .findAny()
+            .orElse(null);
+
+        assert parentConvertMtd != null : schemaInnerNodeClassDef.getName();
+
+        // return this.parent#innerNode.convert(changeClass);
+        convertMtd.getBody()
+            .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+            .append(convertMtd.getScope().getVariable("changeClass"))
+            .invokeVirtual(parentConvertMtd)
+            .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 static Collection<BytecodeNode> invokeVisitForTraverseChildren(
+    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 parent#cfgImpl;
+        FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#cfgImpl",
+            typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", 
typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        // super(parent);
+        // this.parent#cfgImpl = parent;
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(parentVar)
+            .invokeConstructor(superClass, ConfigurationTree.class)
+            .append(constructorMtd.getThis().setField(
+                parentCfgImplFieldDef,
+                parentVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = 
schemaCfgImplClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        for (Field schemaField : concat(schemaFields, polymorphicFields)) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            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())

Review comment:
       Default statement is missing

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
##########
@@ -115,6 +115,10 @@ private static void notifyListeners(
             eventConfigs
         );
 
+        // Polymorphic configuration type has changed.

Review comment:
       Why do we ignore listeners if polymorphic type has changes? Please add 
an explanation in the comment.

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)
+            .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) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(classDef, schemaField, parentInnerNodeFieldDef, 
schemaFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(classDef, polymorphicField, 
parentInnerNodeFieldDef, polymorphicFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates {@code Node#convert}.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // Find parent {@code Node#convert}.
+        MethodDefinition parentConvertMtd = 
schemaInnerNodeClassDef.getMethods().stream()
+            .filter(mtd -> CONVERT_MTD_NAME.equals(mtd.getName()))
+            .findAny()
+            .orElse(null);
+
+        assert parentConvertMtd != null : schemaInnerNodeClassDef.getName();
+
+        // return this.parent#innerNode.convert(changeClass);
+        convertMtd.getBody()
+            .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+            .append(convertMtd.getScope().getVariable("changeClass"))
+            .invokeVirtual(parentConvertMtd)
+            .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 static Collection<BytecodeNode> invokeVisitForTraverseChildren(
+    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 parent#cfgImpl;
+        FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#cfgImpl",
+            typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", 
typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");

Review comment:
       Can you call it "delegate" or something?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",
+            typeFromJavaClassName(schemaClassInfo.nodeClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(constructorMtd.getThis().setField(
+                parentInnerNodeFieldDef,
+                parentVar
+            ))
+            .invokeConstructor(Object.class)
+            .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) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            FieldDefinition schemaFieldDef = 
fieldDefs.get(fieldName(schemaField));
+
+            addNodeViewMethod(classDef, schemaField, parentInnerNodeFieldDef, 
schemaFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                schemaField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                schemaFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
schemaClassInfo.changeClassName, changeMtd);
+        }
+
+        // Creates view and change methods for specific polymorphic instance 
schema.
+        for (Field polymorphicField : polymorphicFields) {
+            FieldDefinition polymorphicFieldDef = 
fieldDefs.get(fieldName(polymorphicField));
+
+            addNodeViewMethod(classDef, polymorphicField, 
parentInnerNodeFieldDef, polymorphicFieldDef);
+
+            MethodDefinition changeMtd = addNodeChangeMethod(
+                classDef,
+                polymorphicField,
+                polymorphicExtensionClassInfo.nodeClassName,
+                parentInnerNodeFieldDef,
+                polymorphicFieldDef
+            );
+
+            addNodeChangeBridgeMethod(classDef, 
polymorphicExtensionClassInfo.changeClassName, changeMtd);
+        }
+
+        ParameterizedType returnType = 
typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+        // Creates {@code Node#convert}.
+        MethodDefinition convertMtd = classDef.declareMethod(
+            of(PUBLIC),
+            CONVERT_MTD_NAME,
+            returnType,
+            arg("changeClass", Class.class)
+        );
+
+        // Find parent {@code Node#convert}.
+        MethodDefinition parentConvertMtd = 
schemaInnerNodeClassDef.getMethods().stream()
+            .filter(mtd -> CONVERT_MTD_NAME.equals(mtd.getName()))
+            .findAny()
+            .orElse(null);
+
+        assert parentConvertMtd != null : schemaInnerNodeClassDef.getName();
+
+        // return this.parent#innerNode.convert(changeClass);
+        convertMtd.getBody()
+            .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+            .append(convertMtd.getScope().getVariable("changeClass"))
+            .invokeVirtual(parentConvertMtd)
+            .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 static Collection<BytecodeNode> invokeVisitForTraverseChildren(
+    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 parent#cfgImpl;
+        FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#cfgImpl",
+            typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+        );
+
+        // Constructor.
+        MethodDefinition constructorMtd = classDef.declareConstructor(
+            of(PUBLIC),
+            arg("parent", 
typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+        );
+
+        Variable parentVar = constructorMtd.getScope().getVariable("parent");
+
+        // Constructor body.
+        // super(parent);
+        // this.parent#cfgImpl = parent;
+        constructorMtd.getBody()
+            .append(constructorMtd.getThis())
+            .append(parentVar)
+            .invokeConstructor(superClass, ConfigurationTree.class)
+            .append(constructorMtd.getThis().setField(
+                parentCfgImplFieldDef,
+                parentVar
+            ))
+            .ret();
+
+        Map<String, FieldDefinition> fieldDefs = 
schemaCfgImplClassDef.getFields().stream()
+            .collect(toMap(FieldDefinition::getName, identity()));
+
+        for (Field schemaField : concat(schemaFields, polymorphicFields)) {
+            // Must be skipped, this is an internal special field.
+            if (isPolymorphicId(schemaField))
+                continue;
+
+            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())
+            .ret();

Review comment:
       Can "ret()" be a valid end to a method that returns Object? Shouldn't it 
be retObject()?

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

Review comment:
       Technically there's a bug here. Maybe you should add a comment with 
explanation of why it doesn't matter

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
##########
@@ -637,102 +447,72 @@ public static boolean hasDefault(Field field) {
     }
 
     /**
-     * Get the default value of a {@link Value}.
+     * Returns the default value for the configuration schema field
+     * by instantiating the schema and retrieving the field value.
      *
-     * @param field Configuration Schema class field.
+     * @param schemaField Configuration schema field for which we want to get 
the default value.
+     * @param <T> Default value type.
      * @return Default value.
+     * @throws IllegalStateException If there were problems creating schema 
instances.
+     * @see Value#hasDefault
+     * @see PolymorphicId#hasDefault
      */
-    public static Object defaultValue(Field field) {
-        assert hasDefault(field) : field;
-
+    public static <T> T defaultValue(Field schemaField) {

Review comment:
       Is this "<T> T" really required?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -1244,196 +1780,959 @@ 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 ParameterizedType[] configClassInterfaces(Class<?> schemaClass, 
Set<Class<?>> schemaExtensions) {
+        var result = new ArrayList<ParameterizedType>();
+
+        Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+            .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+            .forEach(result::add);
+
+        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 parent#innerNode;
+        FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+            of(PRIVATE, FINAL),
+            "parent#innerNode",

Review comment:
       Common pattern is to name these fields "this$0". Word "parent" is 
misleading I think

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -401,122 +539,212 @@ private ClassDefinition createNodeClass(
 
             // Add change methods.
             MethodDefinition changeMtd =
-                addNodeChangeMethod(classDef, schemaField, fieldDef, 
schemaClassInfo.nodeClassName);
+                addNodeChangeMethod(classDef, schemaField, 
schemaClassInfo.nodeClassName, fieldDef);
+
             addNodeChangeBridgeMethod(classDef, 
changeClassName(schemaField.getDeclaringClass()), changeMtd);
         }
 
+        Map<Class<?>, List<Field>> polymorphicFieldsByExtension = Map.of();
+
+        MethodDefinition changePolymorphicTypeIdMtd = null;
+
+        if (!polymorphicExtensions.isEmpty()) {
+            assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+
+            addNodeSpecificNodeMethod(classDef, polymorphicExtensions, 
polymorphicTypeIdFieldDef);
+
+            changePolymorphicTypeIdMtd = addNodeChangePolymorphicTypeIdMethod(
+                classDef,
+                fieldDefs,
+                polymorphicExtensions,
+                polymorphicFields,
+                polymorphicTypeIdFieldDef
+            );
+
+            addNodeConvertMethod(classDef, schemaClass, polymorphicExtensions, 
changePolymorphicTypeIdMtd);
+
+            addNodeIsPolymorphicNodeMethod(classDef);
+
+            polymorphicFieldsByExtension = new LinkedHashMap<>();
+
+            for (Class<?> polymorphicExtension : polymorphicExtensions) {
+                polymorphicFieldsByExtension.put(
+                    polymorphicExtension,
+                    polymorphicFields.stream()
+                        .filter(f -> 
polymorphicExtension.equals(f.getDeclaringClass()))
+                        .collect(toList())
+                );
+            }
+        }
+
         // traverseChildren
-        addNodeTraverseChildrenMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildrenMethod(
+            classDef,
+            schemaClass,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // traverseChild
-        addNodeTraverseChildMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeTraverseChildMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         // construct
-        addNodeConstructMethod(classDef, fieldDefs, schemaFields, 
extensionsFields);
+        addNodeConstructMethod(
+            classDef,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef,
+            changePolymorphicTypeIdMtd
+        );
 
         // constructDefault
-        addNodeConstructDefaultMethod(classDef, specFields, fieldDefs, 
schemaFields, extensionsFields);
+        addNodeConstructDefaultMethod(
+            classDef,
+            specFields,
+            fieldDefs,
+            schemaFields,
+            internalFields,
+            polymorphicFieldsByExtension,
+            polymorphicTypeIdFieldDef
+        );
 
         return classDef;
     }
 
     /**
-     * Add {@link InnerNode#schemaType()} method implementation to the class. 
It looks like the following code:
-     * <pre>{@code
-     * public Class schemaType() {
-     *     return this._spec.getClass();
-     * }
-     * }</pre>
-     * @param classDef Class definition.
-     * @param specField Field definition of the {@code _spec} field.
+     * Add {@link InnerNode#schemaType} method implementation to the class.
+     *
+     * @param classDef                  Class definition.
+     * @param schemaClass               Configuration schema class.
+     * @param polymorphicExtensions     Polymorphic extensions of the 
configuration schema.
+     * @param specFields                Field definitions for the schema and 
its extensions: {@code _spec#}.
+     * @param polymorphicTypeIdFieldDef Identification field for the 
polymorphic configuration instance.
      */
-    private static void addNodeSchemaTypeMethod(ClassDefinition classDef, 
FieldDefinition specField) {
-        MethodDefinition schemaTypeMtd = classDef.declareMethod(of(PUBLIC), 
"schemaType", type(Class.class));
+    private static void addNodeSchemaTypeMethod(
+        ClassDefinition classDef,
+        Class<?> schemaClass,
+        Set<Class<?>> polymorphicExtensions,
+        Map<Class<?>, FieldDefinition> specFields,
+        @Nullable FieldDefinition polymorphicTypeIdFieldDef
+    ) {
+        MethodDefinition schemaTypeMtd = classDef.declareMethod(
+            of(PUBLIC),
+            "schemaType",
+            type(Class.class)
+        );
+
+        BytecodeBlock mtdBody = schemaTypeMtd.getBody();
+
+        if (polymorphicExtensions.isEmpty())
+            mtdBody.append(invokeGetClass(getThisFieldCode(schemaTypeMtd, 
specFields.get(schemaClass))).ret());

Review comment:
       BTW this can be a constant "schemaClass", right?

##########
File path: 
modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
##########
@@ -838,73 +1267,105 @@ private static void addNodeConstructDefaultMethod(
 
         Variable keyVar = constructDfltMtd.getScope().getVariable("key");
 
-        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructDfltMtd.getScope())
-            .expression(keyVar);
+        StringSwitchBuilder switchBuilder = new 
StringSwitchBuilder(constructDfltMtd.getScope()).expression(keyVar);
 
-        for (Field schemaField : concat(asList(schemaFields), 
extensionsFields)) {
-            if (!isValue(schemaField))
-                continue;
+        for (Field schemaField : concat(schemaFields, internalFields)) {
+            if (isValue(schemaField) || isPolymorphicId(schemaField)) {
+                String fieldName = schemaField.getName();
 
-            if (!schemaField.getAnnotation(Value.class).hasDefault()) {
-                switchBuilder.addCase(schemaField.getName(), new 
BytecodeBlock());
+                if (isValue(schemaField) && !hasDefault(schemaField) ||
+                    isPolymorphicId(schemaField) && 
!schemaField.getAnnotation(PolymorphicId.class).hasDefault()) {
+                    // return;
+                    switchBuilder.addCase(fieldName, new 
BytecodeBlock().ret());
+                } else {
+                    FieldDefinition fieldDef = fieldDefs.get(fieldName);
+                    FieldDefinition specFieldDef = 
specFields.get(schemaField.getDeclaringClass());
 
-                continue;
+                    // this.field = spec_#.field;
+                    switchBuilder.addCase(
+                        fieldName,
+                        addNodeConstructDefault(constructDfltMtd, schemaField, 
fieldDef, specFieldDef).ret()
+                    );
+                }
             }
+        }
 
-            FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
-
-            Class<?> schemaFieldType = schemaField.getType();
-
-            // defaultValue = _spec#.field;
-            FieldDefinition specField = 
specFields.get(schemaField.getDeclaringClass());
-            BytecodeExpression defaultValue = 
constructDfltMtd.getThis().getField(specField).getField(schemaField);
+        if (!polymorphicFieldsByExtension.isEmpty()) {
+            StringSwitchBuilder switchBuilderTypeId = 
typeIdSwitchBuilder(constructDfltMtd, polymorphicTypeIdFieldDef);
+
+            for (Map.Entry<Class<?>, List<Field>> e : 
polymorphicFieldsByExtension.entrySet()) {
+                StringSwitchBuilder switchBuilderPolymorphicExtension = new 
StringSwitchBuilder(constructDfltMtd.getScope())
+                    .expression(keyVar)
+                    .defaultCase(throwException(NoSuchElementException.class, 
keyVar));
+
+                for (Field polymorphicField : e.getValue()) {
+                    if (isValue(polymorphicField)) {
+                        String fieldName = fieldName(polymorphicField);
+
+                        if (!hasDefault(polymorphicField)) {
+                            // return;
+                            switchBuilder.addCase(fieldName, new 
BytecodeBlock().ret());
+                        }
+                        else {
+                            FieldDefinition fieldDef = 
fieldDefs.get(fieldName);
+                            FieldDefinition specFieldDef = 
specFields.get(polymorphicField.getDeclaringClass());
+
+                            // this.field = spec_#.field;
+                            switchBuilderPolymorphicExtension.addCase(
+                                polymorphicField.getName(),
+                                addNodeConstructDefault(constructDfltMtd, 
polymorphicField, fieldDef, specFieldDef).ret()
+                            );
+                        }
+                    }
+                }
 
-            // defaultValue = Box.valueOf(defaultValue); // Boxing.
-            if (schemaFieldType.isPrimitive()) {
-                defaultValue = invokeStatic(
-                    fieldDef.getType(),
-                    "valueOf",
-                    fieldDef.getType(),
-                    singleton(defaultValue)
+                switchBuilderTypeId.addCase(
+                    polymorphicInstanceId(e.getKey()),
+                    switchBuilderPolymorphicExtension.build()
                 );
             }
 
-            // defaultValue = defaultValue.clone();
-            if (schemaFieldType.isArray())
-                defaultValue = defaultValue.invoke("clone", 
Object.class).cast(schemaFieldType);
-
-            // this.field = defaultValue;
-            BytecodeBlock caseClause = new BytecodeBlock()
-                .append(constructDfltMtd.getThis().setField(fieldDef, 
defaultValue));
-
-            switchBuilder.addCase(schemaField.getName(), caseClause);
+            constructDfltMtd.getBody()
+                .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+                .append(switchBuilderTypeId.build())
+                .ret();
+        }
+        else {
+            constructDfltMtd.getBody()
+                
.append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, 
keyVar)).build())
+                .ret();
         }
-
-        // Default option is to throw "NoSuchElementException(key)".
-        switchBuilder.defaultCase(new BytecodeBlock()
-            .append(newInstance(NoSuchElementException.class, keyVar))
-            .throwObject()
-        );
-
-        constructDfltMtd.getBody().append(switchBuilder.build()).ret();
     }
 
     /**
      * Copies field into itself or instantiates it if the field is null.
-     * @param mtd Method definition.
-     * @param fieldDef Field definition.
+     *
+     * @param mtd       Method definition.
+     * @param fieldDefs Field definitions.
      * @return Bytecode expression.
      */
-    @NotNull private static BytecodeExpression copyNodeField(MethodDefinition 
mtd, FieldDefinition fieldDef) {
-        return mtd.getThis().setField(fieldDef, inlineIf(
-            isNull(mtd.getThis().getField(fieldDef)),
-            newInstance(fieldDef.getType()),
-            
mtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
-        ));
+    private static BytecodeExpression copyNodeField(MethodDefinition mtd, 
FieldDefinition... fieldDefs) {
+        assert !nullOrEmpty(fieldDefs);
+
+        // this.field;
+        BytecodeExpression getFieldCode = getThisFieldCode(mtd, fieldDefs);
+
+        ParameterizedType fieldType = fieldDefs[fieldDefs.length - 
1].getType();
+
+        // (this.field == null) ? new ValueNode() : 
(ValueNode)this.field.copy();
+        BytecodeExpression value = inlineIf(
+            isNull(getFieldCode),
+            newInstance(fieldType),
+            getFieldCode.invoke(COPY).cast(fieldType)
+        );
+
+        // this.field = (this.field == null) ? new ValueNode() : 
(ValueNode)this.field.copy();

Review comment:
       You can use temp variables in comments to avoid duplications. 
Technically, every expression in code is a temp variable, right?




-- 
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