You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by ib...@apache.org on 2021/11/01 09:17:10 UTC
[ignite-3] branch main updated: IGNITE-14645 Support polymorphic
configuration nodes. (#366)
This is an automated email from the ASF dual-hosted git repository.
ibessonov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 445f455 IGNITE-14645 Support polymorphic configuration nodes. (#366)
445f455 is described below
commit 445f45591ddeaf541140c4c5b2d89f72cd387d08
Author: Kirill Tkalenko <tk...@yandex.ru>
AuthorDate: Mon Nov 1 12:15:32 2021 +0300
IGNITE-14645 Support polymorphic configuration nodes. (#366)
---
.../facebook/presto/bytecode/ClassGenerator.java | 15 +-
.../ignite/client/handler/ITClientHandlerTest.java | 1 +
.../apache/ignite/client/AbstractClientTest.java | 1 +
.../configuration/processor/ITProcessorTest.java | 113 +-
.../ErrorPolymorphic0ConfigurationSchema.java | 29 +
.../ErrorPolymorphic1ConfigurationSchema.java | 29 +
.../ErrorPolymorphic2ConfigurationSchema.java | 29 +
.../ErrorPolymorphic3ConfigurationSchema.java | 29 +
.../ErrorPolymorphic4ConfigurationSchema.java | 28 +
.../ErrorPolymorphic5ConfigurationSchema.java | 29 +
.../ErrorPolymorphic6ConfigurationSchema.java | 31 +
.../ErrorPolymorphic7ConfigurationSchema.java | 35 +
.../ErrorPolymorphic8ConfigurationSchema.java | 36 +
...rorPolymorphicInstance0ConfigurationSchema.java | 29 +
...rorPolymorphicInstance1ConfigurationSchema.java | 29 +
...rorPolymorphicInstance2ConfigurationSchema.java | 29 +
...rorPolymorphicInstance3ConfigurationSchema.java | 27 +
...rorPolymorphicInstance4ConfigurationSchema.java | 29 +
...rorPolymorphicInstance5ConfigurationSchema.java | 33 +
...rorPolymorphicInstance6ConfigurationSchema.java | 31 +
.../polymorphic/SimpleConfigurationSchema.java | 36 +
.../SimplePolymorphicConfigurationSchema.java | 36 +
...mplePolymorphicInstanceConfigurationSchema.java | 36 +
.../polymorphic/SimpleRootConfigurationSchema.java | 36 +
.../configuration/processor/Processor.java | 595 ++++--
.../internal/configuration/processor/Utils.java | 26 +
.../configuration/processor/UtilsTest.java | 44 +
.../ConfigurationReadOnlyException.java | 32 +
...nfigurationWrongPolymorphicTypeIdException.java | 32 +
.../ignite/configuration/NamedListChange.java | 18 +-
.../apache/ignite/configuration/NamedListView.java | 8 +-
.../ignite/configuration/PolymorphicChange.java | 28 +
.../annotation/PolymorphicConfig.java | 40 +
.../annotation/PolymorphicConfigInstance.java | 43 +
.../configuration/annotation/PolymorphicId.java | 43 +
.../configuration/ConfigurationChanger.java | 21 +-
.../configuration/ConfigurationManager.java | 14 +-
.../internal/configuration/ConfigurationNode.java | 6 +-
.../configuration/ConfigurationRegistry.java | 143 +-
.../configuration/ConfigurationTreeWrapper.java | 68 +
.../DirectConfigurationTreeWrapper.java | 46 +
.../configuration/DirectDynamicProperty.java | 6 +-
.../configuration/DynamicConfiguration.java | 96 +-
.../internal/configuration/DynamicProperty.java | 13 +-
.../configuration/NamedListConfiguration.java | 15 +-
.../asm/ConfigurationAsmGenerator.java | 2237 ++++++++++++++++----
.../hocon/HoconObjectConfigurationSource.java | 6 +
.../configuration/tree/ConfigurationSource.java | 16 +-
.../configuration/tree/ConstructableTreeNode.java | 4 +-
.../configuration/tree/ConverterToMapVisitor.java | 2 +-
.../internal/configuration/tree/InnerNode.java | 15 +-
.../internal/configuration/tree/NamedListNode.java | 164 +-
.../configuration/util/ConfigurationFlattener.java | 32 +-
.../util/ConfigurationNotificationsUtil.java | 20 +-
.../configuration/util/ConfigurationUtil.java | 700 +++---
.../util/KeysTrackingConfigurationVisitor.java | 5 +-
.../util/WrongPolymorphicTypeIdException.java | 33 +
.../configuration/ConfigurationChangerTest.java | 3 +-
.../configuration/ConfigurationRegistryTest.java | 161 +-
.../configuration/DirectPropertiesTest.java | 6 +-
.../configuration/TestConfigurationChanger.java | 12 +-
.../asm/ConfigurationAsmGeneratorTest.java | 358 +++-
.../configuration/hocon/HoconConverterTest.java | 78 +-
.../ConfigurationAnyListenerTest.java | 1 +
.../notifications/ConfigurationListenerTest.java | 1 +
.../internal/configuration/sample/UsageTest.java | 2 +
.../testframework/ConfigurationExtension.java | 7 +-
.../testframework/ConfigurationExtensionTest.java | 2 +-
.../testframework/InjectConfiguration.java | 12 +-
.../configuration/tree/ConfigurationArrayTest.java | 2 +-
.../tree/ConstructableTreeNodeTest.java | 2 +-
.../configuration/tree/NamedListNodeTest.java | 12 +-
.../tree/TraversableTreeNodeTest.java | 2 +-
.../configuration/util/ConfigurationUtilTest.java | 434 +++-
.../validation/ValidationUtilTest.java | 2 +-
.../ignite/internal/util/CollectionUtils.java | 185 +-
.../ignite/internal/util/CollectionUtilsTest.java | 42 +-
.../ignite/utils/ClusterServiceTestUtils.java | 1 +
.../ConfigurationPresentationTest.java | 1 +
.../ITDistributedConfigurationPropertiesTest.java | 2 +
.../ITDistributedConfigurationStorageTest.java | 1 +
.../org/apache/ignite/internal/app/IgniteImpl.java | 4 +-
.../SchemaConfigurationConverterTest.java | 1 +
.../ignite/internal/table/TableManagerTest.java | 2 +-
84 files changed, 5460 insertions(+), 1203 deletions(-)
diff --git a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
index 6364b90..612a5fd 100644
--- a/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
+++ b/modules/bytecode/src/main/java/com/facebook/presto/bytecode/ClassGenerator.java
@@ -129,20 +129,7 @@ public class ClassGenerator
Map<String, byte[]> bytecodes = new LinkedHashMap<>();
for (ClassDefinition classDefinition : classDefinitions) {
- // We call the simpler class writer first to get any errors out using simpler setting.
- // This helps when we have large queries that can potentially cause COMPUTE_FRAMES
- // (used by SmartClassWriter for doing more thorough analysis)
- ClassWriter simpleClassWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
- classDefinition.visit(simpleClassWriter);
- try {
- simpleClassWriter.toByteArray();
- }
- catch (ClassTooLargeException | MethodTooLargeException largeCodeException) {
- throw new ByteCodeTooLargeException(largeCodeException);
- }
- catch (RuntimeException e) {
- throw new CompilationException("Error compiling class: " + classDefinition.getName(), e);
- }
+ // Code associated with a simple class writer was removed due to labels reuse when re-generating the bytecode.
ClassWriter writer = new SmartClassWriter(classInfoLoader);
diff --git a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
index dc44eb0..d5424e3 100644
--- a/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
+++ b/modules/client-handler/src/integrationTest/java/org/apache/ignite/client/handler/ITClientHandlerTest.java
@@ -181,6 +181,7 @@ public class ITClientHandlerTest {
List.of(ClientConnectorConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
index 4dce665..db7593c 100644
--- a/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
+++ b/modules/client/src/test/java/org/apache/ignite/client/AbstractClientTest.java
@@ -101,6 +101,7 @@ public abstract class AbstractClientTest {
List.of(ClientConnectorConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
index b871d00..26a0fa1 100644
--- a/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
+++ b/modules/configuration-annotation-processor/src/integrationTest/java/org/apache/ignite/internal/configuration/processor/ITProcessorTest.java
@@ -26,7 +26,6 @@ import org.junit.jupiter.api.Test;
import static com.google.testing.compile.CompilationSubject.assertThat;
import static com.google.testing.compile.Compiler.javac;
import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -47,7 +46,7 @@ public class ITProcessorTest extends AbstractProcessorTest {
final Compilation status = batch.getCompilationStatus();
- assertNotEquals(Compilation.Status.FAILURE, status.status());
+ assertEquals(Compilation.Status.SUCCESS, status.status());
assertEquals(3, batch.generated().size());
@@ -68,7 +67,7 @@ public class ITProcessorTest extends AbstractProcessorTest {
BatchCompilation batchCompile = batchCompile(cls0, cls1, cls2, cls3);
- assertNotEquals(Compilation.Status.FAILURE, batchCompile.getCompilationStatus().status());
+ assertEquals(Compilation.Status.SUCCESS, batchCompile.getCompilationStatus().status());
assertEquals(4 * 3, batchCompile.generated().size());
@@ -144,6 +143,114 @@ public class ITProcessorTest extends AbstractProcessorTest {
);
}
+ /** */
+ @Test
+ void testErrorPolymorphicConfigCodeGeneration() {
+ String packageName = "org.apache.ignite.internal.configuration.processor.polymorphic";
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic0ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic1ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic2ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic3ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic4ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic5ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic6ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic7ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphic8ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance0ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance1ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance2ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance3ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance4ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance5ConfigurationSchema")
+ );
+
+ assertThrows(
+ IllegalStateException.class,
+ () -> batchCompile(packageName, "ErrorPolymorphicInstance6ConfigurationSchema")
+ );
+ }
+
+ /** */
+ @Test
+ void testSuccessPolymorphicConfigCodeGeneration() {
+ String packageName = "org.apache.ignite.internal.configuration.processor.polymorphic";
+
+ ClassName cls0 = ClassName.get(packageName, "SimplePolymorphicConfigurationSchema");
+ ClassName cls1 = ClassName.get(packageName, "SimplePolymorphicInstanceConfigurationSchema");
+ ClassName cls2 = ClassName.get(packageName, "SimpleConfigurationSchema");
+ ClassName cls3 = ClassName.get(packageName, "SimpleRootConfigurationSchema");
+
+ BatchCompilation batchCompile = batchCompile(cls0, cls1, cls2, cls3);
+
+ assertEquals(Compilation.Status.SUCCESS, batchCompile.getCompilationStatus().status());
+
+ assertEquals(4 * 3, batchCompile.generated().size());
+
+ assertTrue(batchCompile.getBySchema(cls0).allGenerated());
+ assertTrue(batchCompile.getBySchema(cls1).allGenerated());
+ assertTrue(batchCompile.getBySchema(cls2).allGenerated());
+ assertTrue(batchCompile.getBySchema(cls3).allGenerated());
+ }
+
/**
* Compile set of classes.
*
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.java
new file mode 100644
index 0000000..19097c0
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic0ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link ConfigurationRoot}.
+ */
+@PolymorphicConfig
+@ConfigurationRoot(rootName = "error")
+public class ErrorPolymorphic0ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.java
new file mode 100644
index 0000000..be32fae
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic1ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link Config}.
+ */
+@PolymorphicConfig
+@Config
+public class ErrorPolymorphic1ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.java
new file mode 100644
index 0000000..9dd9da8
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic2ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link InternalConfiguration}.
+ */
+@PolymorphicConfig
+@InternalConfiguration
+public class ErrorPolymorphic2ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.java
new file mode 100644
index 0000000..7613587
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic3ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfig} and {@link PolymorphicConfigInstance}.
+ */
+@PolymorphicConfig
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphic3ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java
new file mode 100644
index 0000000..35851be
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic4ConfigurationSchema.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import java.util.ArrayList;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+
+/**
+ * Class with {@link PolymorphicConfig} cannot have a super class.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic4ConfigurationSchema extends ArrayList {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.java
new file mode 100644
index 0000000..59eb2cb
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic5ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic5ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.java
new file mode 100644
index 0000000..78ead06
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic6ConfigurationSchema.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic6ConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public int typeId;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.java
new file mode 100644
index 0000000..e031bb1
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic7ConfigurationSchema.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfig} should contain only one string field with {@link PolymorphicId}.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic7ConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId;
+
+ /** Polymorphic type id field 2. */
+ @PolymorphicId
+ public String typeId2;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java
new file mode 100644
index 0000000..34d8ad4
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphic8ConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfig} must contain field {@link PolymorphicId}, which must be the first in the schema.
+ */
+@PolymorphicConfig
+public class ErrorPolymorphic8ConfigurationSchema {
+ /** String value. */
+ @Value
+ public String strVal;
+
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java
new file mode 100644
index 0000000..59d6d86
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance0ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link ConfigurationRoot}.
+ */
+@PolymorphicConfigInstance("error")
+@ConfigurationRoot(rootName = "error")
+public class ErrorPolymorphicInstance0ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.java
new file mode 100644
index 0000000..8dd8c84
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance1ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link Config}.
+ */
+@PolymorphicConfigInstance("error")
+@Config
+public class ErrorPolymorphicInstance1ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.java
new file mode 100644
index 0000000..f19ec41
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance2ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class cannot have {@link PolymorphicConfigInstance} and {@link InternalConfiguration}.
+ */
+@PolymorphicConfigInstance("error")
+@InternalConfiguration
+public class ErrorPolymorphicInstance2ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.java
new file mode 100644
index 0000000..2d1186e
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance3ConfigurationSchema.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} must have a super class.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance3ConfigurationSchema {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.java
new file mode 100644
index 0000000..eea435b
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance4ConfigurationSchema.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import java.util.ArrayList;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} must have a super class with {@link PolymorphicConfig}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance4ConfigurationSchema extends ArrayList {
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java
new file mode 100644
index 0000000..97808b9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance5ConfigurationSchema.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} should not contain duplicate fields
+ * (by names) with {@link PolymorphicConfig}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance5ConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+ /** String value, duplicate {@link SimplePolymorphicConfigurationSchema#str}. */
+ @Value
+ public String str;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.java
new file mode 100644
index 0000000..3b29ee6
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/ErrorPolymorphicInstance6ConfigurationSchema.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+
+/**
+ * Class with {@link PolymorphicConfigInstance} should not contain field with {@link PolymorphicId}.
+ */
+@PolymorphicConfigInstance("error")
+public class ErrorPolymorphicInstance6ConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId2;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java
new file mode 100644
index 0000000..9268729
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Configuration schema class with polymorphic configuration.
+ */
+@Config
+public class SimpleConfigurationSchema {
+ /** String value. */
+ @Value
+ public String str;
+
+ /** Polymorphic configuration. */
+ @ConfigValue
+ public SimplePolymorphicConfigurationSchema polymorphicConfig;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java
new file mode 100644
index 0000000..76cfe71
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple polymorphic configuration.
+ */
+@PolymorphicConfig
+public class SimplePolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId;
+
+ /** String value. */
+ @Value
+ public String str;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java
new file mode 100644
index 0000000..d174be9
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimplePolymorphicInstanceConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Simple instance of a polymorphic configuration.
+ */
+@PolymorphicConfigInstance("test")
+public class SimplePolymorphicInstanceConfigurationSchema extends SimplePolymorphicConfigurationSchema {
+ /** Integer value. */
+ @Value
+ public int i;
+
+ /** String value. */
+ @Value
+ public String str1;
+}
diff --git a/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java
new file mode 100644
index 0000000..7c2122a
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/integrationTest/resources/org/apache/ignite/internal/configuration/processor/polymorphic/SimpleRootConfigurationSchema.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.internal.configuration.processor.polymorphic;
+
+import org.apache.ignite.configuration.annotation.ConfigValue;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.configuration.annotation.Value;
+
+/**
+ * Configuration schema class with polymorphic configuration.
+ */
+@ConfigurationRoot(rootName = "root")
+public class SimpleRootConfigurationSchema {
+ /** String value. */
+ @Value
+ public String str;
+
+ /** Polymorphic configuration. */
+ @ConfigValue
+ public SimplePolymorphicConfigurationSchema polymorphicConfig;
+}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
index c90d8d9..37cc42d 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Processor.java
@@ -17,13 +17,15 @@
package org.apache.ignite.internal.configuration.processor;
-import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.function.Consumer;
+import java.util.stream.Stream;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
@@ -45,16 +47,22 @@ import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.TypeSpec;
+import com.squareup.javapoet.TypeVariableName;
import com.squareup.javapoet.WildcardTypeName;
import org.apache.ignite.configuration.NamedConfigurationTree;
import org.apache.ignite.configuration.NamedListChange;
import org.apache.ignite.configuration.NamedListView;
+import org.apache.ignite.configuration.PolymorphicChange;
+import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.DirectAccess;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.jetbrains.annotations.Nullable;
@@ -64,6 +72,10 @@ import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.FINAL;
import static javax.lang.model.element.Modifier.PUBLIC;
import static javax.lang.model.element.Modifier.STATIC;
+import static org.apache.ignite.internal.configuration.processor.Utils.joinSimpleName;
+import static org.apache.ignite.internal.configuration.processor.Utils.simpleName;
+import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
/**
* Annotation processor that produces configuration classes.
@@ -72,9 +84,18 @@ public class Processor extends AbstractProcessor {
/** Java file padding. */
private static final String INDENT = " ";
- /** */
+ /** {@link RootKey} class name. */
private static final ClassName ROOT_KEY_CLASSNAME = ClassName.get("org.apache.ignite.configuration", "RootKey");
+ /** {@link PolymorphicChange} class name. */
+ private static final ClassName POLYMORPHIC_CHANGE_CLASSNAME = ClassName.get(PolymorphicChange.class);
+
+ /** Error format for the superclass missing annotation. */
+ private static final String SUPERCLASS_MISSING_ANNOTATION_ERROR_FORMAT = "Superclass must have %s: %s";
+
+ /** Error format for an empty field. */
+ private static final String EMPTY_FIELD_ERROR_FORMAT = "Field %s cannot be empty: %s";
+
/** {@inheritDoc} */
@Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnvironment) {
try {
@@ -92,14 +113,14 @@ public class Processor extends AbstractProcessor {
* Processes a set of annotation types on type elements.
*
* @param roundEnvironment Processing environment.
- * @return Whether or not the set of annotation types are claimed by this processor.
+ * @return Whether the set of annotation types are claimed by this processor.
*/
private boolean process0(RoundEnvironment roundEnvironment) {
Elements elementUtils = processingEnv.getElementUtils();
- // All classes annotated with @ConfigurationRoot, @Config, @InternalConfiguration.
+ // All classes annotated with {@link #supportedAnnotationTypes}.
List<TypeElement> annotatedConfigs = roundEnvironment
- .getElementsAnnotatedWithAny(Set.of(ConfigurationRoot.class, Config.class, InternalConfiguration.class))
+ .getElementsAnnotatedWithAny(supportedAnnotationTypes())
.stream()
.filter(element -> element.getKind() == ElementKind.CLASS)
.map(TypeElement.class::cast)
@@ -110,16 +131,10 @@ public class Processor extends AbstractProcessor {
for (TypeElement clazz : annotatedConfigs) {
// Find all the fields of the schema.
- Collection<VariableElement> fields = fields(clazz);
+ List<VariableElement> fields = fields(clazz);
validate(clazz, fields);
- // Is root of the configuration.
- boolean isRootConfig = clazz.getAnnotation(ConfigurationRoot.class) != null;
-
- // Is the internal configuration.
- boolean isInternalConfig = clazz.getAnnotation(InternalConfiguration.class) != null;
-
// Get package name of the schema class
String packageName = elementUtils.getPackageOf(clazz).getQualifiedName().toString();
@@ -138,44 +153,16 @@ public class Processor extends AbstractProcessor {
if (!field.getModifiers().contains(PUBLIC))
throw new ProcessorException("Field " + clazz.getQualifiedName() + "." + field + " must be public");
- Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
-
String fieldName = field.getSimpleName().toString();
// Get configuration types (VIEW, CHANGE and so on)
TypeName interfaceGetMethodType = getInterfaceGetMethodType(field);
- ConfigValue confAnnotation = field.getAnnotation(ConfigValue.class);
- if (confAnnotation != null) {
- if (fieldTypeElement.getAnnotation(Config.class) == null) {
- throw new ProcessorException(
- "Class for @ConfigValue field must be defined as @Config: " +
- clazz.getQualifiedName() + "." + field.getSimpleName()
- );
- }
-
- if (field.getAnnotation(DirectAccess.class) != null) {
- throw new ProcessorException(
- "@DirectAccess annotation must not be present on nested configuration fields"
- );
- }
- }
+ if (field.getAnnotation(ConfigValue.class) != null)
+ checkConfigField(field, ConfigValue.class);
- NamedConfigValue namedConfigAnnotation = field.getAnnotation(NamedConfigValue.class);
- if (namedConfigAnnotation != null) {
- if (fieldTypeElement.getAnnotation(Config.class) == null) {
- throw new ProcessorException(
- "Class for @NamedConfigValue field must be defined as @Config: " +
- clazz.getQualifiedName() + "." + field.getSimpleName()
- );
- }
-
- if (field.getAnnotation(DirectAccess.class) != null) {
- throw new ProcessorException(
- "@DirectAccess annotation must not be present on nested configuration fields"
- );
- }
- }
+ if (field.getAnnotation(NamedConfigValue.class) != null)
+ checkConfigField(field, NamedConfigValue.class);
Value valueAnnotation = field.getAnnotation(Value.class);
if (valueAnnotation != null) {
@@ -189,16 +176,42 @@ public class Processor extends AbstractProcessor {
}
}
+ PolymorphicId polymorphicId = field.getAnnotation(PolymorphicId.class);
+ if (polymorphicId != null) {
+ if (!isStringClass(field.asType())) {
+ throw new ProcessorException(String.format(
+ "%s %s.%s field field must be String.",
+ simpleName(PolymorphicId.class),
+ clazz.getQualifiedName(),
+ field.getSimpleName()
+ ));
+ }
+ }
+
createGetters(configurationInterfaceBuilder, fieldName, interfaceGetMethodType);
}
+ // Is root of the configuration.
+ boolean isRootConfig = clazz.getAnnotation(ConfigurationRoot.class) != null;
+
+ // Is the internal configuration.
+ boolean isInternalConfig = clazz.getAnnotation(InternalConfiguration.class) != null;
+
+ // Is a polymorphic configuration.
+ boolean isPolymorphicConfig = clazz.getAnnotation(PolymorphicConfig.class) != null;
+
+ // Is an instance of a polymorphic configuration.
+ boolean isPolymorphicInstance = clazz.getAnnotation(PolymorphicConfigInstance.class) != null;
+
// Create VIEW and CHANGE classes.
createPojoBindings(
fields,
schemaClassName,
configurationInterfaceBuilder,
- isInternalConfig && !isRootConfig,
- clazz
+ (isInternalConfig && !isRootConfig) || isPolymorphicInstance,
+ clazz,
+ isPolymorphicConfig,
+ isPolymorphicInstance
);
if (isRootConfig)
@@ -236,9 +249,9 @@ public class Processor extends AbstractProcessor {
/**
* Create getters for configuration class.
*
- * @param configurationInterfaceBuilder
- * @param fieldName
- * @param interfaceGetMethodType
+ * @param configurationInterfaceBuilder Interface builder.
+ * @param fieldName Field name.
+ * @param interfaceGetMethodType Return type.
*/
private static void createGetters(
TypeSpec.Builder configurationInterfaceBuilder,
@@ -255,7 +268,8 @@ public class Processor extends AbstractProcessor {
/**
* Get types for configuration classes generation.
- * @param field
+ *
+ * @param field Field.
* @return Bundle with all types for configuration
*/
private static TypeName getInterfaceGetMethodType(VariableElement field) {
@@ -274,11 +288,17 @@ public class Processor extends AbstractProcessor {
TypeName viewClassType = Utils.getViewName((ClassName) baseType);
TypeName changeClassType = Utils.getChangeName((ClassName) baseType);
- interfaceGetMethodType = ParameterizedTypeName.get(ClassName.get(NamedConfigurationTree.class), interfaceGetType, viewClassType, changeClassType);
+ interfaceGetMethodType = ParameterizedTypeName.get(
+ ClassName.get(NamedConfigurationTree.class),
+ interfaceGetType,
+ viewClassType,
+ changeClassType
+ );
}
Value valueAnnotation = field.getAnnotation(Value.class);
- if (valueAnnotation != null) {
+ PolymorphicId polymorphicIdAnnotation = field.getAnnotation(PolymorphicId.class);
+ if (valueAnnotation != null || polymorphicIdAnnotation != null) {
// It is necessary to use class names without loading classes so that we won't
// accidentally get NoClassDefFoundError
ClassName confValueClass = ClassName.get("org.apache.ignite.configuration", "ConfigurationValue");
@@ -297,18 +317,22 @@ public class Processor extends AbstractProcessor {
/**
* Create VIEW and CHANGE classes and methods.
*
- * @param fields List of configuration fields.
+ * @param fields Collection of configuration fields.
* @param schemaClassName Class name of schema.
* @param configurationInterfaceBuilder Configuration interface builder.
* @param extendBaseSchema {@code true} if extending base schema interfaces.
* @param realSchemaClass Class descriptor.
+ * @param isPolymorphicConfig Is a polymorphic configuration.
+ * @param isPolymorphicInstanceConfig Is an instance of polymorphic configuration.
*/
private void createPojoBindings(
Collection<VariableElement> fields,
ClassName schemaClassName,
TypeSpec.Builder configurationInterfaceBuilder,
boolean extendBaseSchema,
- TypeElement realSchemaClass
+ TypeElement realSchemaClass,
+ boolean isPolymorphicConfig,
+ boolean isPolymorphicInstanceConfig
) {
ClassName viewClsName = Utils.getViewName(schemaClassName);
ClassName changeClsName = Utils.getChangeName(schemaClassName);
@@ -352,6 +376,9 @@ public class Processor extends AbstractProcessor {
if (changeBaseSchemaInterfaceType != null)
changeClsBuilder.addSuperinterface(changeBaseSchemaInterfaceType);
+ if (isPolymorphicInstanceConfig)
+ changeClsBuilder.addSuperinterface(POLYMORPHIC_CHANGE_CLASSNAME);
+
ClassName consumerClsName = ClassName.get(Consumer.class);
for (VariableElement field : fields) {
@@ -385,36 +412,54 @@ public class Processor extends AbstractProcessor {
);
}
- {
- MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
- .addModifiers(PUBLIC, ABSTRACT)
- .returns(viewFieldType);
+ MethodSpec.Builder getMtdBuilder = MethodSpec.methodBuilder(fieldName)
+ .addModifiers(PUBLIC, ABSTRACT)
+ .returns(viewFieldType);
- viewClsBuilder.addMethod(getMtdBuilder.build());
- }
+ viewClsBuilder.addMethod(getMtdBuilder.build());
- {
- String changeMtdName = "change" + capitalize(fieldName);
+ // Read only.
+ if (field.getAnnotation(PolymorphicId.class) != null)
+ continue;
- {
- MethodSpec.Builder changeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
- .addModifiers(PUBLIC, ABSTRACT)
- .returns(changeClsName);
+ String changeMtdName = "change" + capitalize(fieldName);
- if (valAnnotation != null) {
- if (schemaFieldType.getKind() == TypeKind.ARRAY)
- changeMtdBuilder.varargs(true);
+ MethodSpec.Builder changeMtdBuilder = MethodSpec.methodBuilder(changeMtdName)
+ .addModifiers(PUBLIC, ABSTRACT)
+ .returns(changeClsName);
- changeMtdBuilder.addParameter(changeFieldType, fieldName);
- }
- else
- changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
+ if (valAnnotation != null) {
+ if (schemaFieldType.getKind() == TypeKind.ARRAY)
+ changeMtdBuilder.varargs(true);
- changeClsBuilder.addMethod(changeMtdBuilder.build());
- }
+ changeMtdBuilder.addParameter(changeFieldType, fieldName);
}
+ else
+ changeMtdBuilder.addParameter(ParameterizedTypeName.get(consumerClsName, changeFieldType), fieldName);
+
+ changeClsBuilder.addMethod(changeMtdBuilder.build());
}
+ if (isPolymorphicConfig) {
+ // Parameter type: Class<T>.
+ ParameterizedTypeName parameterType = ParameterizedTypeName.get(
+ ClassName.get(Class.class),
+ TypeVariableName.get("T")
+ );
+
+ // Variable type, for example: <T extends SimpleChange & PolymorphicInstance>.
+ TypeVariableName typeVariable = TypeVariableName.get("T", changeClsName, POLYMORPHIC_CHANGE_CLASSNAME);
+
+ // Method like: <T extends SimpleChange> T convert(Class<T> changeClass);
+ MethodSpec.Builder convertMtdBuilder = MethodSpec.methodBuilder("convert")
+ .addModifiers(PUBLIC, ABSTRACT)
+ .addTypeVariable(typeVariable)
+ .addParameter(parameterType, "changeClass")
+ .returns(TypeVariableName.get("T"));
+
+ changeClsBuilder.addMethod(convertMtdBuilder.build());
+ }
+
TypeSpec viewCls = viewClsBuilder.build();
TypeSpec changeCls = changeClsBuilder.build();
@@ -430,8 +475,8 @@ public class Processor extends AbstractProcessor {
.build()
.writeTo(processingEnv.getFiler());
}
- catch (IOException e) {
- throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, e);
+ catch (Throwable throwable) {
+ throw new ProcessorException("Failed to generate class " + packageName + "." + cls.name, throwable);
}
}
@@ -482,7 +527,7 @@ public class Processor extends AbstractProcessor {
* @param type Class type.
* @return Class fields.
*/
- private static Collection<VariableElement> fields(TypeElement type) {
+ private static List<VariableElement> fields(TypeElement type) {
return type.getEnclosedElements().stream()
.filter(el -> el.getKind() == ElementKind.FIELD)
.map(VariableElement.class::cast)
@@ -496,88 +541,364 @@ public class Processor extends AbstractProcessor {
* @param fields Class fields.
* @throws ProcessorException If the class validation fails.
*/
- private void validate(TypeElement clazz, Collection<VariableElement> fields) {
+ private void validate(TypeElement clazz, List<VariableElement> fields) {
if (clazz.getAnnotation(InternalConfiguration.class) != null) {
- if (clazz.getAnnotation(Config.class) != null) {
- throw new ProcessorException(String.format(
- "Class with @%s is not allowed with @%s: %s",
- Config.class.getSimpleName(),
- InternalConfiguration.class.getSimpleName(),
- clazz.getQualifiedName()
- ));
- }
- else if (clazz.getAnnotation(ConfigurationRoot.class) != null) {
- if (!isObjectClass(clazz.getSuperclass())) {
+ checkIncompatibleClassAnnotations(
+ clazz,
+ InternalConfiguration.class,
+ Config.class, PolymorphicConfig.class, PolymorphicConfigInstance.class
+ );
+
+ checkNotContainsPolymorphicIdField(clazz, InternalConfiguration.class, fields);
+
+ if (clazz.getAnnotation(ConfigurationRoot.class) != null)
+ checkNotExistSuperClass(clazz, InternalConfiguration.class);
+ else {
+ checkExistSuperClass(clazz, InternalConfiguration.class);
+
+ TypeElement superClazz = superClass(clazz);
+
+ if (superClazz.getAnnotation(InternalConfiguration.class) != null) {
throw new ProcessorException(String.format(
- "Class with @%s and @%s should not have a superclass: %s",
- ConfigurationRoot.class.getSimpleName(),
- InternalConfiguration.class.getSimpleName(),
+ "Superclass must not have %s: %s",
+ simpleName(InternalConfiguration.class),
clazz.getQualifiedName()
));
}
+
+ checkSuperclassContainAnyAnnotation(clazz, superClazz, ConfigurationRoot.class, Config.class);
+
+ checkNoConflictFieldNames(clazz, superClazz, fields, fields(superClazz));
}
- else if (isObjectClass(clazz.getSuperclass())) {
+ }
+ else if (clazz.getAnnotation(PolymorphicConfig.class) != null) {
+ checkIncompatibleClassAnnotations(
+ clazz,
+ PolymorphicConfig.class,
+ ConfigurationRoot.class, Config.class, PolymorphicConfigInstance.class
+ );
+
+ checkNotExistSuperClass(clazz, PolymorphicConfig.class);
+
+ List<VariableElement> typeIdFields = collectAnnotatedFields(fields, PolymorphicId.class);
+
+ if (typeIdFields.size() != 1 || fields.indexOf(typeIdFields.get(0)) != 0) {
throw new ProcessorException(String.format(
- "Class with @%s must have a superclass: %s",
- InternalConfiguration.class.getSimpleName(),
+ "Class with %s must contain one field with %s and it should be the first in the schema: %s",
+ simpleName(PolymorphicConfig.class),
+ simpleName(PolymorphicId.class),
clazz.getQualifiedName()
));
}
- else {
- TypeElement superClazz = processingEnv
- .getElementUtils()
- .getTypeElement(clazz.getSuperclass().toString());
+ }
+ else if (clazz.getAnnotation(PolymorphicConfigInstance.class) != null) {
+ checkIncompatibleClassAnnotations(
+ clazz,
+ PolymorphicConfigInstance.class,
+ ConfigurationRoot.class, Config.class
+ );
- if (superClazz.getAnnotation(InternalConfiguration.class) != null) {
- throw new ProcessorException(String.format(
- "Superclass must not have @%s: %s",
- InternalConfiguration.class.getSimpleName(),
- clazz.getQualifiedName()
- ));
- }
- else if (superClazz.getAnnotation(ConfigurationRoot.class) == null &&
- superClazz.getAnnotation(Config.class) == null) {
- throw new ProcessorException(String.format(
- "Superclass must have @%s or @%s: %s",
- ConfigurationRoot.class.getSimpleName(),
- Config.class.getSimpleName(),
- clazz.getQualifiedName()
- ));
- }
- else {
- Set<Name> superClazzFieldNames = fields(superClazz).stream()
- .map(VariableElement::getSimpleName)
- .collect(toSet());
+ checkNotContainsPolymorphicIdField(clazz, PolymorphicConfigInstance.class, fields);
- Collection<Name> duplicateFieldNames = fields.stream()
- .map(VariableElement::getSimpleName)
- .filter(superClazzFieldNames::contains)
- .collect(toList());
+ String id = clazz.getAnnotation(PolymorphicConfigInstance.class).value();
- if (!duplicateFieldNames.isEmpty()) {
- throw new ProcessorException(String.format(
- "Duplicate field names are not allowed [class=%s, superClass=%s, fields=%s]",
- clazz.getQualifiedName(),
- superClazz.getQualifiedName(),
- duplicateFieldNames
- ));
- }
- }
+ if (id == null || id.isBlank()) {
+ throw new ProcessorException(String.format(
+ EMPTY_FIELD_ERROR_FORMAT,
+ simpleName(PolymorphicConfigInstance.class) + ".id()",
+ clazz.getQualifiedName()
+ ));
}
+
+ checkExistSuperClass(clazz, PolymorphicConfigInstance.class);
+
+ TypeElement superClazz = superClass(clazz);
+
+ checkSuperclassContainAnyAnnotation(clazz, superClazz, PolymorphicConfig.class);
+
+ checkNoConflictFieldNames(clazz, superClazz, fields, fields(superClazz));
}
+ else if (clazz.getAnnotation(ConfigurationRoot.class) != null)
+ checkNotContainsPolymorphicIdField(clazz, ConfigurationRoot.class, fields);
+ else if (clazz.getAnnotation(Config.class) != null)
+ checkNotContainsPolymorphicIdField(clazz, Config.class, fields);
}
/** {@inheritDoc} */
@Override public Set<String> getSupportedAnnotationTypes() {
- return Set.of(
- Config.class.getCanonicalName(),
- ConfigurationRoot.class.getCanonicalName(),
- InternalConfiguration.class.getCanonicalName()
- );
+ return Set.copyOf(viewReadOnly(supportedAnnotationTypes(), Class::getCanonicalName));
}
/** {@inheritDoc} */
@Override public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
+
+ /**
+ * @return Annotation types supported by this processor.
+ */
+ private Set<Class<? extends Annotation>> supportedAnnotationTypes() {
+ return Set.of(
+ Config.class,
+ ConfigurationRoot.class,
+ InternalConfiguration.class,
+ PolymorphicConfig.class,
+ PolymorphicConfigInstance.class
+ );
+ }
+
+ /**
+ * Getting a superclass.
+ *
+ * @param clazz Class type.
+ * @return Superclass type.
+ */
+ private TypeElement superClass(TypeElement clazz) {
+ return processingEnv.getElementUtils().getTypeElement(clazz.getSuperclass().toString());
+ }
+
+ /**
+ * Returns the first annotation found for the class.
+ *
+ * @param clazz Class type.
+ * @param annotationClasses Annotation classes that will be searched for the class.
+ * @return First annotation found.
+ */
+ @SafeVarargs
+ @Nullable private static Annotation findFirst(
+ TypeElement clazz,
+ Class<? extends Annotation>... annotationClasses
+ ) {
+ return Stream.of(annotationClasses).map(clazz::getAnnotation).filter(Objects::nonNull).findFirst().orElse(null);
+ }
+
+ /**
+ * Search for duplicate class fields by name.
+ *
+ * @param fields1 First class fields.
+ * @param fields2 Second class fields.
+ * @return Field names.
+ */
+ private static Collection<Name> findDuplicates(
+ Collection<VariableElement> fields1,
+ Collection<VariableElement> fields2
+ ) {
+ if (fields1.isEmpty() || fields2.isEmpty())
+ return List.of();
+
+ Set<Name> filedNames1 = fields1.stream()
+ .map(VariableElement::getSimpleName)
+ .collect(toSet());
+
+ return fields2.stream()
+ .map(VariableElement::getSimpleName)
+ .filter(filedNames1::contains)
+ .collect(toList());
+ }
+
+ /**
+ * Checking a class field with annotations {@link ConfigValue} or {@link NamedConfigValue}.
+ *
+ * @param field Class field.
+ * @param annotationClass Field annotation: {@link ConfigValue} or {@link NamedConfigValue}.
+ * @throws ProcessorException If the check is not successful.
+ */
+ private void checkConfigField(
+ VariableElement field,
+ Class<? extends Annotation> annotationClass
+ ) {
+ assert annotationClass == ConfigValue.class || annotationClass == NamedConfigValue.class : annotationClass;
+ assert field.getAnnotation(annotationClass) != null : field.getEnclosingElement() + "." + field;
+
+ Element fieldTypeElement = processingEnv.getTypeUtils().asElement(field.asType());
+
+ if (fieldTypeElement.getAnnotation(Config.class) == null &&
+ fieldTypeElement.getAnnotation(PolymorphicConfig.class) == null) {
+ throw new ProcessorException(String.format(
+ "Class for %s field must be defined as %s: %s.%s",
+ simpleName(annotationClass),
+ joinSimpleName(" or ", Config.class, PolymorphicConfig.class),
+ field.getEnclosingElement(),
+ field.getSimpleName()
+ ));
+ }
+
+ if (field.getAnnotation(DirectAccess.class) != null) {
+ throw new ProcessorException(String.format(
+ "%s annotation must not be present on nested configuration fields: %s.%s",
+ simpleName(DirectAccess.class),
+ field.getEnclosingElement(),
+ field.getSimpleName()
+ ));
+ }
+ }
+
+ /**
+ * Check if a class type is {@link String}.
+ *
+ * @param type Class type.
+ * @return {@code true} if class type is {@link String}.
+ */
+ private boolean isStringClass(TypeMirror type) {
+ TypeMirror objectType = processingEnv
+ .getElementUtils()
+ .getTypeElement(String.class.getCanonicalName())
+ .asType();
+
+ return objectType.equals(type);
+ }
+
+ /**
+ * Collect fields with annotation.
+ *
+ * @param fields Fields.
+ * @param annotationClass Annotation class.
+ * @return Fields with annotation.
+ */
+ private static List<VariableElement> collectAnnotatedFields(
+ Collection<VariableElement> fields,
+ Class<? extends Annotation> annotationClass
+ ) {
+ return fields.stream().filter(f -> f.getAnnotation(annotationClass) != null).collect(toList());
+ }
+
+ /**
+ * Checks for an incompatible class annotation with {@code clazzAnnotation}.
+ *
+ * @param clazz Class type.
+ * @param clazzAnnotation Class annotation.
+ * @param incompatibleAnnotations Incompatible class annotations with {@code clazzAnnotation}.
+ * @throws ProcessorException If there is an incompatible class annotation with {@code clazzAnnotation}.
+ */
+ private void checkIncompatibleClassAnnotations(
+ TypeElement clazz,
+ Class<? extends Annotation> clazzAnnotation,
+ Class<? extends Annotation>... incompatibleAnnotations
+ ) {
+ assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+ assert !nullOrEmpty(incompatibleAnnotations);
+
+ Annotation incompatible = findFirst(clazz, incompatibleAnnotations);
+
+ if (incompatible != null) {
+ throw new ProcessorException(String.format(
+ "Class with %s is not allowed with %s: %s",
+ simpleName(incompatible.getClass()),
+ simpleName(clazzAnnotation),
+ clazz.getQualifiedName()
+ ));
+ }
+ }
+
+ /**
+ * Checks that the class has a superclass.
+ *
+ * @param clazz Class type.
+ * @param clazzAnnotation Class annotation.
+ * @throws ProcessorException If the class doesn't have a superclass.
+ */
+ private void checkExistSuperClass(TypeElement clazz, Class<? extends Annotation> clazzAnnotation) {
+ assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+ if (isObjectClass(clazz.getSuperclass())) {
+ throw new ProcessorException(String.format(
+ "Class with %s should not have a superclass: %s",
+ simpleName(clazzAnnotation),
+ clazz.getQualifiedName()
+ ));
+ }
+ }
+
+ /**
+ * Checks that the class should not have a superclass.
+ *
+ * @param clazz Class type.
+ * @param clazzAnnotation Class annotation.
+ * @throws ProcessorException If the class have a superclass.
+ */
+ private void checkNotExistSuperClass(TypeElement clazz, Class<? extends Annotation> clazzAnnotation) {
+ assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+ if (!isObjectClass(clazz.getSuperclass())) {
+ throw new ProcessorException(String.format(
+ "Class with %s should not have a superclass: %s",
+ simpleName(clazzAnnotation),
+ clazz.getQualifiedName()
+ ));
+ }
+ }
+
+ /**
+ * Checks that the class does not have a field with {@link PolymorphicId}.
+ *
+ * @param clazz Class type.
+ * @param clazzAnnotation Class annotation.
+ * @param clazzfields Class fields.
+ * @throws ProcessorException If the class has a field with {@link PolymorphicId}.
+ */
+ private void checkNotContainsPolymorphicIdField(
+ TypeElement clazz,
+ Class<? extends Annotation> clazzAnnotation,
+ List<VariableElement> clazzfields
+ ) {
+ assert clazz.getAnnotation(clazzAnnotation) != null : clazz.getQualifiedName();
+
+ if (!collectAnnotatedFields(clazzfields, PolymorphicId.class).isEmpty()) {
+ throw new ProcessorException(String.format(
+ "Class with %s cannot have a field with %s: %s",
+ simpleName(clazzAnnotation),
+ simpleName(PolymorphicId.class),
+ clazz.getQualifiedName()
+ ));
+ }
+ }
+
+ /**
+ * Checks that there is no conflict of field names between classes.
+ *
+ * @param clazz0 First class type.
+ * @param clazz1 Second class type.
+ * @param clazzFields0 First class fields.
+ * @param clazzFields1 Second class fields.
+ * @throws ProcessorException If there is a conflict of field names between classes.
+ */
+ private void checkNoConflictFieldNames(
+ TypeElement clazz0,
+ TypeElement clazz1,
+ List<VariableElement> clazzFields0,
+ List<VariableElement> clazzFields1
+ ) {
+ Collection<Name> duplicateFieldNames = findDuplicates(clazzFields0, clazzFields1);
+
+ if (!duplicateFieldNames.isEmpty()) {
+ throw new ProcessorException(String.format(
+ "Duplicate field names are not allowed [class=%s, superClass=%s, fields=%s]",
+ clazz0.getQualifiedName(),
+ clazz1.getQualifiedName(),
+ duplicateFieldNames
+ ));
+ }
+ }
+
+ /**
+ * Checks if the superclass has at least one annotation from {@code superClazzAnnotations}.
+ *
+ * @param clazz Class type.
+ * @param superClazz Superclass type.
+ * @param superClazzAnnotations Superclass annotations.
+ * @throws ProcessorException If the superclass has none of the annotations from {@code superClazzAnnotations}.
+ */
+ private void checkSuperclassContainAnyAnnotation(
+ TypeElement clazz,
+ TypeElement superClazz,
+ Class<? extends Annotation>... superClazzAnnotations
+ ) {
+ if (Stream.of(superClazzAnnotations).allMatch(a -> superClazz.getAnnotation(a) == null)) {
+ throw new ProcessorException(String.format(
+ SUPERCLASS_MISSING_ANNOTATION_ERROR_FORMAT,
+ joinSimpleName(" or ", superClazzAnnotations),
+ clazz.getQualifiedName()
+ ));
+ }
+ }
}
diff --git a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
index ffa0b4f..e7c6790 100644
--- a/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
+++ b/modules/configuration-annotation-processor/src/main/java/org/apache/ignite/internal/configuration/processor/Utils.java
@@ -16,8 +16,12 @@
*/
package org.apache.ignite.internal.configuration.processor;
+import java.lang.annotation.Annotation;
+import java.util.stream.Stream;
import com.squareup.javapoet.ClassName;
+import static java.util.stream.Collectors.joining;
+
/**
* Annotation processing utilities.
*/
@@ -64,4 +68,26 @@ public class Utils {
schemaClassName.simpleName().replace("ConfigurationSchema", "Change")
);
}
+
+ /**
+ * Returns the simple name of the annotation as: @Config.
+ *
+ * @param annotationClass Annotation class.
+ * @return Simple name of the annotation.
+ */
+ public static String simpleName(Class<? extends Annotation> annotationClass) {
+ return '@' + annotationClass.getSimpleName();
+ }
+
+ /**
+ * Create a string with simple annotation names like: @Config and @PolymorphicConfig.
+ *
+ * @param delimiter Delimiter between elements.
+ * @param annotations Annotations.
+ * @return String with simple annotation names.
+ */
+ @SafeVarargs
+ public static String joinSimpleName(String delimiter, Class<? extends Annotation>... annotations) {
+ return Stream.of(annotations).map(Utils::simpleName).collect(joining(delimiter));
+ }
}
diff --git a/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java
new file mode 100644
index 0000000..a829f89
--- /dev/null
+++ b/modules/configuration-annotation-processor/src/test/java/org/apache/ignite/internal/configuration/processor/UtilsTest.java
@@ -0,0 +1,44 @@
+package org.apache.ignite.internal.configuration.processor;/*
+ * 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.
+ */
+
+import org.apache.ignite.configuration.annotation.Config;
+import org.apache.ignite.configuration.annotation.ConfigurationRoot;
+import org.apache.ignite.internal.configuration.processor.Utils;
+import org.junit.jupiter.api.Test;
+
+import static org.apache.ignite.internal.configuration.processor.Utils.joinSimpleName;
+import static org.apache.ignite.internal.configuration.processor.Utils.simpleName;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Class for testing the {@link Utils}.
+ */
+public class UtilsTest {
+ /** */
+ @Test
+ void testSimpleName() {
+ assertEquals("@Config", simpleName(Config.class));
+ }
+
+ /** */
+ @Test
+ void testJoinSimpleName() {
+ assertEquals("@Config", joinSimpleName(" and ", Config.class));
+ assertEquals("@Config or @ConfigurationRoot", joinSimpleName(" or ", Config.class, ConfigurationRoot.class));
+ assertEquals("", joinSimpleName(" or "));
+ }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java
new file mode 100644
index 0000000..b485d8f
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationReadOnlyException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+/**
+ * This exception is used if an attempt was made to update a configuration value in read-only mode.
+ */
+public class ConfigurationReadOnlyException extends RuntimeException {
+ /**
+ * Constructor.
+ *
+ * @param message Error message.
+ */
+ public ConfigurationReadOnlyException(String message) {
+ super(message);
+ }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java
new file mode 100644
index 0000000..88da743
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/ConfigurationWrongPolymorphicTypeIdException.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+/**
+ * Thrown when the wrong (unknown) type of polymorphic configuration is used.
+ */
+public class ConfigurationWrongPolymorphicTypeIdException extends RuntimeException {
+ /**
+ * Constructor.
+ *
+ * @param message Error message.
+ */
+ public ConfigurationWrongPolymorphicTypeIdException(String message) {
+ super(message);
+ }
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
index bded5d9..b4029a2 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListChange.java
@@ -24,10 +24,10 @@ import org.apache.ignite.configuration.notifications.ConfigurationNotificationEv
/**
* Closure parameter for {@link NamedConfigurationTree#change(Consumer)} method. Contains methods to modify named lists.
*
- * @param <View> Type for the reading named list elements of this particular list.
- * @param <Change> Type for changing named list elements of this particular list.
+ * @param <VIEW> Type for the reading named list elements of this particular list.
+ * @param <CHANGE> Type for changing named list elements of this particular list.
*/
-public interface NamedListChange<View, Change extends View> extends NamedListView<View> {
+public interface NamedListChange<VIEW, CHANGE extends VIEW> extends NamedListView<VIEW> {
/**
* Creates a new value in the named list configuration.
*
@@ -39,7 +39,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* @throws NullPointerException If one of the parameters is null.
* @throws IllegalArgumentException If an element with the given name already exists.
*/
- NamedListChange<View, Change> create(String key, Consumer<Change> valConsumer);
+ NamedListChange<VIEW, CHANGE> create(String key, Consumer<CHANGE> valConsumer);
/**
* Creates a new value at the given position in the named list configuration.
@@ -54,7 +54,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* @throws IndexOutOfBoundsException If index is negative of exceeds the size of the list.
* @throws IllegalArgumentException If an element with the given name already exists.
*/
- NamedListChange<View, Change> create(int index, String key, Consumer<Change> valConsumer);
+ NamedListChange<VIEW, CHANGE> create(int index, String key, Consumer<CHANGE> valConsumer);
/**
* Create a new value after a given precedingKey key in the named list configuration.
@@ -69,7 +69,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* @throws IllegalArgumentException If element with given name already exists
* or if {@code precedingKey} element doesn't exist.
*/
- NamedListChange<View, Change> createAfter(String precedingKey, String key, Consumer<Change> valConsumer);
+ NamedListChange<VIEW, CHANGE> createAfter(String precedingKey, String key, Consumer<CHANGE> valConsumer);
/**
* Updates a value in the named list configuration. If the value cannot be found, creates a new one instead.
@@ -82,7 +82,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* @throws NullPointerException If one of parameters is null.
* @throws IllegalArgumentException If {@link #delete(String)} has been invoked with the same key previously.
*/
- NamedListChange<View, Change> createOrUpdate(String key, Consumer<Change> valConsumer);
+ NamedListChange<VIEW, CHANGE> createOrUpdate(String key, Consumer<CHANGE> valConsumer);
/**
* Renames the existing value in the named list configuration. Element with key {@code oldKey} must exist and key
@@ -100,7 +100,7 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* {@code oldKey} doesn't exist, or {@link #delete(String)} has previously been invoked with either the
* {@code newKey} or the {@code oldKey}.
*/
- NamedListChange<View, Change> rename(String oldKey, String newKey);
+ NamedListChange<VIEW, CHANGE> rename(String oldKey, String newKey);
/**
* Removes the value from the named list configuration.
@@ -112,5 +112,5 @@ public interface NamedListChange<View, Change extends View> extends NamedListVie
* @throws IllegalArgumentException If {@link #createOrUpdate(String, Consumer)} has been invoked with the same key
* previously.
*/
- NamedListChange<View, Change> delete(String key);
+ NamedListChange<VIEW, CHANGE> delete(String key);
}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
index c422cb3..ce49522 100644
--- a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/NamedListView.java
@@ -22,9 +22,9 @@ import java.util.List;
/**
* View type for a {@link NamedConfigurationTree}. Represents an immutable snapshot of a named list configuration.
*
- * @param <View> Type for immutable snapshots of named list elements.
+ * @param <VIEW> Type for immutable snapshots of named list elements.
*/
-public interface NamedListView<View> {
+public interface NamedListView<VIEW> {
/**
* Returns an immutable collection of keys contained within this list.
*
@@ -38,7 +38,7 @@ public interface NamedListView<View> {
* @param key Key string.
* @return Requested value or {@code null} if it's not found.
*/
- View get(String key);
+ VIEW get(String key);
/**
* Returns value located at the specified index.
@@ -47,7 +47,7 @@ public interface NamedListView<View> {
* @return Requested value.
* @throws IndexOutOfBoundsException If index is out of bounds.
*/
- View get(int index) throws IndexOutOfBoundsException;
+ VIEW get(int index) throws IndexOutOfBoundsException;
/**
* Returns the number of elements in this list.
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java
new file mode 100644
index 0000000..1a19392
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/PolymorphicChange.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration;
+
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+
+/**
+ * Marker interface for {@code *Node} classes whose schemas are marked with annotation {@link PolymorphicConfigInstance},
+ * this will allow at the compilation stage to distinguish an instance of a polymorphic configuration from
+ * the polymorphic configuration itself.
+ */
+public interface PolymorphicChange {
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java
new file mode 100644
index 0000000..2066436
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfig.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation marks the class as a polymorphic configuration schema.
+ * Has basically the same properties as a {@link Config}, but it should be treated like an abstract class in java.
+ *
+ * <p>To change the type of polymorphic configuration, you must use the {@code PolymorphicConfigChange#convert}.
+ *
+ * <p>NOTE: {@link PolymorphicId} field must go first, you should explicitly declare that, also at least one
+ * {@link PolymorphicConfigInstance} is required.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicConfig {
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java
new file mode 100644
index 0000000..cfc2574
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicConfigInstance.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation marks the class as an instance of polymorphic configuration schema.
+ * Has basically the same properties as {@link Config}, but must inherit from the {@link PolymorphicConfig}.
+ *
+ * <p>NOTE: Field name conflicts with the parent ({@link PolymorphicConfig}) are not allowed.
+ */
+@Target(TYPE)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicConfigInstance {
+ /**
+ * Unique identifier for an extension within a single {@link PolymorphicConfig polymorphic configuration}.
+ *
+ * @return Unique identifier for the extension of the polymorphic configuration instance.
+ */
+ String value();
+}
diff --git a/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java
new file mode 100644
index 0000000..0af7aa4
--- /dev/null
+++ b/modules/configuration-api/src/main/java/org/apache/ignite/configuration/annotation/PolymorphicId.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.configuration.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.RetentionPolicy.RUNTIME;
+
+/**
+ * This annotation marks the {@link PolymorphicConfig polymorphic configuration schema} field as a special
+ * (read only) leaf that will store the current {@link PolymorphicConfigInstance#value polymorphic configuration type}.
+ *
+ * <p>NOTE: Field must be the first in the schema, and the type must be {@link String}.
+ */
+@Target(FIELD)
+@Retention(RUNTIME)
+@Documented
+public @interface PolymorphicId {
+ /**
+ * Indicates that the field contains a default value that should be equal to one of the {@link PolymorphicConfigInstance#value}.
+ *
+ * @return {@code hasDefault} flag value.
+ */
+ boolean hasDefault() default false;
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
index 382a8d5..dc600c1 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationChanger.java
@@ -59,6 +59,7 @@ import static java.util.stream.Collectors.toMap;
import static org.apache.ignite.internal.configuration.util.ConfigurationFlattener.createFlattenedUpdatesMap;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.compressDeletedEntries;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.dropNulls;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.fillFromPrefixMap;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.toPrefixMap;
@@ -451,24 +452,4 @@ public abstract class ConfigurationChanger implements DynamicConfigurationChange
oldStorageRoots.changeFuture.completeExceptionally(t);
});
}
-
- /**
- * "Compress" prefix map - this means that deleted named list elements will be represented as a single {@code null}
- * objects instead of a number of nullified configuration leaves.
- *
- * @param prefixMap Prefix map, constructed from the storage notification data or its subtree.
- */
- private void compressDeletedEntries(Map<String, ?> prefixMap) {
- // Here we basically assume that if prefix subtree contains single null child then all its childrens are nulls.
- // Replace all such elements will nulls, signifying that these are deleted named list elements.
- prefixMap.replaceAll((key, value) ->
- value instanceof Map && ((Map<?, ?>)value).containsValue(null) ? null : value
- );
-
- // Continue recursively.
- for (Object value : prefixMap.values()) {
- if (value instanceof Map)
- compressDeletedEntries((Map<String, ?>)value);
- }
- }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
index 0a54d78..8785405 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationManager.java
@@ -28,6 +28,7 @@ import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.validation.Validator;
import org.apache.ignite.internal.configuration.hocon.HoconConverter;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -51,6 +52,8 @@ public class ConfigurationManager implements IgniteComponent {
* @param storage Configuration storage.
* @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
* of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+ * of configuration schemas.
* @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type,
* or if the schema or its extensions are not valid.
*/
@@ -58,11 +61,18 @@ public class ConfigurationManager implements IgniteComponent {
Collection<RootKey<?, ?>> rootKeys,
Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
ConfigurationStorage storage,
- Collection<Class<?>> internalSchemaExtensions
+ Collection<Class<?>> internalSchemaExtensions,
+ Collection<Class<?>> polymorphicSchemaExtensions
) {
checkConfigurationType(rootKeys, storage);
- registry = new ConfigurationRegistry(rootKeys, validators, storage, internalSchemaExtensions);
+ registry = new ConfigurationRegistry(
+ rootKeys,
+ validators,
+ storage,
+ internalSchemaExtensions,
+ polymorphicSchemaExtensions
+ );
}
/** {@inheritDoc} */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
index 28e0544..5e6cd4a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationNode.java
@@ -29,6 +29,7 @@ import org.apache.ignite.configuration.notifications.ConfigurationListener;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
import org.apache.ignite.internal.configuration.util.KeyNotFoundException;
+import org.jetbrains.annotations.Nullable;
import static java.util.Collections.unmodifiableCollection;
@@ -134,7 +135,7 @@ public abstract class ConfigurationNode<VIEW> implements ConfigurationProperty<V
synchronized (this) {
if (cachedRootNode == oldRootNode) {
- beforeRefreshValue(newVal);
+ beforeRefreshValue(newVal, val);
val = newVal;
@@ -172,8 +173,9 @@ public abstract class ConfigurationNode<VIEW> implements ConfigurationProperty<V
* Callback from {@link #refreshValue()} that's called right before the update. Synchronized.
*
* @param newValue New configuration value.
+ * @param oldValue Old configuration value.
*/
- protected void beforeRefreshValue(VIEW newValue) {
+ protected void beforeRefreshValue(VIEW newValue, @Nullable VIEW oldValue) {
// No-op.
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
index 5de9ade..08c5b23 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationRegistry.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.configuration;
import java.io.Serializable;
import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -33,6 +34,8 @@ import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.validation.ExceptKeys;
import org.apache.ignite.configuration.validation.Immutable;
import org.apache.ignite.configuration.validation.Max;
@@ -57,12 +60,18 @@ import org.apache.ignite.internal.manager.IgniteComponent;
import org.apache.ignite.lang.IgniteLogger;
import static java.util.function.Predicate.not;
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.configuration.util.ConfigurationNotificationsUtil.notifyListeners;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.innerNodeVisitor;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
+import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
/** */
public class ConfigurationRegistry implements IgniteComponent {
@@ -89,6 +98,8 @@ public class ConfigurationRegistry implements IgniteComponent {
* @param storage Configuration storage.
* @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
* of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+ * of configuration schemas.
* @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type,
* or if the schema or its extensions are not valid.
*/
@@ -96,23 +107,16 @@ public class ConfigurationRegistry implements IgniteComponent {
Collection<RootKey<?, ?>> rootKeys,
Map<Class<? extends Annotation>, Set<Validator<? extends Annotation, ?>>> validators,
ConfigurationStorage storage,
- Collection<Class<?>> internalSchemaExtensions
+ Collection<Class<?>> internalSchemaExtensions,
+ Collection<Class<?>> polymorphicSchemaExtensions
) {
checkConfigurationType(rootKeys, storage);
- Set<Class<?>> allSchemas = collectSchemas(rootKeys.stream().map(RootKey::schemaClass).collect(toSet()));
+ Set<Class<?>> allSchemas = collectSchemas(viewReadOnly(rootKeys, RootKey::schemaClass));
- Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+ Map<Class<?>, Set<Class<?>>> internalExtensions = internalExtensionsWithCheck(allSchemas, internalSchemaExtensions);
- if (!allSchemas.containsAll(extensions.keySet())) {
- Set<Class<?>> notInAllSchemas = extensions.keySet().stream()
- .filter(not(allSchemas::contains))
- .collect(toSet());
-
- throw new IllegalArgumentException(
- "Internal extensions for which no parent configuration schemes were found: " + notInAllSchemas
- );
- }
+ Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicExtensionsWithCheck(allSchemas, polymorphicSchemaExtensions);
this.rootKeys = rootKeys;
@@ -132,7 +136,7 @@ public class ConfigurationRegistry implements IgniteComponent {
};
rootKeys.forEach(rootKey -> {
- cgen.compileRootSchema(rootKey.schemaClass(), extensions);
+ cgen.compileRootSchema(rootKey.schemaClass(), internalExtensions, polymorphicExtensions);
DynamicConfiguration<?, ?> cfg = cgen.instantiateCfg(rootKey, changer);
@@ -267,4 +271,115 @@ public class ConfigurationRegistry implements IgniteComponent {
return CompletableFuture.allOf(resultFutures);
}
+
+ /**
+ * Get configuration schemas and their validated internal extensions with checks.
+ *
+ * @param allSchemas All configuration schemas.
+ * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+ * of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+ * @return Mapping: original of the schema -> internal schema extensions.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ */
+ private Map<Class<?>, Set<Class<?>>> internalExtensionsWithCheck(
+ Set<Class<?>> allSchemas,
+ Collection<Class<?>> internalSchemaExtensions
+ ) {
+ if (internalSchemaExtensions.isEmpty())
+ return Map.of();
+
+ Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(internalSchemaExtensions);
+
+ Set<Class<?>> notInAllSchemas = difference(internalExtensions.keySet(), allSchemas);
+
+ if (!notInAllSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Internal extensions for which no parent configuration schemas were found: " + notInAllSchemas
+ );
+ }
+
+ return internalExtensions;
+ }
+
+ /**
+ * Get polymorphic extensions of configuration schemas with checks.
+ *
+ * @param allSchemas All configuration schemas.
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+ * of configuration schemas.
+ * @return Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ */
+ private Map<Class<?>, Set<Class<?>>> polymorphicExtensionsWithCheck(
+ Set<Class<?>> allSchemas,
+ Collection<Class<?>> polymorphicSchemaExtensions
+ ) {
+ if (polymorphicSchemaExtensions.isEmpty())
+ return Map.of();
+
+ Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicSchemaExtensions(polymorphicSchemaExtensions);
+
+ Set<Class<?>> notInAllSchemas = difference(polymorphicExtensions.keySet(), allSchemas);
+
+ if (!notInAllSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Polymorphic extensions for which no polymorphic configuration schemas were found: " + notInAllSchemas
+ );
+ }
+
+ Collection<Class<?>> noPolymorphicExtensionsSchemas = allSchemas.stream()
+ .filter(ConfigurationUtil::isPolymorphicConfig)
+ .filter(not(polymorphicExtensions::containsKey))
+ .collect(toList());
+
+ if (!noPolymorphicExtensionsSchemas.isEmpty()) {
+ throw new IllegalArgumentException(
+ "Polymorphic configuration schemas for which no extensions were found: " + noPolymorphicExtensionsSchemas
+ );
+ }
+
+ checkPolymorphicConfigIds(polymorphicExtensions);
+
+ for (Map.Entry<Class<?>, Set<Class<?>>> e : polymorphicExtensions.entrySet()) {
+ Class<?> schemaClass = e.getKey();
+
+ Field typeIdField = schemaFields(schemaClass).get(0);
+
+ if (!isPolymorphicId(typeIdField)) {
+ throw new IllegalArgumentException(String.format(
+ "First field in a polymorphic configuration schema must contain @%s: %s",
+ PolymorphicId.class,
+ schemaClass.getName()
+ ));
+ }
+ }
+
+ return polymorphicExtensions;
+ }
+
+ /**
+ * Checks that there are no conflicts between ids of a polymorphic configuration and its extensions (instances).
+ *
+ * @param polymorphicExtensions Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+ * @throws IllegalArgumentException If a polymorphic configuration id conflict is found.
+ * @see PolymorphicConfigInstance#value
+ */
+ private void checkPolymorphicConfigIds(Map<Class<?>, Set<Class<?>>> polymorphicExtensions) {
+ // Mapping: id -> configuration schema.
+ Map<String, Class<?>> ids = new HashMap<>();
+
+ for (Map.Entry<Class<?>, Set<Class<?>>> e : polymorphicExtensions.entrySet()) {
+ for (Class<?> schemaClass : e.getValue()) {
+ String id = polymorphicInstanceId(schemaClass);
+ Class<?> prev = ids.put(id, schemaClass);
+
+ if (prev != null) {
+ throw new IllegalArgumentException("Found an id conflict for a polymorphic configuration [id=" +
+ id + ", schemas=" + List.of(prev, schemaClass));
+ }
+ }
+
+ ids.clear();
+ }
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java
new file mode 100644
index 0000000..16bd4c4
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/ConfigurationTreeWrapper.java
@@ -0,0 +1,68 @@
+/*
+ * 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 java.util.concurrent.CompletableFuture;
+import java.util.function.Consumer;
+import org.apache.ignite.configuration.ConfigurationTree;
+import org.apache.ignite.configuration.notifications.ConfigurationListener;
+
+/**
+ * {@link ConfigurationTree} wrapper.
+ *
+ * @param <VIEW> Value type of the node.
+ * @param <CHANGE> Type of the object that changes this node's value.
+ */
+public class ConfigurationTreeWrapper<VIEW, CHANGE> implements ConfigurationTree<VIEW, CHANGE> {
+ /** Configuration tree. */
+ protected final ConfigurationTree<VIEW, CHANGE> configTree;
+
+ /**
+ * Constructor.
+ *
+ * @param configTree Configuration tree.
+ */
+ public ConfigurationTreeWrapper(ConfigurationTree<VIEW, CHANGE> configTree) {
+ this.configTree = configTree;
+ }
+
+ /** {@inheritDoc} */
+ @Override public String key() {
+ return configTree.key();
+ }
+
+ /** {@inheritDoc} */
+ @Override public VIEW value() {
+ return configTree.value();
+ }
+
+ /** {@inheritDoc} */
+ @Override public void listen(ConfigurationListener<VIEW> listener) {
+ configTree.listen(listener);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void stopListen(ConfigurationListener<VIEW> listener) {
+ configTree.stopListen(listener);
+ }
+
+ /** {@inheritDoc} */
+ @Override public CompletableFuture<Void> change(Consumer<CHANGE> change) {
+ return configTree.change(change);
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectConfigurationTreeWrapper.java
new file mode 100644
index 0000000..eea1e8f
--- /dev/null
+++ b/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 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;
+ }
+
+ /** {@inheritDoc} */
+ @Override public VIEW directValue() {
+ return ((DirectConfigurationProperty<VIEW>)configTree).directValue();
+ }
+}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
index 9393444..bb60780 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DirectDynamicProperty.java
@@ -39,15 +39,17 @@ public class DirectDynamicProperty<T extends Serializable>
* @param rootKey Root key.
* @param changer Configuration changer.
* @param listenOnly Only adding listeners mode, without the ability to get or update the property value.
+ * @param readOnly Value cannot be changed.
*/
public DirectDynamicProperty(
List<String> prefix,
String key,
RootKey<?, ?> rootKey,
DynamicConfigurationChanger changer,
- boolean listenOnly
+ boolean listenOnly,
+ boolean readOnly
) {
- super(prefix, key, rootKey, changer, listenOnly);
+ super(prefix, key, rootKey, changer, listenOnly, readOnly);
}
/** {@inheritDoc} */
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
index 59d5097..ba883a8 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicConfiguration.java
@@ -30,7 +30,9 @@ import org.apache.ignite.configuration.ConfigurationTree;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
+import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.util.ConfigurationNotificationsUtil;
+import org.jetbrains.annotations.Nullable;
/**
* This class represents configuration root or node.
@@ -62,11 +64,12 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
/**
* Add new configuration member.
+ *
* @param member Configuration member (leaf or node).
* @param <P> Type of member.
*/
protected final <P extends ConfigurationProperty<?>> void add(P member) {
- members.put(member.key(), member);
+ addMember(members, member);
}
/** {@inheritDoc} */
@@ -84,8 +87,16 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
/** {@inheritDoc} */
@Override public void descend(ConstructableTreeNode node) {
- if (level == keys.size())
- change.accept((CHANGE)node);
+ if (level == keys.size()) {
+ if (node instanceof InnerNode) {
+ // To support polymorphic configuration.
+ change.accept(((InnerNode)node).specificNode());
+ }
+ else {
+ // To support namedList configuration.
+ change.accept((CHANGE)node);
+ }
+ }
else
node.construct(keys.get(level++), this, true);
}
@@ -106,8 +117,23 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
}
/** {@inheritDoc} */
- @Override public final VIEW value() {
- return refreshValue();
+ @Override public VIEW value() {
+ // To support polymorphic configuration.
+ return ((InnerNode)refreshValue()).specificNode();
+ }
+
+ /** {@inheritDoc} */
+ @Override protected void beforeRefreshValue(VIEW newValue, @Nullable VIEW oldValue) {
+ if (oldValue == null || ((InnerNode)oldValue).schemaType() != ((InnerNode)newValue).schemaType()) {
+ Map<String, ConfigurationProperty<?>> newMembers = new LinkedHashMap<>(members);
+
+ if (oldValue != null)
+ removeMembers(oldValue, newMembers);
+
+ addMembers(newValue, newMembers);
+
+ members = newMembers;
+ }
}
/**
@@ -142,8 +168,68 @@ public abstract class DynamicConfiguration<VIEW, CHANGE> extends ConfigurationNo
}
/**
+ * 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(
+ Map<String, ConfigurationProperty<?>> members,
+ P member
+ ) {
+ members.remove(member.key());
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
index 925cd09..4b31db4 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/DynamicProperty.java
@@ -22,6 +22,7 @@ import java.util.List;
import java.util.Objects;
import java.util.RandomAccess;
import java.util.concurrent.CompletableFuture;
+import org.apache.ignite.configuration.ConfigurationReadOnlyException;
import org.apache.ignite.configuration.ConfigurationValue;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -33,6 +34,9 @@ import org.apache.ignite.internal.tostring.S;
* Expected to be used with numbers, strings and other immutable objects, e.g. IP addresses.
*/
public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T> implements ConfigurationValue<T> {
+ /** Value cannot be changed. */
+ private final boolean readOnly;
+
/**
* Constructor.
*
@@ -41,15 +45,19 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
* @param rootKey Root key.
* @param changer Configuration changer.
* @param listenOnly Only adding listeners mode, without the ability to get or update the property value.
+ * @param readOnly Value cannot be changed.
*/
public DynamicProperty(
List<String> prefix,
String key,
RootKey<?, ?> rootKey,
DynamicConfigurationChanger changer,
- boolean listenOnly
+ boolean listenOnly,
+ boolean readOnly
) {
super(prefix, key, rootKey, changer, listenOnly);
+
+ this.readOnly = readOnly;
}
/** {@inheritDoc} */
@@ -64,6 +72,9 @@ public class DynamicProperty<T extends Serializable> extends ConfigurationNode<T
if (listenOnly)
throw listenOnlyException();
+ if (readOnly)
+ throw new ConfigurationReadOnlyException("Read only mode: " + keys);
+
assert keys instanceof RandomAccess;
assert !keys.isEmpty();
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
index cfac2b1..cf91ac6 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/NamedListConfiguration.java
@@ -29,6 +29,7 @@ import org.apache.ignite.configuration.NamedListChange;
import org.apache.ignite.configuration.NamedListView;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.notifications.ConfigurationNamedListListener;
+import org.jetbrains.annotations.Nullable;
/**
* Named configuration wrapper.
@@ -79,11 +80,16 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW>, VIEW,
@Override public T get(String name) {
refreshValue();
- return (T)members.get(name);
+ ConfigurationProperty<?> configProperty = members.get(name);
+
+ return configProperty == null ? null : (T)((DynamicConfiguration<?, ?>)configProperty).specificConfigTree();
}
/** {@inheritDoc} */
- @Override protected synchronized void beforeRefreshValue(NamedListView<VIEW> newValue) {
+ @Override protected synchronized void beforeRefreshValue(
+ NamedListView<VIEW> newValue,
+ @Nullable NamedListView<VIEW> oldValue
+ ) {
Map<String, ConfigurationProperty<?>> oldValues = this.members;
Map<String, ConfigurationProperty<?>> newValues = new LinkedHashMap<>();
@@ -134,4 +140,9 @@ public class NamedListConfiguration<T extends ConfigurationProperty<VIEW>, VIEW,
@Override public Class<? extends ConfigurationProperty<NamedListView<VIEW>>> configType() {
throw new UnsupportedOperationException("Not supported.");
}
+
+ /** {@inheritDoc} */
+ @Override public NamedListView<VIEW> value() {
+ return refreshValue();
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
index 03edebe..f318482 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGenerator.java
@@ -27,11 +27,11 @@ import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayDeque;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -40,6 +40,7 @@ import java.util.Queue;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.function.Consumer;
+import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Stream;
import com.facebook.presto.bytecode.BytecodeBlock;
@@ -53,7 +54,9 @@ import com.facebook.presto.bytecode.Variable;
import com.facebook.presto.bytecode.control.IfStatement;
import com.facebook.presto.bytecode.expression.BytecodeExpression;
import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationTree;
import org.apache.ignite.configuration.ConfigurationValue;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
import org.apache.ignite.configuration.DirectConfigurationProperty;
import org.apache.ignite.configuration.NamedConfigurationTree;
import org.apache.ignite.configuration.NamedListView;
@@ -63,7 +66,12 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.DirectAccess;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
-import org.apache.ignite.configuration.annotation.Value;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
+import org.apache.ignite.internal.configuration.ConfigurationNode;
+import org.apache.ignite.internal.configuration.ConfigurationTreeWrapper;
+import org.apache.ignite.internal.configuration.DirectConfigurationTreeWrapper;
import org.apache.ignite.internal.configuration.DirectDynamicConfiguration;
import org.apache.ignite.internal.configuration.DirectDynamicProperty;
import org.apache.ignite.internal.configuration.DirectNamedListConfiguration;
@@ -77,7 +85,9 @@ import org.apache.ignite.internal.configuration.tree.ConfigurationVisitor;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
+import org.apache.ignite.internal.configuration.util.ConfigurationUtil;
import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@@ -98,20 +108,32 @@ import static com.facebook.presto.bytecode.expression.BytecodeExpressions.consta
import static com.facebook.presto.bytecode.expression.BytecodeExpressions.inlineIf;
import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeDynamic;
import static com.facebook.presto.bytecode.expression.BytecodeExpressions.invokeStatic;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNotNull;
import static com.facebook.presto.bytecode.expression.BytecodeExpressions.isNull;
import static com.facebook.presto.bytecode.expression.BytecodeExpressions.newInstance;
+import static com.facebook.presto.bytecode.expression.BytecodeExpressions.not;
import static java.lang.invoke.MethodType.methodType;
import static java.util.Arrays.asList;
import static java.util.Collections.singleton;
import static java.util.EnumSet.of;
+import static java.util.function.Function.identity;
+import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.changeClassName;
import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.configurationClassName;
import static org.apache.ignite.internal.configuration.asm.SchemaClassesInfo.viewClassName;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.extensionsFields;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.hasDefault;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isConfigValue;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isNamedConfigValue;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfig;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicConfigInstance;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isPolymorphicId;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.isValue;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicInstanceId;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.schemaFields;
+import static org.apache.ignite.internal.util.ArrayUtils.nullOrEmpty;
import static org.apache.ignite.internal.util.CollectionUtils.concat;
import static org.apache.ignite.internal.util.CollectionUtils.union;
import static org.objectweb.asm.Opcodes.H_NEWINVOKESPECIAL;
@@ -124,10 +146,16 @@ import static org.objectweb.asm.Type.getType;
* from {@code bytecode} module to achieve this goal, like {@link ClassGenerator}, for examples.
*/
public class ConfigurationAsmGenerator {
+ /** {@link DynamicConfiguration#DynamicConfiguration} constructor. */
+ private static final Constructor<?> DYNAMIC_CONFIGURATION_CTOR;
+
+ /** {@link DirectDynamicConfiguration#DirectDynamicConfiguration} constructor. */
+ private static final Constructor<?> DIRECT_DYNAMIC_CONFIGURATION_CTOR;
+
/** {@link LambdaMetafactory#metafactory(Lookup, String, MethodType, MethodType, MethodHandle, MethodType)} */
private static final Method LAMBDA_METAFACTORY;
- /** {@link Consumer#accept(Object)}*/
+ /** {@link Consumer#accept(Object)} */
private static final Method ACCEPT;
/** {@link ConfigurationVisitor#visitLeafNode(String, Serializable)} */
@@ -148,18 +176,45 @@ public class ConfigurationAsmGenerator {
/** {@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 DynamicConfiguration#addMember} method. */
+ private static final Method ADD_MEMBER_MTD;
+
+ /** {@code DynamicConfiguration#removeMember} method. */
+ private static final Method REMOVE_MEMBER_MTD;
+
+ /** {@link InnerNode#specificNode} method. */
+ private static final Method SPECIFIC_NODE_MTD;
+
+ /** {@link DynamicConfiguration#specificConfigTree} method. */
+ private static final Method SPECIFIC_CONFIG_TREE_MTD;
+
+ /** {@link ConfigurationUtil#addDefaults}. */
+ private static final Method ADD_DEFAULTS_MTD;
+
+ /** {@code Node#convert} method name. */
+ private static final String CONVERT_MTD_NAME = "convert";
+
static {
try {
LAMBDA_METAFACTORY = LambdaMetafactory.class.getDeclaredMethod(
@@ -205,12 +260,32 @@ public class ConfigurationAsmGenerator {
boolean.class
);
- DYNAMIC_CONFIGURATION_ADD = DynamicConfiguration.class.getDeclaredMethod(
+ DYNAMIC_CONFIGURATION_ADD_MTD = DynamicConfiguration.class.getDeclaredMethod(
"add",
ConfigurationProperty.class
);
REQUIRE_NON_NULL = Objects.class.getDeclaredMethod("requireNonNull", Object.class, String.class);
+
+ CLASS_GET_NAME_MTD = Class.class.getDeclaredMethod("getName");
+
+ STRING_EQUALS_MTD = String.class.getDeclaredMethod("equals", Object.class);
+
+ POLYMORPHIC_TYPE_ID_MTD = ConfigurationSource.class.getDeclaredMethod("polymorphicTypeId", String.class);
+
+ CONSTRUCT_DEFAULT_MTD = InnerNode.class.getDeclaredMethod("constructDefault", String.class);
+
+ REFRESH_VALUE_MTD = ConfigurationNode.class.getDeclaredMethod("refreshValue");
+
+ ADD_MEMBER_MTD = DynamicConfiguration.class.getDeclaredMethod("addMember", Map.class, ConfigurationProperty.class);
+
+ REMOVE_MEMBER_MTD = DynamicConfiguration.class.getDeclaredMethod("removeMember", Map.class, ConfigurationProperty.class);
+
+ SPECIFIC_NODE_MTD = InnerNode.class.getDeclaredMethod("specificNode");
+
+ SPECIFIC_CONFIG_TREE_MTD = DynamicConfiguration.class.getDeclaredMethod("specificConfigTree");
+
+ ADD_DEFAULTS_MTD = ConfigurationUtil.class.getDeclaredMethod("addDefaults", InnerNode.class);
}
catch (NoSuchMethodException nsme) {
throw new ExceptionInInitializerError(nsme);
@@ -225,6 +300,7 @@ public class ConfigurationAsmGenerator {
/**
* Creates new instance of {@code *Node} class corresponding to the given Configuration Schema.
+ *
* @param schemaClass Configuration Schema class.
* @return Node instance.
*/
@@ -247,6 +323,7 @@ public class ConfigurationAsmGenerator {
/**
* Creates new instance of {@code *Configuration} class corresponding to the given Configuration Schema.
+ *
* @param rootKey Root key of the configuration root.
* @param changer Configuration changer instance to pass into constructor.
* @return Configuration instance.
@@ -280,14 +357,18 @@ public class ConfigurationAsmGenerator {
/**
* Generates, defines, loads and initializes all dynamic classes required for the given configuration schema.
*
- * @param rootSchemaClass Class of the root configuration schema.
- * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
- * of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
- * Mapping: original schema -> extensions.
+ * @param rootSchemaClass Class of the root configuration schema.
+ * @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
+ * of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+ * Mapping: original schema -> extensions.
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+ * of configuration schemas ({@link PolymorphicConfig}).
+ * Mapping: original schema -> extensions.
*/
public synchronized void compileRootSchema(
Class<?> rootSchemaClass,
- Map<Class<?>, Set<Class<?>>> internalSchemaExtensions
+ Map<Class<?>, Set<Class<?>>> internalSchemaExtensions,
+ Map<Class<?>, Set<Class<?>>> polymorphicSchemaExtensions
) {
if (schemasInfo.containsKey(rootSchemaClass))
return; // Already compiled.
@@ -298,25 +379,29 @@ public class ConfigurationAsmGenerator {
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() :
+ "Internal and polymorphic extensions are not allowed at the same time: " + schemaClass;
- Set<Class<?>> schemaExtensions = internalSchemaExtensions.getOrDefault(schemaClass, Set.of());
- Set<Field> extensionsFields = extensionsFields(schemaExtensions);
+ List<Field> schemaFields = schemaFields(schemaClass);
+ Collection<Field> internalExtensionsFields = extensionsFields(internalExtensions, true);
+ Collection<Field> polymorphicExtensionsFields = extensionsFields(polymorphicExtensions, false);
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+ for (Field schemaField : concat(schemaFields, internalExtensionsFields, polymorphicExtensionsFields)) {
if (isConfigValue(schemaField) || isNamedConfigValue(schemaField)) {
Class<?> subSchemaClass = schemaField.getType();
@@ -328,12 +413,58 @@ public class ConfigurationAsmGenerator {
}
}
+ for (Class<?> polymorphicExtension : polymorphicExtensions)
+ schemasInfo.put(polymorphicExtension, new SchemaClassesInfo(polymorphicExtension));
+
schemas.add(schemaClass);
- definitions.add(createNodeClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
- definitions.add(createCfgImplClass(schemaClass, schemaExtensions, schemaFields, extensionsFields));
+
+ ClassDefinition innerNodeClassDef = createNodeClass(
+ schemaClass,
+ internalExtensions,
+ polymorphicExtensions,
+ schemaFields,
+ internalExtensionsFields,
+ polymorphicExtensionsFields
+ );
+
+ classDefs.add(innerNodeClassDef);
+
+ ClassDefinition cfgImplClassDef = createCfgImplClass(
+ schemaClass,
+ internalExtensions,
+ polymorphicExtensions,
+ schemaFields,
+ internalExtensionsFields,
+ polymorphicExtensionsFields
+ );
+
+ classDefs.add(cfgImplClassDef);
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ // Only the fields of a specific instance of a polymorphic configuration.
+ Collection<Field> polymorphicFields = polymorphicExtensionsFields.stream()
+ .filter(f -> f.getDeclaringClass() == polymorphicExtension)
+ .collect(toList());
+
+ classDefs.add(createPolymorphicExtensionNodeClass(
+ schemaClass,
+ polymorphicExtension,
+ innerNodeClassDef,
+ schemaFields,
+ polymorphicFields
+ ));
+
+ classDefs.add(createPolymorphicExtensionCfgImplClass(
+ schemaClass,
+ polymorphicExtension,
+ cfgImplClassDef,
+ schemaFields,
+ polymorphicFields
+ ));
+ }
}
- Map<String, Class<?>> definedClasses = generator.defineClasses(definitions);
+ Map<String, Class<?>> definedClasses = generator.defineClasses(classDefs);
for (Class<?> schemaClass : schemas) {
SchemaClassesInfo info = schemasInfo.get(schemaClass);
@@ -346,17 +477,21 @@ public class ConfigurationAsmGenerator {
/**
* Construct a {@link InnerNode} definition for a configuration schema.
*
- * @param schemaClass Configuration schema class.
- * @param schemaExtensions Internal extensions of the configuration schema.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param schemaClass Configuration schema class.
+ * @param internalExtensions Internal extensions of the configuration schema.
+ * @param polymorphicExtensions Polymorphic extensions of the configuration schema.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFields Fields of polymorphic extensions of the configuration schema.
* @return Constructed {@link InnerNode} definition for the configuration schema.
*/
private ClassDefinition createNodeClass(
Class<?> schemaClass,
- Set<Class<?>> schemaExtensions,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Set<Class<?>> internalExtensions,
+ Set<Class<?>> polymorphicExtensions,
+ List<Field> schemaFields,
+ Collection<Field> internalFields,
+ Collection<Field> polymorphicFields
) {
SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
@@ -365,7 +500,7 @@ public class ConfigurationAsmGenerator {
of(PUBLIC, FINAL),
internalName(schemaClassInfo.nodeClassName),
type(InnerNode.class),
- nodeClassInterfaces(schemaClass, schemaExtensions)
+ nodeClassInterfaces(schemaClass, internalExtensions)
);
// Spec fields.
@@ -373,69 +508,176 @@ public class ConfigurationAsmGenerator {
int i = 0;
- for (Class<?> clazz : concat(List.of(schemaClass), schemaExtensions))
+ for (Class<?> clazz : concat(List.of(schemaClass), internalExtensions, polymorphicExtensions))
specFields.put(clazz, classDef.declareField(of(PRIVATE, FINAL), "_spec" + i++, clazz));
- // org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
- addNodeSchemaTypeMethod(classDef, specFields.get(schemaClass));
-
// Define the rest of the fields.
Map<String, FieldDefinition> fieldDefs = new HashMap<>();
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
- assert isValue(schemaField) || isConfigValue(schemaField) || isNamedConfigValue(schemaField) : schemaField;
+ // To store the id of the polymorphic configuration instance.
+ FieldDefinition polymorphicTypeIdFieldDef = null;
- fieldDefs.put(schemaField.getName(), addNodeField(classDef, schemaField));
+ for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+ String fieldName = fieldName(schemaField);
+
+ FieldDefinition fieldDef = addNodeField(classDef, schemaField, fieldName);
+
+ fieldDefs.put(fieldName, fieldDef);
+
+ if (isPolymorphicId(schemaField))
+ polymorphicTypeIdFieldDef = fieldDef;
}
+ // org.apache.ignite.internal.configuration.tree.InnerNode#schemaType
+ addNodeSchemaTypeMethod(classDef, schemaClass, polymorphicExtensions, polymorphicTypeIdFieldDef);
+
// Constructor.
- addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, extensionsFields);
+ addNodeConstructor(classDef, specFields, fieldDefs, schemaFields, internalFields, polymorphicFields);
// VIEW and CHANGE methods.
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+ for (Field schemaField : concat(schemaFields, internalFields)) {
String fieldName = schemaField.getName();
FieldDefinition fieldDef = fieldDefs.get(fieldName);
- addNodeViewMethod(classDef, schemaField, fieldDef);
+ addNodeViewMethod(
+ classDef,
+ schemaField,
+ viewMtd -> getThisFieldCode(viewMtd, fieldDef),
+ null
+ );
+
+ // Read only.
+ if (isPolymorphicId(schemaField))
+ continue;
// Add change methods.
- MethodDefinition changeMtd =
- addNodeChangeMethod(classDef, schemaField, fieldDef, schemaClassInfo.nodeClassName);
- addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd);
+ MethodDefinition changeMtd0 = addNodeChangeMethod(
+ classDef,
+ schemaField,
+ changeMtd -> getThisFieldCode(changeMtd, fieldDef),
+ (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, fieldDef),
+ null
+ );
+
+ addNodeChangeBridgeMethod(classDef, changeClassName(schemaField.getDeclaringClass()), changeMtd0);
+ }
+
+ 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);
+
+ 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 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,
+ @Nullable FieldDefinition polymorphicTypeIdFieldDef
+ ) {
+ MethodDefinition schemaTypeMtd = classDef.declareMethod(
+ of(PUBLIC),
+ "schemaType",
+ type(Class.class)
+ );
+
+ BytecodeBlock mtdBody = schemaTypeMtd.getBody();
+
+ if (polymorphicExtensions.isEmpty())
+ mtdBody.append(constantClass(schemaClass)).retObject();
+ else {
+ assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+ StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(schemaTypeMtd, polymorphicTypeIdFieldDef);
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ switchBuilderTypeId.addCase(
+ polymorphicInstanceId(polymorphicExtension),
+ constantClass(polymorphicExtension).ret()
+ );
+ }
- schemaTypeMtd.getBody().append(
- schemaTypeMtd.getThis().getField(specField).invoke("getClass", Class.class)
- ).retObject();
+ mtdBody.append(switchBuilderTypeId.build());
+ }
}
/**
@@ -443,80 +685,81 @@ public class ConfigurationAsmGenerator {
* <ul>
* <li>
* {@code @Value public type fieldName}<br/>becomes<br/>
- * {@code private BoxedType fieldName}
+ * {@code public BoxedType fieldName}
* </li>
* <li>
* {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
- * {@code private MyNode fieldName}
+ * {@code public MyNode fieldName}
* </li>
* <li>
* {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
- * {@code private NamedListNode fieldName}
+ * {@code public NamedListNode fieldName}
+ * </li>
+ * <li>
+ * {@code @PolymorphicId public String fieldName}<br/>becomes<br/>
+ * {@code public String fieldName}
* </li>
* </ul>
- * @param classDef Node class definition.
+ *
+ * @param classDef Node class definition.
* @param schemaField Configuration Schema class field.
+ * @param fieldName Field name.
* @return Declared field definition.
*/
- private FieldDefinition addNodeField(ClassDefinition classDef, Field schemaField) {
+ private FieldDefinition addNodeField(ClassDefinition classDef, Field schemaField, String fieldName) {
Class<?> schemaFieldClass = schemaField.getType();
ParameterizedType nodeFieldType;
- if (isValue(schemaField))
+ if (isValue(schemaField) || isPolymorphicId(schemaField))
nodeFieldType = type(box(schemaFieldClass));
else if (isConfigValue(schemaField))
nodeFieldType = typeFromJavaClassName(schemasInfo.get(schemaFieldClass).nodeClassName);
else
nodeFieldType = type(NamedListNode.class);
- return classDef.declareField(of(PRIVATE), schemaField.getName(), nodeFieldType);
+ return classDef.declareField(of(PUBLIC), fieldName, nodeFieldType);
}
/**
* Implements default constructor for the node class. It initializes {@code _spec} field and every other field
* that represents named list configuration.
*
- * @param classDef Node class definition.
- * @param specFields Definition of fields for the {@code _spec#} fields of the node class.
- * Mapping: configuration schema class -> {@code _spec#} field.
- * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param classDef Node class definition.
+ * @param specFields Definition of fields for the {@code _spec#} fields of the node class.
+ * Mapping: configuration schema class -> {@code _spec#} field.
+ * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFields Fields of polymorphic extensions of the configuration schema.
*/
private void addNodeConstructor(
ClassDefinition classDef,
Map<Class<?>, FieldDefinition> specFields,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Collection<Field> polymorphicFields
) {
MethodDefinition ctor = classDef.declareConstructor(of(PUBLIC));
// super();
- ctor.getBody().append(ctor.getThis()).invokeConstructor(InnerNode.class);
+ ctor.getBody()
+ .append(ctor.getThis())
+ .invokeConstructor(InnerNode.class);
// this._spec# = new MyConfigurationSchema();
for (Map.Entry<Class<?>, FieldDefinition> e : specFields.entrySet())
ctor.getBody().append(ctor.getThis().setField(e.getValue(), newInstance(e.getKey())));
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
+ for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
if (!isNamedConfigValue(schemaField))
continue;
- NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
-
- SchemaClassesInfo fieldClassNames = schemasInfo.get(schemaField.getType());
+ FieldDefinition fieldDef = fieldDefs.get(fieldName(schemaField));
- // this.values = new NamedListNode<>(key, ValueNode::new);
- ctor.getBody().append(ctor.getThis().setField(
- fieldDefs.get(schemaField.getName()),
- newInstance(
- NamedListNode.class,
- constantString(namedCfgAnnotation.syntheticKeyName()),
- newNamedListElementLambda(fieldClassNames.nodeClassName)
- )
- ));
+ // this.values = new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+ ctor.getBody().append(setThisFieldCode(ctor, newNamedListNode(schemaField), fieldDef));
}
// return;
@@ -524,24 +767,32 @@ public class ConfigurationAsmGenerator {
}
/**
- * Implements getter method from {@code VIEW} interface. It returns field value, possibly unboxed or cloned,
- * depending on type.
- * @param classDef Node class definition.
- * @param schemaField Configuration Schema class field.
- * @param fieldDef Field definition.
+ * Implements getter method from {@code VIEW} interface.
+ * It returns field value, possibly unboxed or cloned, depending on type.
+ *
+ * @param classDef Node class definition.
+ * @param schemaField Configuration Schema class field.
+ * @param getFieldCodeFun Function for creating bytecode to get a field,
+ * for example: {@code this.field} or {@code this.field.field}.
+ * @param getPolymorphicTypeIdFieldFun Function for creating bytecode to get the field that stores the identifier
+ * of the polymorphic configuration instance is needed to add a polymorphicTypeId check,
+ * for example: {@code this.typeId} or {@code this.field.typeId}.
*/
private void addNodeViewMethod(
ClassDefinition classDef,
Field schemaField,
- FieldDefinition fieldDef
+ Function<MethodDefinition, BytecodeExpression> getFieldCodeFun,
+ @Nullable Function<MethodDefinition, BytecodeExpression> getPolymorphicTypeIdFieldFun
) {
Class<?> schemaFieldType = schemaField.getType();
ParameterizedType returnType;
+ SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaFieldType);
+
// Return type is either corresponding VIEW type or the same type as declared in schema.
if (isConfigValue(schemaField))
- returnType = typeFromJavaClassName(schemasInfo.get(schemaFieldType).viewClassName);
+ returnType = typeFromJavaClassName(schemaClassInfo.viewClassName);
else if (isNamedConfigValue(schemaField))
returnType = type(NamedListView.class);
else
@@ -555,86 +806,152 @@ public class ConfigurationAsmGenerator {
returnType
);
- // result = this.field;
- viewMtd.getBody().append(viewMtd.getThis().getField(fieldDef));
+ BytecodeBlock bytecodeBlock = new BytecodeBlock();
+
+ // result = this.field; OR this.field.field.
+ bytecodeBlock.append(getFieldCodeFun.apply(viewMtd));
- // result = Box.boxValue(result); // Unboxing.
if (schemaFieldType.isPrimitive()) {
- viewMtd.getBody().invokeVirtual(
+ // result = Box.boxValue(result); // Unboxing.
+ bytecodeBlock.invokeVirtual(
box(schemaFieldType),
schemaFieldType.getSimpleName() + "Value",
schemaFieldType
);
}
-
- // retuls = result.clone();
- if (schemaFieldType.isArray())
- viewMtd.getBody().invokeVirtual(schemaFieldType, "clone", Object.class).checkCast(schemaFieldType);
+ else if (schemaFieldType.isArray()) {
+ // result = result.clone();
+ bytecodeBlock.invokeVirtual(schemaFieldType, "clone", Object.class).checkCast(schemaFieldType);
+ }
+ else if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+ // result = result.specificNode();
+ bytecodeBlock.invokeVirtual(SPECIFIC_NODE_MTD);
+ }
// return result;
- viewMtd.getBody().ret(schemaFieldType);
+ bytecodeBlock.ret(schemaFieldType);
+
+ if (getPolymorphicTypeIdFieldFun != null) {
+ assert isPolymorphicConfigInstance(schemaField.getDeclaringClass()) : schemaField;
+
+ // tmpVar = this.typeId; OR this.field.typeId.
+ BytecodeExpression getPolymorphicTypeIdFieldValue = getPolymorphicTypeIdFieldFun.apply(viewMtd);
+ String polymorphicInstanceId = polymorphicInstanceId(schemaField.getDeclaringClass());
+
+ // if (!"first".equals(tmpVar)) throw Ex;
+ // else return value;
+ viewMtd.getBody().append(
+ new IfStatement()
+ .condition(not(constantString(polymorphicInstanceId).invoke(STRING_EQUALS_MTD, getPolymorphicTypeIdFieldValue)))
+ .ifTrue(throwException(ConfigurationWrongPolymorphicTypeIdException.class, getPolymorphicTypeIdFieldValue))
+ .ifFalse(bytecodeBlock)
+ );
+ }
+ else
+ viewMtd.getBody().append(bytecodeBlock);
}
/**
* Implements changer method from {@code CHANGE} interface.
*
* @param classDef Node class definition.
- * @param schemaField Configuration Schema class field.
- * @param fieldDef Field definition.
- * @param nodeClassName Class name for the Node class.
+ * @param schemaField Configuration schema class field.
* @return Definition of change method.
*/
- private static MethodDefinition addNodeChangeMethod(
+ private MethodDefinition addNodeChangeMethod(
ClassDefinition classDef,
Field schemaField,
- FieldDefinition fieldDef,
- String nodeClassName
+ Function<MethodDefinition, BytecodeExpression> getFieldCodeFun,
+ BiFunction<MethodDefinition, BytecodeExpression, BytecodeExpression> setFieldCodeFun,
+ @Nullable Function<MethodDefinition, BytecodeExpression> getPolymorphicTypeIdFieldFun
) {
Class<?> schemaFieldType = schemaField.getType();
MethodDefinition changeMtd = classDef.declareMethod(
of(PUBLIC),
- "change" + capitalize(schemaField.getName()),
- typeFromJavaClassName(nodeClassName),
+ changeMethodName(schemaField.getName()),
+ classDef.getType(),
// Change argument type is a Consumer for all inner or named fields.
arg("change", isValue(schemaField) ? type(schemaFieldType) : type(Consumer.class))
);
- BytecodeBlock changeBody = changeMtd.getBody();
+ // var change;
+ BytecodeExpression changeVar = changeMtd.getScope().getVariable("change");
- // newValue = change;
- BytecodeExpression newValue = changeMtd.getScope().getVariable("change");
+ BytecodeBlock bytecodeBlock = new BytecodeBlock();
if (!schemaFieldType.isPrimitive()) {
// Objects.requireNonNull(newValue, "change");
- changeBody.append(invokeStatic(REQUIRE_NON_NULL, newValue, constantString("change")));
+ bytecodeBlock.append(invokeStatic(REQUIRE_NON_NULL, changeVar, constantString("change")));
}
if (isValue(schemaField)) {
- // newValue = Box.valueOf(newValue); // Boxing.
- if (schemaFieldType.isPrimitive())
- newValue = invokeStatic(fieldDef.getType(), "valueOf", fieldDef.getType(), singleton(newValue));
+ BytecodeExpression newValue;
+
+ if (schemaFieldType.isPrimitive()) {
+ ParameterizedType type = type(box(schemaFieldType));
- // newValue = newValue.clone();
- if (schemaFieldType.isArray())
- newValue = newValue.invoke("clone", Object.class).cast(schemaFieldType);
+ // newValue = Box.valueOf(newValue); // Boxing.
+ newValue = invokeStatic(type, "valueOf", type, singleton(changeVar));
+ } else if (schemaFieldType.isArray()) {
+ // newValue = newValue.clone();
+ newValue = changeVar.invoke("clone", Object.class).cast(schemaFieldType);
+ } else
+ newValue = changeVar;
// this.field = newValue;
- changeBody.append(changeMtd.getThis().setField(fieldDef, newValue));
+ bytecodeBlock.append(setFieldCodeFun.apply(changeMtd, newValue));
}
else {
- // this.field = (this.field == null) ? new ValueNode() : (ValueNode)this.field.copy();
- changeBody.append(copyNodeField(changeMtd, fieldDef));
-
- // change.accept(this.field);
- changeBody.append(changeMtd.getScope().getVariable("change").invoke(
- ACCEPT,
- changeMtd.getThis().getField(fieldDef)
- ));
+ BytecodeExpression newValue;
+
+ if (isConfigValue(schemaField)) {
+ // newValue = (this.field == null) ? new ValueNode() : (ValueNode)this.field.copy();
+ newValue = newOrCopyNodeField(schemaField, getFieldCodeFun.apply(changeMtd));
+ }
+ else {
+ assert isNamedConfigValue(schemaField) : schemaField;
+
+ // newValue = (ValueNode)this.field.copy();
+ newValue = copyNodeField(schemaField, getFieldCodeFun.apply(changeMtd));
+ }
+
+ // this.field = newValue;
+ bytecodeBlock.append(setFieldCodeFun.apply(changeMtd, newValue));
+
+ // this.field;
+ BytecodeExpression getFieldCode = getFieldCodeFun.apply(changeMtd);
+
+ if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+ // this.field.specificNode();
+ getFieldCode = getFieldCode.invoke(SPECIFIC_NODE_MTD);
+ }
+
+ // change.accept(this.field); OR change.accept(this.field.specificNode());
+ bytecodeBlock.append(changeVar.invoke(ACCEPT, getFieldCode));
}
// return this;
- changeBody.append(changeMtd.getThis()).retObject();
+ bytecodeBlock.append(changeMtd.getThis()).retObject();
+
+ if (getPolymorphicTypeIdFieldFun != null) {
+ assert isPolymorphicConfigInstance(schemaField.getDeclaringClass()) : schemaField;
+
+ // tmpVar = this.typeId; OR this.field.typeId.
+ BytecodeExpression getPolymorphicTypeIdFieldValue = getPolymorphicTypeIdFieldFun.apply(changeMtd);
+ String polymorphicInstanceId = polymorphicInstanceId(schemaField.getDeclaringClass());
+
+ // if (!"first".equals(tmpVar)) throw Ex;
+ // else change_value;
+ changeMtd.getBody().append(
+ new IfStatement()
+ .condition(not(constantString(polymorphicInstanceId).invoke(STRING_EQUALS_MTD, getPolymorphicTypeIdFieldValue)))
+ .ifTrue(throwException(ConfigurationWrongPolymorphicTypeIdException.class, getPolymorphicTypeIdFieldValue))
+ .ifFalse(bytecodeBlock)
+ );
+ }
+ else
+ changeMtd.getBody().append(bytecodeBlock);
return changeMtd;
}
@@ -642,9 +959,9 @@ public class ConfigurationAsmGenerator {
/**
* Implements changer bridge method from {@code CHANGE} interface.
*
- * @param classDef Node class definition.
+ * @param classDef Node class definition.
* @param changeClassName Class name for the CHANGE class.
- * @param changeMtd Definition of change method.
+ * @param changeMtd Definition of change method.
*/
private static void addNodeChangeBridgeMethod(
ClassDefinition classDef,
@@ -670,16 +987,22 @@ public class ConfigurationAsmGenerator {
/**
* Implements {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} method.
*
- * @param classDef Class definition.
- * @param fieldDefs Definitions for all fields in {@code schemaFields}.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param classDef Class definition.
+ * @param schemaClass Configuration schema class.
+ * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
*/
private static void addNodeTraverseChildrenMethod(
ClassDefinition classDef,
+ Class<?> schemaClass,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ List<Field> schemaFields,
+ Collection<Field> internalFields,
+ Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+ @Nullable FieldDefinition polymorphicTypeIdFieldDef
) {
MethodDefinition traverseChildrenMtd = classDef.declareMethod(
of(PUBLIC),
@@ -691,18 +1014,57 @@ public class ConfigurationAsmGenerator {
BytecodeBlock mtdBody = traverseChildrenMtd.getBody();
- invokeVisitForTraverseChildren(asList(schemaFields), fieldDefs, traverseChildrenMtd)
- .forEach(mtdBody::append);
-
- if (!extensionsFields.isEmpty()) {
- Variable includeInternalVar = traverseChildrenMtd.getScope().getVariable("includeInternal");
+ // invokeVisit for public (common in case polymorphic config) fields.
+ for (Field schemaField : schemaFields) {
+ mtdBody.append(
+ invokeVisit(traverseChildrenMtd, schemaField, fieldDefs.get(schemaField.getName())).pop()
+ );
+ }
+ if (!internalFields.isEmpty()) {
BytecodeBlock includeInternalBlock = new BytecodeBlock();
- invokeVisitForTraverseChildren(extensionsFields, fieldDefs, traverseChildrenMtd)
- .forEach(includeInternalBlock::append);
+ for (Field internalField : internalFields) {
+ includeInternalBlock.append(
+ invokeVisit(traverseChildrenMtd, internalField, fieldDefs.get(internalField.getName())).pop()
+ );
+ }
+
+ // if (includeInternal) invokeVisit for internal fields.
+ mtdBody.append(
+ new IfStatement()
+ .condition(traverseChildrenMtd.getScope().getVariable("includeInternal"))
+ .ifTrue(includeInternalBlock)
+ );
+ }
+ else if (!polymorphicFieldsByExtension.isEmpty()) {
+ assert polymorphicTypeIdFieldDef != null : schemaClass.getName();
+ assert isPolymorphicId(schemaFields.get(0)) : schemaClass.getName();
+
+ // Create switch by polymorphicTypeIdField.
+ StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(traverseChildrenMtd, polymorphicTypeIdFieldDef);
- mtdBody.append(new IfStatement().condition(includeInternalVar).ifTrue(includeInternalBlock));
+ for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+ BytecodeBlock codeBlock = new BytecodeBlock();
+
+ for (Field polymorphicField : e.getValue()) {
+ String fieldName = fieldName(polymorphicField);
+
+ // invokeVisit for specific polymorphic config fields.
+ codeBlock.append(
+ invokeVisit(traverseChildrenMtd, polymorphicField, fieldDefs.get(fieldName)).pop()
+ );
+ }
+
+ switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), codeBlock);
+ }
+
+ // if (polymorphicTypeIdField != null) switch_by_polymorphicTypeIdField
+ mtdBody.append(
+ new IfStatement()
+ .condition(isNotNull(getThisFieldCode(traverseChildrenMtd, polymorphicTypeIdFieldDef)))
+ .ifTrue(switchBuilderTypeId.build())
+ );
}
mtdBody.ret();
@@ -711,16 +1073,20 @@ public class ConfigurationAsmGenerator {
/**
* Implements {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} method.
*
- * @param classDef Class definition.
- * @param fieldDefs Definitions for all fields in {@code schemaFields}.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param classDef Class definition.
+ * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
*/
private static void addNodeTraverseChildMethod(
ClassDefinition classDef,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+ @Nullable FieldDefinition polymorphicTypeIdFieldDef
) {
MethodDefinition traverseChildMtd = classDef.declareMethod(
of(PUBLIC),
@@ -731,35 +1097,96 @@ public class ConfigurationAsmGenerator {
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");
+ // Create switch for public (common in case polymorphic config) fields only.
+ 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()) {
+ // Create switch for public + internal fields.
+ StringSwitchBuilder switchBuilderAllFields = new StringSwitchBuilder(traverseChildMtd.getScope())
+ .expression(keyVar)
+ .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+ for (Field schemaField : union(schemaFields, internalFields)) {
+ String fieldName = fieldName(schemaField);
+
+ switchBuilderAllFields.addCase(
+ fieldName,
+ invokeVisit(traverseChildMtd, schemaField, fieldDefs.get(fieldName)).retObject()
+ );
+ }
+
+ // if (includeInternal) switch_by_all_fields
+ // else switch_only_public_fields
+ traverseChildMtd.getBody().append(
new IfStatement()
- .condition(includeInternalVar)
- .ifTrue(invokeVisitForTraverseChild(union(extensionsFields, schemaFields), fieldDefs, traverseChildMtd))
- .ifFalse(invokeVisitForTraverseChild(asList(schemaFields), fieldDefs, traverseChildMtd))
+ .condition(traverseChildMtd.getScope().getVariable("includeInternal"))
+ .ifTrue(switchBuilderAllFields.build())
+ .ifFalse(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
);
}
+ else if (!polymorphicFieldsByExtension.isEmpty()) {
+ assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+ // Create switch by polymorphicTypeIdField.
+ StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(traverseChildMtd, polymorphicTypeIdFieldDef);
+
+ for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+ // Create switch for specific polymorphic instance.
+ StringSwitchBuilder switchBuilderPolymorphicExtension = new StringSwitchBuilder(traverseChildMtd.getScope())
+ .expression(keyVar)
+ .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+ for (Field polymorphicField : e.getValue()) {
+ String fieldName = fieldName(polymorphicField);
+
+ switchBuilderPolymorphicExtension.addCase(
+ polymorphicField.getName(),
+ invokeVisit(traverseChildMtd, polymorphicField, fieldDefs.get(fieldName)).retObject()
+ );
+ }
+
+ switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), switchBuilderPolymorphicExtension.build());
+ }
+
+ // switch_by_common_fields
+ // switch_by_polymorphicTypeIdField
+ // switch_by_polymorphic_0_fields
+ // switch_by_polymorphic_1_fields
+ // ...
+ traverseChildMtd.getBody()
+ .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+ .append(switchBuilderTypeId.build());
+ }
+ else {
+ traverseChildMtd.getBody()
+ .append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build());
+ }
}
/**
* Creates bytecode block that invokes one of {@link ConfigurationVisitor}'s methods.
*
- * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} or
- * {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} defined in {@code *Node} class.
+ * @param mtd Method definition, either {@link InnerNode#traverseChildren(ConfigurationVisitor, boolean)} or
+ * {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)} defined in {@code *Node} class.
* @param schemaField Configuration Schema field to visit.
- * @param fieldDef Field definition from current class.
+ * @param fieldDef Field definition from current class.
* @return Bytecode block that invokes "visit*" method.
*/
private static BytecodeBlock invokeVisit(MethodDefinition mtd, Field schemaField, FieldDefinition fieldDef) {
Method visitMethod;
- if (isValue(schemaField))
+ if (isValue(schemaField) || isPolymorphicId(schemaField))
visitMethod = VISIT_LEAF;
else if (isConfigValue(schemaField))
visitMethod = VISIT_INNER;
@@ -768,7 +1195,7 @@ public class ConfigurationAsmGenerator {
return new BytecodeBlock().append(mtd.getScope().getVariable("visitor").invoke(
visitMethod,
- constantString(fieldDef.getName()),
+ constantString(schemaField.getName()),
mtd.getThis().getField(fieldDef)
));
}
@@ -776,16 +1203,22 @@ public class ConfigurationAsmGenerator {
/**
* Implements {@link ConstructableTreeNode#construct(String, ConfigurationSource, boolean)} method.
*
- * @param classDef Class definition.
- * @param fieldDefs Definitions for all fields in {@code schemaFields}.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param classDef Class definition.
+ * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+ * @param changePolymorphicTypeIdMtd Method for changing the type of polymorphic configuration.
*/
private void addNodeConstructMethod(
ClassDefinition classDef,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+ @Nullable FieldDefinition polymorphicTypeIdFieldDef,
+ @Nullable MethodDefinition changePolymorphicTypeIdMtd
) {
MethodDefinition constructMtd = classDef.declareMethod(
of(PUBLIC),
@@ -796,38 +1229,126 @@ public class ConfigurationAsmGenerator {
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");
+ // Create switch for public (common in case polymorphic config) fields only.
+ 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)) {
+ // src == null ? null : src.unwrap(FieldType.class);
+ BytecodeExpression getTypeIdFromSrcVar = inlineIf(
+ isNull(srcVar),
+ constantNull(fieldDef.getType()),
+ srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
+ );
+
+ // this.changePolymorphicTypeId(src == null ? null : src.unwrap(FieldType.class));
+ switchBuilder.addCase(
+ fieldName,
+ new BytecodeBlock()
+ .append(constructMtd.getThis())
+ .append(getTypeIdFromSrcVar)
+ .invokeVirtual(changePolymorphicTypeIdMtd)
+ .ret()
+ );
+ }
+ else {
+ switchBuilder.addCase(
+ fieldName,
+ treatSourceForConstruct(constructMtd, schemaField, fieldDef).ret()
+ );
+ }
+ }
+
+ if (!internalFields.isEmpty()) {
+ // Create switch for public + internal fields.
+ StringSwitchBuilder switchBuilderAllFields = new StringSwitchBuilder(constructMtd.getScope())
+ .expression(keyVar)
+ .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+ for (Field schemaField : union(schemaFields, internalFields)) {
+ String fieldName = fieldName(schemaField);
+
+ switchBuilderAllFields.addCase(
+ fieldName,
+ treatSourceForConstruct(constructMtd, schemaField, fieldDefs.get(fieldName)).ret()
+ );
+ }
+
+ // if (includeInternal) switch_by_all_fields
+ // else switch_only_public_fields
+ constructMtd.getBody().append(
+ new IfStatement().condition(constructMtd.getScope().getVariable("includeInternal"))
+ .ifTrue(switchBuilderAllFields.build())
+ .ifFalse(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
).ret();
}
+ else if (!polymorphicFieldsByExtension.isEmpty()) {
+ assert polymorphicTypeIdFieldDef != null : classDef.getName();
+
+ // Create switch by polymorphicTypeIdField.
+ StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(constructMtd, polymorphicTypeIdFieldDef);
+
+ for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+ // Create switch for specific polymorphic instance.
+ StringSwitchBuilder switchBuilderPolymorphicExtension = new StringSwitchBuilder(constructMtd.getScope())
+ .expression(keyVar)
+ .defaultCase(throwException(NoSuchElementException.class, keyVar));
+
+ for (Field polymorphicField : e.getValue()) {
+ String fieldName = fieldName(polymorphicField);
+ FieldDefinition fieldDef = fieldDefs.get(fieldName);
+
+ switchBuilderPolymorphicExtension.addCase(
+ polymorphicField.getName(),
+ treatSourceForConstruct(constructMtd, polymorphicField, fieldDef).ret()
+ );
+ }
+
+ switchBuilderTypeId.addCase(polymorphicInstanceId(e.getKey()), switchBuilderPolymorphicExtension.build());
+ }
+
+ // switch_by_common_fields
+ // switch_by_polymorphicTypeIdField
+ // switch_by_polymorphic_0_fields
+ // switch_by_polymorphic_1_fields
+ // ...
+ constructMtd.getBody()
+ .append(switchBuilder.defaultCase(new BytecodeBlock()).build())
+ .append(switchBuilderTypeId.build())
+ .ret();
+ }
+ else {
+ constructMtd.getBody()
+ .append(switchBuilder.defaultCase(throwException(NoSuchElementException.class, keyVar)).build())
+ .ret();
+ }
}
/**
* Implements {@link InnerNode#constructDefault(String)} method.
*
- * @param classDef Node class definition.
- * @param specFields Definition of fields for the {@code _spec#} fields of the node class.
- * Mapping: configuration schema class -> {@code _spec#} field.
- * @param fieldDefs Field definitions for all fields of node class excluding {@code _spec}.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param classDef Class definition.
+ * @param specFields Field definitions for the schema and its extensions: {@code _spec#}.
+ * @param fieldDefs Definitions for all fields in {@code schemaFields}.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFieldsByExtension Fields of polymorphic configuration instances grouped by them.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
*/
private static void addNodeConstructDefaultMethod(
ClassDefinition classDef,
Map<Class<?>, FieldDefinition> specFields,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Map<Class<?>, List<Field>> polymorphicFieldsByExtension,
+ @Nullable FieldDefinition polymorphicTypeIdFieldDef
) {
MethodDefinition constructDfltMtd = classDef.declareMethod(
of(PUBLIC),
@@ -838,73 +1359,122 @@ public class ConfigurationAsmGenerator {
Variable keyVar = constructDfltMtd.getScope().getVariable("key");
- StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope())
- .expression(keyVar);
-
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
- if (!isValue(schemaField))
- continue;
-
- if (!schemaField.getAnnotation(Value.class).hasDefault()) {
- switchBuilder.addCase(schemaField.getName(), new BytecodeBlock());
-
- continue;
+ // Create switch for public (common in case polymorphic config) + internal fields.
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructDfltMtd.getScope()).expression(keyVar);
+
+ for (Field schemaField : concat(schemaFields, internalFields)) {
+ if (isValue(schemaField) || isPolymorphicId(schemaField)) {
+ String fieldName = schemaField.getName();
+
+ 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());
+
+ // 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()) {
+ // Create switch by polymorphicTypeIdField.
+ StringSwitchBuilder switchBuilderTypeId = typeIdSwitchBuilder(constructDfltMtd, polymorphicTypeIdFieldDef);
+
+ for (Map.Entry<Class<?>, List<Field>> e : polymorphicFieldsByExtension.entrySet()) {
+ // Create switch for specific polymorphic instance.
+ 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);
+ // switch_by_common_fields
+ // switch_by_polymorphicTypeIdField
+ // switch_by_polymorphic_0_fields
+ // switch_by_polymorphic_1_fields
+ // ...
+ 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.
+ * Code like: {@code this.field == null ? new ValueNode() : (ValueNode)this.field.copy();}.
+ *
+ * @param schemaField Configuration schema class field.
+ * @param getFieldCode Bytecode of getting the field, for example: {@code this.field} or {@code this.field.field};
* @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 BytecodeExpression newOrCopyNodeField(Field schemaField, BytecodeExpression getFieldCode) {
+ ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).nodeClassName);
+
+ // this.field == null ? new ValueNode() : (ValueNode)this.field.copy();
+ return inlineIf(
+ isNull(getFieldCode),
+ newInstance(nodeType),
+ copyNodeField(schemaField, getFieldCode)
+ );
}
/**
- * Creates {@code *Node::new} lambda expression with {@link Supplier} type.
+ * Copies field into itself.
+ * Code like: {@code (ValueNode)this.field.copy();}.
+ *
+ * @param schemaField Configuration schema class field.
+ * @param getFieldCode Bytecode of getting the field, for example: {@code this.field} or {@code this.field.field};
+ * @return Bytecode expression.
+ */
+ private BytecodeExpression copyNodeField(Field schemaField, BytecodeExpression getFieldCode) {
+ ParameterizedType nodeType = isNamedConfigValue(schemaField)
+ ? type(NamedListNode.class) : typeFromJavaClassName(schemasInfo.get(schemaField.getType()).nodeClassName);
+
+ // (ValueNode)this.field.copy();
+ return getFieldCode.invoke(COPY).cast(nodeType);
+ }
+
+ /**
+ * Creates {@code *Node::new} lambda expression with {@link Supplier} type.
+ *
* @param nodeClassName Name of the {@code *Node} class.
* @return InvokeDynamic bytecode expression.
*/
@@ -930,17 +1500,20 @@ public class ConfigurationAsmGenerator {
/**
* Construct a {@link DynamicConfiguration} definition for a configuration schema.
*
- * @param schemaClass Configuration schema class.
- * @param schemaExtensions Internal extensions of the configuration schema.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ * @param schemaClass Configuration schema class.
+ * @param internalExtensions Internal extensions of the configuration schema.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
+ * @param polymorphicFields Fields of polymorphic extensions of the configuration schema.
* @return Constructed {@link DynamicConfiguration} definition for the configuration schema.
*/
private ClassDefinition createCfgImplClass(
Class<?> schemaClass,
- Set<Class<?>> schemaExtensions,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Set<Class<?>> internalExtensions,
+ Set<Class<?>> polymorphicExtensions,
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Collection<Field> polymorphicFields
) {
SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
@@ -951,24 +1524,64 @@ public class ConfigurationAsmGenerator {
of(PUBLIC, FINAL),
internalName(schemaClassInfo.cfgImplClassName),
type(superClass),
- configClassInterfaces(schemaClass, schemaExtensions)
+ configClassInterfaces(schemaClass, internalExtensions)
);
// Fields.
Map<String, FieldDefinition> fieldDefs = new HashMap<>();
- for (Field schemaField : concat(asList(schemaFields), extensionsFields))
- fieldDefs.put(schemaField.getName(), addConfigurationImplField(classDef, schemaField));
+ // To store the id of the polymorphic configuration instance.
+ FieldDefinition polymorphicTypeIdFieldDef = null;
+
+ for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+ String fieldName = fieldName(schemaField);
+
+ FieldDefinition fieldDef = addConfigurationImplField(classDef, schemaField, fieldName);
+
+ fieldDefs.put(fieldName, fieldDef);
+
+ if (isPolymorphicId(schemaField))
+ polymorphicTypeIdFieldDef = fieldDef;
+ }
// Constructor
- addConfigurationImplConstructor(classDef, schemaClassInfo, fieldDefs, schemaFields, extensionsFields);
+ addConfigurationImplConstructor(
+ classDef,
+ schemaClass,
+ fieldDefs,
+ schemaFields,
+ internalFields,
+ polymorphicFields
+ );
- for (Field schemaField : concat(asList(schemaFields), extensionsFields))
- addConfigurationImplGetMethod(classDef, schemaClass, fieldDefs, schemaField);
+ for (Field schemaField : concat(schemaFields, internalFields))
+ addConfigurationImplGetMethod(classDef, schemaField, fieldDefs.get(fieldName(schemaField)));
// org.apache.ignite.internal.configuration.DynamicConfiguration#configType
addCfgImplConfigTypeMethod(classDef, typeFromJavaClassName(schemaClassInfo.cfgClassName));
+ if (!polymorphicExtensions.isEmpty()) {
+ addCfgSpecificConfigTreeMethod(classDef, schemaClass, polymorphicExtensions, polymorphicTypeIdFieldDef);
+
+ addCfgRemoveMembersMethod(
+ classDef,
+ schemaClass,
+ polymorphicExtensions,
+ fieldDefs,
+ polymorphicFields,
+ polymorphicTypeIdFieldDef
+ );
+
+ addCfgAddMembersMethod(
+ classDef,
+ schemaClass,
+ polymorphicExtensions,
+ fieldDefs,
+ polymorphicFields,
+ polymorphicTypeIdFieldDef
+ );
+ }
+
return classDef;
}
@@ -977,49 +1590,61 @@ public class ConfigurationAsmGenerator {
* <ul>
* <li>
* {@code @Value public type fieldName}<br/>becomes<br/>
- * {@code private DynamicProperty fieldName}
+ * {@code public DynamicProperty fieldName}
* </li>
* <li>
* {@code @ConfigValue public MyConfigurationSchema fieldName}<br/>becomes<br/>
- * {@code private MyConfiguration fieldName}
+ * {@code public MyConfiguration fieldName}
* </li>
* <li>
* {@code @NamedConfigValue public type fieldName}<br/>becomes<br/>
- * {@code private NamedListConfiguration fieldName}
+ * {@code public NamedListConfiguration fieldName}
+ * </li>
+ * <li>
+ * {@code @PolymorphicId public String fieldName}<br/>becomes<br/>
+ * {@code public String fieldName}
* </li>
* </ul>
- * @param classDef Configuration impl class definition.
+ *
+ * @param classDef Configuration impl class definition.
* @param schemaField Configuration Schema class field.
+ * @param fieldName Field name, if {@code null} will be used {@link Field#getName}.
* @return Declared field definition.
*/
- private FieldDefinition addConfigurationImplField(ClassDefinition classDef, Field schemaField) {
+ private FieldDefinition addConfigurationImplField(
+ ClassDefinition classDef,
+ Field schemaField,
+ String fieldName
+ ) {
ParameterizedType fieldType;
if (isConfigValue(schemaField))
- fieldType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).cfgClassName);
+ fieldType = typeFromJavaClassName(schemasInfo.get(schemaField.getType()).cfgImplClassName);
else if (isNamedConfigValue(schemaField))
fieldType = type(NamedListConfiguration.class);
else
fieldType = type(DynamicProperty.class);
- return classDef.declareField(of(PRIVATE), schemaField.getName(), fieldType);
+ return classDef.declareField(of(PUBLIC), fieldName, fieldType);
}
/**
* Implements default constructor for the configuration class. It initializes all fields and adds them to members
* collection.
- * @param classDef Configuration impl class definition.
- * @param schemaClassInfo Configuration Schema class info.
- * @param fieldDefs Field definitions for all fields of configuration impl class.
- * @param schemaFields Fields of the schema class.
- * @param extensionsFields Fields of internal extensions of the configuration schema.
+ *
+ * @param classDef Configuration impl class definition.
+ * @param schemaClass Configuration schema class.
+ * @param fieldDefs Field definitions for all fields of configuration impl class.
+ * @param schemaFields Fields of the schema class.
+ * @param internalFields Fields of internal extensions of the configuration schema.
*/
private void addConfigurationImplConstructor(
ClassDefinition classDef,
- SchemaClassesInfo schemaClassInfo,
+ Class<?> schemaClass,
Map<String, FieldDefinition> fieldDefs,
- Field[] schemaFields,
- Set<Field> extensionsFields
+ Collection<Field> schemaFields,
+ Collection<Field> internalFields,
+ Collection<Field> polymorphicFields
) {
MethodDefinition ctor = classDef.declareConstructor(
of(PUBLIC),
@@ -1034,8 +1659,9 @@ public class ConfigurationAsmGenerator {
Variable changerVar = ctor.getScope().getVariable("changer");
Variable listenOnlyVar = ctor.getScope().getVariable("listenOnly");
- Constructor<?> superCtor = schemaClassInfo.direct ?
- DIRECT_DYNAMIC_CONFIGURATION_CTOR : DYNAMIC_CONFIGURATION_CTOR;
+ SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+ Constructor<?> superCtor = schemaClassInfo.direct ? DIRECT_DYNAMIC_CONFIGURATION_CTOR : DYNAMIC_CONFIGURATION_CTOR;
BytecodeBlock ctorBody = ctor.getBody()
.append(ctor.getThis())
@@ -1049,12 +1675,12 @@ public class ConfigurationAsmGenerator {
BytecodeExpression thisKeysVar = ctor.getThis().getField("keys", List.class);
int newIdx = 0;
- for (Field schemaField : concat(asList(schemaFields), extensionsFields)) {
- FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+ for (Field schemaField : concat(schemaFields, internalFields, polymorphicFields)) {
+ String fieldName = schemaField.getName();
BytecodeExpression newValue;
- if (isValue(schemaField)) {
+ if (isValue(schemaField) || isPolymorphicId(schemaField)) {
Class<?> fieldImplClass = schemaField.isAnnotationPresent(DirectAccess.class) ?
DirectDynamicProperty.class : DynamicProperty.class;
@@ -1062,10 +1688,11 @@ public class ConfigurationAsmGenerator {
newValue = newInstance(
fieldImplClass,
thisKeysVar,
- constantString(schemaField.getName()),
+ constantString(fieldName),
rootKeyVar,
changerVar,
- listenOnlyVar
+ listenOnlyVar,
+ constantBoolean(isPolymorphicId(schemaField))
);
}
else {
@@ -1078,7 +1705,7 @@ public class ConfigurationAsmGenerator {
newValue = newInstance(
cfgImplParameterizedType,
thisKeysVar,
- constantString(schemaField.getName()),
+ constantString(fieldName),
rootKeyVar,
changerVar,
listenOnlyVar
@@ -1108,7 +1735,7 @@ public class ConfigurationAsmGenerator {
newValue = newInstance(
fieldImplClass,
thisKeysVar,
- constantString(schemaField.getName()),
+ constantString(fieldName),
rootKeyVar,
changerVar,
listenOnlyVar,
@@ -1158,11 +1785,15 @@ public class ConfigurationAsmGenerator {
}
}
+ FieldDefinition fieldDef = fieldDefs.get(fieldName(schemaField));
+
// this.field = newValue;
ctorBody.append(ctor.getThis().setField(fieldDef, newValue));
- // add(this.field);
- ctorBody.append(ctor.getThis().invoke(DYNAMIC_CONFIGURATION_ADD, ctor.getThis().getField(fieldDef)));
+ if (!isPolymorphicConfigInstance(schemaField.getDeclaringClass())) {
+ // add(this.field);
+ ctorBody.append(ctor.getThis().invoke(DYNAMIC_CONFIGURATION_ADD_MTD, ctor.getThis().getField(fieldDef)));
+ }
}
ctorBody.ret();
@@ -1170,50 +1801,58 @@ public class ConfigurationAsmGenerator {
/**
* Implements accessor method in configuration impl class.
- * @param classDef Configuration impl class definition.
- * @param schemaClass Configuration Schema class.
- * @param fieldDefs Field definitions for all fields of configuration impl class.
+ *
+ * @param classDef Configuration impl class definition.
* @param schemaField Configuration Schema class field.
+ * @param fieldDefs Field definitions.
*/
private void addConfigurationImplGetMethod(
ClassDefinition classDef,
- Class<?> schemaClass,
- Map<String, FieldDefinition> fieldDefs,
- Field schemaField
+ Field schemaField,
+ FieldDefinition... fieldDefs
) {
+ assert !nullOrEmpty(fieldDefs);
+
Class<?> schemaFieldType = schemaField.getType();
String fieldName = schemaField.getName();
- FieldDefinition fieldDef = fieldDefs.get(fieldName);
ParameterizedType returnType;
+ SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaFieldType);
+
if (isConfigValue(schemaField))
- returnType = typeFromJavaClassName(schemasInfo.get(schemaFieldType).cfgClassName);
+ returnType = typeFromJavaClassName(schemaClassInfo.cfgClassName);
else if (isNamedConfigValue(schemaField))
returnType = type(NamedConfigurationTree.class);
else {
- assert isValue(schemaField) : schemaClass;
+ assert isValue(schemaField) || isPolymorphicId(schemaField) : schemaField;
returnType = type(ConfigurationValue.class);
}
+ // public ConfigurationProperty fieldName()
MethodDefinition viewMtd = classDef.declareMethod(
of(PUBLIC),
fieldName,
returnType
);
- BytecodeBlock viewBody = viewMtd.getBody();
+ // result = this.field;
+ BytecodeBlock body = viewMtd.getBody().append(getThisFieldCode(viewMtd, fieldDefs));
- viewBody
- .append(viewMtd.getThis())
- .getField(fieldDef)
- .retObject();
+ if (isPolymorphicConfig(schemaFieldType) && isConfigValue(schemaField)) {
+ // result = this.field.specificConfigTree();
+ body.invokeVirtual(SPECIFIC_CONFIG_TREE_MTD);
+ }
+
+ // return result;
+ body.retObject();
}
/**
* Replaces first letter in string with its upper-cased variant.
+ *
* @param name Some string.
* @return Capitalized version of passed string.
*/
@@ -1223,6 +1862,7 @@ public class ConfigurationAsmGenerator {
/**
* Returns internalized version of class name, replacing dots with slashes.
+ *
* @param className Class name (with package).
* @return Internal class name.
* @see Type#getInternalName(Class)
@@ -1232,8 +1872,8 @@ public class ConfigurationAsmGenerator {
}
/**
- * 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.
+ * Creates boxed version of the class.
+ *
* @param clazz Maybe primitive class.
* @return Not primitive class that represents parameter class.
*/
@@ -1244,196 +1884,931 @@ public class ConfigurationAsmGenerator {
}
/**
- * 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)));
+ }
+
+ 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) {
+ List<ParameterizedType> result = Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
+ .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
+ .collect(toCollection(ArrayList::new));
+
+ if (schemasInfo.get(schemaClass).direct)
+ result.add(type(DirectConfigurationProperty.class));
+
+ return result.toArray(new ParameterizedType[0]);
+ }
+
+ /**
+ * Add {@link DynamicConfiguration#configType} method implementation to the class. It looks like the following code:
+ * <pre><code>
+ * public Class configType() {
+ * return RootConfiguration.class;
+ * }
+ * </code></pre>
+ * @param classDef Class definition.
+ * @param clazz Definition of the configuration interface, for example {@code RootConfiguration}.
+ */
+ private static void addCfgImplConfigTypeMethod(ClassDefinition classDef, ParameterizedType clazz) {
+ classDef.declareMethod(of(PUBLIC), "configType", type(Class.class))
+ .getBody()
+ .append(constantClass(clazz))
+ .retObject();
+ }
+
+ /**
+ * Create a {@code *Node} for the polymorphic configuration instance schema.
+ *
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtension Polymorphic configuration instance schema (child).
+ * @param schemaInnerNodeClassDef {@link InnerNode} definition for the polymorphic configuration schema {@code schemaClass}.
+ * @param schemaFields Schema fields of polymorphic configuration {@code schemaClass}.
+ * @param polymorphicFields Schema fields of a polymorphic configuration instance {@code polymorphicExtension}.
+ */
+ private ClassDefinition createPolymorphicExtensionNodeClass(
+ Class<?> schemaClass,
+ Class<?> polymorphicExtension,
+ ClassDefinition schemaInnerNodeClassDef,
+ Collection<Field> schemaFields,
+ Collection<Field> polymorphicFields
+ ) {
+ SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+ SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+ // Node class definition.
+ ClassDefinition classDef = new ClassDefinition(
+ of(PUBLIC, FINAL),
+ internalName(polymorphicExtensionClassInfo.nodeClassName),
+ type(Object.class),
+ nodeClassInterfaces(polymorphicExtension, Set.of())
+ );
+
+ // private final ParentNode this$0;
+ FieldDefinition parentInnerNodeFieldDef = classDef.declareField(
+ of(PRIVATE, FINAL),
+ "this$0",
+ typeFromJavaClassName(schemaClassInfo.nodeClassName)
+ );
+
+ // Constructor.
+ MethodDefinition constructorMtd = classDef.declareConstructor(
+ of(PUBLIC),
+ arg("delegate", typeFromJavaClassName(schemaClassInfo.nodeClassName))
+ );
+
+ Variable delegateVar = constructorMtd.getScope().getVariable("delegate");
+
+ // Constructor body.
+ constructorMtd.getBody()
+ .append(constructorMtd.getThis())
+ .invokeConstructor(Object.class)
+ .append(constructorMtd.getThis().setField(
+ parentInnerNodeFieldDef,
+ delegateVar
+ ))
+ .ret();
+
+ Map<String, FieldDefinition> fieldDefs = schemaInnerNodeClassDef.getFields().stream()
+ .collect(toMap(FieldDefinition::getName, identity()));
+
+ // Creates view and change methods for parent schema.
+ for (Field schemaField : schemaFields) {
+ FieldDefinition schemaFieldDef = fieldDefs.get(fieldName(schemaField));
+
+ addNodeViewMethod(
+ classDef,
+ schemaField,
+ viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, schemaFieldDef),
+ null
+ );
+
+ // Read only.
+ if (isPolymorphicId(schemaField))
+ continue;
+
+ MethodDefinition changeMtd0 = addNodeChangeMethod(
+ classDef,
+ schemaField,
+ changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, schemaFieldDef),
+ (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, parentInnerNodeFieldDef, schemaFieldDef),
+ null
+ );
+
+ addNodeChangeBridgeMethod(classDef, schemaClassInfo.changeClassName, changeMtd0);
+ }
+
+ FieldDefinition polymorphicTypeIdFieldDef = fieldDefs.get(polymorphicIdField(schemaClass).getName());
+
+ // Creates view and change methods for specific polymorphic instance schema.
+ for (Field polymorphicField : polymorphicFields) {
+ FieldDefinition polymorphicFieldDef = fieldDefs.get(fieldName(polymorphicField));
+
+ addNodeViewMethod(
+ classDef,
+ polymorphicField,
+ viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, polymorphicFieldDef),
+ viewMtd -> getThisFieldCode(viewMtd, parentInnerNodeFieldDef, polymorphicTypeIdFieldDef)
+ );
+
+ MethodDefinition changeMtd0 = addNodeChangeMethod(
+ classDef,
+ polymorphicField,
+ changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, polymorphicFieldDef),
+ (changeMtd, newValue) -> setThisFieldCode(changeMtd, newValue, parentInnerNodeFieldDef, polymorphicFieldDef),
+ changeMtd -> getThisFieldCode(changeMtd, parentInnerNodeFieldDef, polymorphicTypeIdFieldDef)
+ );
+
+ addNodeChangeBridgeMethod(classDef, polymorphicExtensionClassInfo.changeClassName, changeMtd0);
+ }
+
+ ParameterizedType returnType = typeFromJavaClassName(schemaClassInfo.changeClassName);
+
+ // Creates Node#convert.
+ MethodDefinition convertMtd = classDef.declareMethod(
+ of(PUBLIC),
+ CONVERT_MTD_NAME,
+ returnType,
+ arg("changeClass", Class.class)
+ );
+
+ // return this.this$0.convert(changeClass);
+ convertMtd.getBody()
+ .append(getThisFieldCode(convertMtd, parentInnerNodeFieldDef))
+ .append(convertMtd.getScope().getVariable("changeClass"))
+ .invokeVirtual(schemaInnerNodeClassDef.getType(), CONVERT_MTD_NAME, returnType, type(Class.class))
+ .retObject();
+
+ return classDef;
+ }
+
+ /**
+ * Create a {@code *CfgImpl} for the polymorphic configuration instance schema.
+ *
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtension Polymorphic configuration instance schema (child).
+ * @param schemaCfgImplClassDef {@link DynamicConfiguration} definition for the polymorphic configuration schema {@code schemaClass}.
+ * @param schemaFields Schema fields of polymorphic configuration {@code schemaClass}.
+ * @param polymorphicFields Schema fields of a polymorphic configuration instance {@code polymorphicExtension}.
*/
- private 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 this$0;
+ FieldDefinition parentCfgImplFieldDef = classDef.declareField(
+ of(PRIVATE, FINAL),
+ "this$0",
+ typeFromJavaClassName(schemaClassInfo.cfgImplClassName)
+ );
+
+ // Constructor.
+ MethodDefinition constructorMtd = classDef.declareConstructor(
+ of(PUBLIC),
+ arg("delegate", typeFromJavaClassName(schemaClassInfo.cfgImplClassName))
+ );
+
+ Variable delegateVar = constructorMtd.getScope().getVariable("delegate");
+
+ // Constructor body.
+ // super(parent);
+ // this.this$0 = parent;
+ constructorMtd.getBody()
+ .append(constructorMtd.getThis())
+ .append(delegateVar)
+ .invokeConstructor(superClass, ConfigurationTree.class)
+ .append(constructorMtd.getThis().setField(
+ parentCfgImplFieldDef,
+ delegateVar
+ ))
+ .ret();
+
+ Map<String, FieldDefinition> fieldDefs = schemaCfgImplClassDef.getFields().stream()
+ .collect(toMap(FieldDefinition::getName, identity()));
+
+ for (Field schemaField : concat(schemaFields, polymorphicFields)) {
+ addConfigurationImplGetMethod(
+ classDef,
+ schemaField,
+ parentCfgImplFieldDef,
+ fieldDefs.get(fieldName(schemaField))
+ );
+ }
+
+ return classDef;
+ }
+
+ /**
+ * Adds a {@link InnerNode#specificNode} override for the polymorphic configuration case.
+ *
+ * @param classDef Definition of a polymorphic configuration class (parent).
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+ */
+ private void addNodeSpecificNodeMethod(
+ ClassDefinition classDef,
+ Set<Class<?>> polymorphicExtensions,
+ FieldDefinition polymorphicTypeIdFieldDef
+ ) {
+ MethodDefinition specificNodeMtd = classDef.declareMethod(
+ of(PUBLIC),
+ SPECIFIC_NODE_MTD.getName(),
+ type(Object.class)
+ );
+
+ StringSwitchBuilder switchBuilder = typeIdSwitchBuilder(specificNodeMtd, polymorphicTypeIdFieldDef);
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+ switchBuilder.addCase(
+ polymorphicInstanceId(polymorphicExtension),
+ newInstance(
+ typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+ specificNodeMtd.getThis()
+ ).ret()
+ );
+ }
+
+ specificNodeMtd.getBody().append(switchBuilder.build());
+ }
+
+ /**
+ * Adds a {@code *Node#convert} for the polymorphic configuration case.
+ *
+ * @param classDef Definition of a polymorphic configuration class {@code schemaClass}.
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param changePolymorphicTypeIdMtd Method for changing the type of polymorphic configuration.
+ */
+ private void addNodeConvertMethod(
+ ClassDefinition classDef,
+ Class<?> schemaClass,
+ Set<Class<?>> polymorphicExtensions,
+ MethodDefinition changePolymorphicTypeIdMtd
+ ) {
+ SchemaClassesInfo schemaClassInfo = schemasInfo.get(schemaClass);
+
+ MethodDefinition convertMtd = classDef.declareMethod(
+ of(PUBLIC),
+ CONVERT_MTD_NAME,
+ typeFromJavaClassName(schemaClassInfo.changeClassName),
+ arg("changeClass", Class.class)
+ );
+
+ // changeClass.getName();
+ BytecodeExpression changeClassName = convertMtd.getScope()
+ .getVariable("changeClass")
+ .invoke(CLASS_GET_NAME_MTD);
+
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(convertMtd.getScope())
+ .expression(changeClassName)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, changeClassName));
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ SchemaClassesInfo polymorphicExtensionClassInfo = schemasInfo.get(polymorphicExtension);
+
+ // this.changePolymorphicTypeId(polymorphicTypeId);
+ // return new ChildNode(this);
+ switchBuilder.addCase(
+ polymorphicExtensionClassInfo.changeClassName,
+ new BytecodeBlock()
+ .append(constantString(polymorphicInstanceId(polymorphicExtension)))
+ .invokeVirtual(changePolymorphicTypeIdMtd)
+ .append(newInstance(
+ typeFromJavaClassName(polymorphicExtensionClassInfo.nodeClassName),
+ convertMtd.getThis()
+ ))
+ .retObject()
+ );
+ }
+
+ convertMtd.getBody()
+ .append(convertMtd.getThis())
+ .append(switchBuilder.build())
+ .ret();
+ }
+
+ /**
+ * Adds a {@code Node#changeTypeId} for the polymorphic configuration case.
+ *
+ * @param classDef Definition of a polymorphic configuration class (parent).
+ * @param fieldDefs Definitions for all fields in {@code classDef}.
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param polymorphicFields Fields of polymorphic extensions.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+ * @return Method definition.
+ */
+ private MethodDefinition addNodeChangePolymorphicTypeIdMethod(
+ ClassDefinition classDef,
Map<String, FieldDefinition> fieldDefs,
- MethodDefinition traverseChildrenMtd
+ Set<Class<?>> polymorphicExtensions,
+ Collection<Field> polymorphicFields,
+ FieldDefinition polymorphicTypeIdFieldDef
) {
- if (schemaFields.isEmpty())
- return List.of();
- else {
- return schemaFields.stream()
- .map(field -> invokeVisit(traverseChildrenMtd, field, fieldDefs.get(field.getName())).pop())
+ MethodDefinition changePolymorphicTypeIdMtd = classDef.declareMethod(
+ of(PUBLIC),
+ changeMethodName(polymorphicTypeIdFieldDef.getName()),
+ type(void.class),
+ arg("typeId", String.class)
+ );
+
+ Variable typeIdVar = changePolymorphicTypeIdMtd.getScope().getVariable("typeId");
+
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(changePolymorphicTypeIdMtd.getScope())
+ .expression(typeIdVar)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, typeIdVar));
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ // Fields that need to be cleared when changing the type of the polymorphic configuration instance.
+ Collection<Field> resetFields = polymorphicFields.stream()
+ .filter(f -> !polymorphicExtension.equals(f.getDeclaringClass()))
.collect(toList());
+
+ // this.typeId = typeId;
+ BytecodeBlock codeBlock = new BytecodeBlock()
+ .append(setThisFieldCode(changePolymorphicTypeIdMtd, typeIdVar, polymorphicTypeIdFieldDef));
+
+ // Reset fields.
+ for (Field resetField : resetFields) {
+ FieldDefinition fieldDef = fieldDefs.get(fieldName(resetField));
+
+ if (isValue(resetField) || isConfigValue(resetField)) {
+ // this.field = null;
+ codeBlock.append(setThisFieldCode(
+ changePolymorphicTypeIdMtd,
+ constantNull(fieldDef.getType()),
+ fieldDef
+ ));
+ }
+ else {
+ // this.field = new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+ codeBlock.append(setThisFieldCode(changePolymorphicTypeIdMtd, newNamedListNode(resetField), fieldDef));
+ }
+ }
+
+ // ConfigurationUtil.addDefaults(this);
+ codeBlock
+ .append(changePolymorphicTypeIdMtd.getThis())
+ .invokeStatic(ADD_DEFAULTS_MTD);
+
+ switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), codeBlock);
}
+
+ // if(typeId.equals(this.typeId)) return;
+ // else switch(typeId)...
+ changePolymorphicTypeIdMtd.getBody()
+ .append(typeIdVar)
+ .append(getThisFieldCode(changePolymorphicTypeIdMtd, polymorphicTypeIdFieldDef))
+ .append(
+ new IfStatement()
+ .condition(new BytecodeBlock().invokeVirtual(STRING_EQUALS_MTD))
+ .ifTrue(new BytecodeBlock().ret())
+ .ifFalse(switchBuilder.build().ret())
+ );
+
+ return changePolymorphicTypeIdMtd;
}
/**
- * Created switch bytecode block that invokes of {@link ConfigurationVisitor}'s methods for
- * {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)}.
+ * Adds a {@link DynamicConfiguration#specificConfigTree} override for the polymorphic configuration case.
*
- * @param schemaFields Fields of the schema.
- * @param fieldDefs Definitions for all fields in {@code schemaFields}.
- * @param traverseChildMtd Method definition {@link InnerNode#traverseChild(String, ConfigurationVisitor, boolean)}}
- * defined in {@code *Node} class.
- * @return Created switch bytecode block that invokes of {@link ConfigurationVisitor}'s methods for fields.
+ * @param classDef Definition of a polymorphic configuration class (parent).
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
*/
- private static BytecodeNode invokeVisitForTraverseChild(
- Collection<Field> schemaFields,
+ private void addCfgSpecificConfigTreeMethod(
+ ClassDefinition classDef,
+ Class<?> schemaClass,
+ Set<Class<?>> polymorphicExtensions,
+ FieldDefinition polymorphicTypeIdFieldDef
+ ) {
+ MethodDefinition specificConfigMtd = classDef.declareMethod(
+ of(PUBLIC),
+ SPECIFIC_CONFIG_TREE_MTD.getName(),
+ type(ConfigurationTree.class)
+ );
+
+ // String tmpStr;
+ Variable tmpStrVar = specificConfigMtd.getScope().createTempVariable(String.class);
+
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(specificConfigMtd.getScope())
+ .expression(tmpStrVar)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ // return new SpecialCfgImpl(this);
+ switchBuilder.addCase(
+ polymorphicInstanceId(polymorphicExtension),
+ newInstance(
+ typeFromJavaClassName(schemasInfo.get(polymorphicExtension).cfgImplClassName),
+ specificConfigMtd.getThis()
+ ).ret()
+ );
+ }
+
+ // ConfigNode
+ ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+ // Object tmpObj;
+ Variable tmpObjVar = specificConfigMtd.getScope().createTempVariable(Object.class);
+
+ // tmpObj = this.refreshValue();
+ // tmpStr = ((ConfigNode) tmpObj).typeId;
+ // switch(tmpStr) ...
+ specificConfigMtd.getBody()
+ .append(tmpObjVar.set(specificConfigMtd.getThis().invoke(REFRESH_VALUE_MTD)))
+ .append(tmpStrVar.set(tmpObjVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+ .append(switchBuilder.build())
+ .ret();
+ }
+
+ /**
+ * Adds a {@code DynamicConfiguration#removeMembers} override for the polymorphic configuration case.
+ *
+ * @param classDef Definition of a polymorphic configuration class (parent).
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param fieldDefs Field definitions for all fields of {@code classDef}.
+ * @param polymorphicFields Fields of polymorphic extensions.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+ */
+ private void addCfgRemoveMembersMethod(
+ ClassDefinition classDef,
+ Class<?> schemaClass,
+ Set<Class<?>> polymorphicExtensions,
Map<String, FieldDefinition> fieldDefs,
- MethodDefinition traverseChildMtd
+ Collection<Field> polymorphicFields,
+ FieldDefinition polymorphicTypeIdFieldDef
) {
- Variable keyVar = traverseChildMtd.getScope().getVariable("key");
+ MethodDefinition removeMembersMtd = classDef.declareMethod(
+ of(PUBLIC),
+ "removeMembers",
+ type(void.class),
+ arg("oldValue", type(Object.class)),
+ arg("members", type(Map.class))
+ );
- StringSwitchBuilder switchBuilder = new StringSwitchBuilder(traverseChildMtd.getScope()).expression(keyVar);
+ // InnerNode oldValue;
+ Variable oldValueVar = removeMembersMtd.getScope().getVariable("oldValue");
- for (Field schemaField : schemaFields) {
- String fieldName = schemaField.getName();
+ // Map members;
+ Variable membersVar = removeMembersMtd.getScope().getVariable("members");
- FieldDefinition fieldDef = fieldDefs.get(fieldName);
+ // String tmpStr;
+ Variable tmpStrVar = removeMembersMtd.getScope().createTempVariable(String.class);
+
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(removeMembersMtd.getScope())
+ .expression(tmpStrVar)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ Collection<Field> removeFields = polymorphicFields.stream()
+ .filter(f -> !polymorphicExtension.equals(f.getDeclaringClass()))
+ .collect(toList());
+
+ BytecodeBlock blockCode = new BytecodeBlock();
+
+ for (Field removeField : removeFields) {
+ // this.removeMember(members, this.field);
+ blockCode
+ .append(removeMembersMtd.getThis())
+ .append(membersVar)
+ .append(getThisFieldCode(removeMembersMtd, fieldDefs.get(fieldName(removeField))))
+ .invokeVirtual(REMOVE_MEMBER_MTD);
+ }
- // Visit result should be immediately returned.
- switchBuilder.addCase(fieldName, invokeVisit(traverseChildMtd, schemaField, fieldDef).retObject());
+ switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), blockCode);
}
- // Default option is to throw "NoSuchElementException(key)".
- switchBuilder.defaultCase(new BytecodeBlock()
- .append(newInstance(NoSuchElementException.class, keyVar))
- .throwObject()
+ // ConfigNode
+ ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+ // tmpStr = ((ConfigNode) oldValue).typeId;
+ // switch(tmpStr) ...
+ removeMembersMtd.getBody()
+ .append(tmpStrVar.set(oldValueVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+ .append(switchBuilder.build())
+ .ret();
+ }
+
+ /**
+ * Adds a {@code DynamicConfiguration#addMembers} override for the polymorphic configuration case.
+ *
+ * @param classDef Definition of a polymorphic configuration class (parent).
+ * @param schemaClass Polymorphic configuration schema (parent).
+ * @param polymorphicExtensions Polymorphic configuration instance schemas (children).
+ * @param fieldDefs Field definitions for all fields of {@code classDef}.
+ * @param polymorphicFields Fields of polymorphic extensions.
+ * @param polymorphicTypeIdFieldDef Identification field for the polymorphic configuration instance.
+ */
+ private void addCfgAddMembersMethod(
+ ClassDefinition classDef,
+ Class<?> schemaClass,
+ Set<Class<?>> polymorphicExtensions,
+ Map<String, FieldDefinition> fieldDefs,
+ Collection<Field> polymorphicFields,
+ FieldDefinition polymorphicTypeIdFieldDef
+ ) {
+ MethodDefinition removeMembersMtd = classDef.declareMethod(
+ of(PUBLIC),
+ "addMembers",
+ type(void.class),
+ arg("newValue", type(Object.class)),
+ arg("members", type(Map.class))
);
- return switchBuilder.build();
+ // InnerNode newValue;
+ Variable newValueVar = removeMembersMtd.getScope().getVariable("newValue");
+
+ // Map members;
+ Variable membersVar = removeMembersMtd.getScope().getVariable("members");
+
+ // String tmpStr;
+ Variable tmpStrVar = removeMembersMtd.getScope().createTempVariable(String.class);
+
+ StringSwitchBuilder switchBuilder = new StringSwitchBuilder(removeMembersMtd.getScope())
+ .expression(tmpStrVar)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, tmpStrVar));
+
+ for (Class<?> polymorphicExtension : polymorphicExtensions) {
+ Collection<Field> addFields = polymorphicFields.stream()
+ .filter(f -> polymorphicExtension.equals(f.getDeclaringClass()))
+ .collect(toList());
+
+ BytecodeBlock blockCode = new BytecodeBlock();
+
+ for (Field addField : addFields) {
+ // this.addMember(members, this.field);
+ blockCode
+ .append(removeMembersMtd.getThis())
+ .append(membersVar)
+ .append(getThisFieldCode(removeMembersMtd, fieldDefs.get(fieldName(addField))))
+ .invokeVirtual(ADD_MEMBER_MTD);
+ }
+
+ switchBuilder.addCase(polymorphicInstanceId(polymorphicExtension), blockCode);
+ }
+
+ // ConfigNode
+ ParameterizedType nodeType = typeFromJavaClassName(schemasInfo.get(schemaClass).nodeClassName);
+
+ // tmpStr = ((ConfigNode) newValue).typeId;
+ // switch(tmpStr) ...
+ removeMembersMtd.getBody()
+ .append(tmpStrVar.set(newValueVar.cast(nodeType).getField(polymorphicTypeIdFieldDef.getName(), String.class)))
+ .append(switchBuilder.build())
+ .ret();
}
/**
- * Created switch bytecode block that invokes of construct methods for
- * {@link InnerNode#construct(String, ConfigurationSource, boolean)}.
+ * Creates bytecode block that invokes of construct methods for
+ * {@link InnerNode#construct(String, ConfigurationSource, boolean)}.
*
- * @param schemaFields Fields of the schema.
- * @param fieldDefs Definitions for all fields in {@code schemaFields}.
* @param constructMtd Method definition {@link InnerNode#construct(String, ConfigurationSource, boolean)}
* defined in {@code *Node} class.
- * @return Created switch bytecode block that invokes of construct methods for fields.
+ * @param schemaField Schema field.
+ * @param schemaFieldDef Schema field definition.
+ * @return Bytecode block that invokes of construct method for field.
*/
- private BytecodeNode treatSourceForConstruct(
- Collection<Field> schemaFields,
- Map<String, FieldDefinition> fieldDefs,
- MethodDefinition constructMtd
+ private BytecodeBlock treatSourceForConstruct(
+ MethodDefinition constructMtd,
+ Field schemaField,
+ FieldDefinition schemaFieldDef
) {
- Variable keyVar = constructMtd.getScope().getVariable("key");
+ BytecodeBlock codeBlock = new BytecodeBlock();
+
+ Variable thisVar = constructMtd.getThis();
Variable srcVar = constructMtd.getScope().getVariable("src");
- StringSwitchBuilder switchBuilder = new StringSwitchBuilder(constructMtd.getScope()).expression(keyVar);
+ if (isValue(schemaField)) {
+ // this.field = src == null ? null : src.unwrap(FieldType.class);
+ codeBlock.append(thisVar.setField(schemaFieldDef, inlineIf(
+ isNull(srcVar),
+ constantNull(schemaFieldDef.getType()),
+ srcVar.invoke(UNWRAP, constantClass(schemaFieldDef.getType())).cast(schemaFieldDef.getType())
+ )));
+ }
+ else if (isConfigValue(schemaField)) {
+ BytecodeNode setField;
- for (Field schemaField : schemaFields) {
- FieldDefinition fieldDef = fieldDefs.get(schemaField.getName());
+ ParameterizedType fieldDefType = schemaFieldDef.getType();
- BytecodeBlock caseClause = new BytecodeBlock();
+ if (isPolymorphicConfig(schemaField.getType())) {
+ Field polymorphicIdField = polymorphicIdField(schemaField.getType());
- switchBuilder.addCase(schemaField.getName(), caseClause);
+ assert polymorphicIdField != null : schemaField.getType().getName();
- // this.field = src == null ? null : src.unwrap(FieldType.class);
- if (isValue(schemaField)) {
- caseClause.append(constructMtd.getThis().setField(fieldDef, inlineIf(
- isNull(srcVar),
- constantNull(fieldDef.getType()),
- srcVar.invoke(UNWRAP, constantClass(fieldDef.getType())).cast(fieldDef.getType())
- )));
- }
- // this.field = src == null ? null : src.descend(field = (field == null ? new FieldType() : field.copy()));
- else if (isConfigValue(schemaField)) {
- caseClause.append(new IfStatement()
- .condition(isNull(srcVar))
- .ifTrue(constructMtd.getThis().setField(fieldDef, constantNull(fieldDef.getType())))
- .ifFalse(new BytecodeBlock()
- .append(copyNodeField(constructMtd, fieldDef))
- .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
- )
- );
+ // this.field;
+ BytecodeExpression thisField = getThisFieldCode(constructMtd, schemaFieldDef);
+
+ // String tmpStr;
+ Variable tmpStrVar = constructMtd.getScope().createTempVariable(String.class);
+
+ // this.field = (FieldType) this.field.copy();
+ // if(tmpStr != null) this.field.changeTypeId(tmpStr);
+ BytecodeBlock copyWithChange = new BytecodeBlock()
+ .append(setThisFieldCode(constructMtd, thisField.invoke(COPY).cast(fieldDefType), schemaFieldDef))
+ .append(new IfStatement()
+ .condition(isNotNull(tmpStrVar))
+ .ifTrue(thisField.invoke(changeMethodName(polymorphicIdField.getName()), void.class, tmpStrVar))
+ );
+
+ // this.field = new FieldType();
+ // if(tmpStr != null) this.field.changeTypeId(tmpStr);
+ // else {
+ // this.field.constructDefault("typeId");
+ // if(this.field.typeId == null) throw new IllegalStateException();
+ // }
+ BytecodeBlock newInstanceWithChange = new BytecodeBlock()
+ .append(setThisFieldCode(constructMtd, newInstance(fieldDefType), schemaFieldDef))
+ .append(new IfStatement()
+ .condition(isNotNull(tmpStrVar))
+ .ifTrue(thisField.invoke(changeMethodName(polymorphicIdField.getName()), void.class, tmpStrVar))
+ .ifFalse(new BytecodeBlock()
+ .append(thisField.invoke(CONSTRUCT_DEFAULT_MTD, constantString(polymorphicIdField.getName())))
+ .append(new IfStatement()
+ .condition(isNull(thisField.getField(polymorphicIdField.getName(), String.class)))
+ .ifTrue(throwException(
+ IllegalStateException.class,
+ constantString("Polymorphic configuration type is not defined: " +
+ polymorphicIdField.getDeclaringClass().getName())
+ ))
+ )
+ )
+ );
+
+ // tmpStr = src.polymorphicTypeId("typeId");
+ // if(this.field == null)
+ setField = new BytecodeBlock()
+ .append(tmpStrVar.set(srcVar.invoke(POLYMORPHIC_TYPE_ID_MTD, constantString(polymorphicIdField.getName()))))
+ .append(new IfStatement()
+ .condition(isNull(thisField))
+ .ifTrue(newInstanceWithChange)
+ .ifFalse(copyWithChange)
+ );
}
- // this.field = src == null ? new NamedListNode<>(key, ValueNode::new) : src.descend(field = field.copy()));
else {
- NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+ // newValue = this.field == null ? new ValueNode() : field.copy());
+ BytecodeExpression newValue = newOrCopyNodeField(schemaField, getThisFieldCode(constructMtd, schemaFieldDef));
- String fieldNodeClassName = schemasInfo.get(schemaField.getType()).nodeClassName;
+ // this.field = newValue;
+ setField = setThisFieldCode(constructMtd, newValue, schemaFieldDef);
+ }
- caseClause.append(new IfStatement()
+ codeBlock.append(
+ new IfStatement()
.condition(isNull(srcVar))
- .ifTrue(constructMtd.getThis().setField(
- fieldDef,
- newInstance(
- NamedListNode.class,
- constantString(namedCfgAnnotation.syntheticKeyName()),
- newNamedListElementLambda(fieldNodeClassName)
- )
- ))
+ .ifTrue(setThisFieldCode(constructMtd, constantNull(fieldDefType), schemaFieldDef))
.ifFalse(new BytecodeBlock()
- .append(constructMtd.getThis().setField(
- fieldDef,
- constructMtd.getThis().getField(fieldDef).invoke(COPY).cast(fieldDef.getType())
- ))
- .append(srcVar.invoke(DESCEND, constructMtd.getThis().getField(fieldDef)))
+ .append(setField)
+ .append(srcVar.invoke(DESCEND, thisVar.getField(schemaFieldDef)))
)
- );
- }
+ );
+ }
+ else {
+ // this.field = src == null ? new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName")
+ // : src.descend(field = field.copy()));
+ codeBlock.append(new IfStatement()
+ .condition(isNull(srcVar))
+ .ifTrue(setThisFieldCode(constructMtd, newNamedListNode(schemaField), schemaFieldDef))
+ .ifFalse(new BytecodeBlock()
+ .append(setThisFieldCode(
+ constructMtd,
+ thisVar.getField(schemaFieldDef).invoke(COPY).cast(schemaFieldDef.getType()),
+ schemaFieldDef
+ )).append(srcVar.invoke(DESCEND, thisVar.getField(schemaFieldDef)))
+ )
+ );
}
- // Default option is to throw "NoSuchElementException(key)".
- switchBuilder.defaultCase(new BytecodeBlock()
- .append(newInstance(NoSuchElementException.class, keyVar))
- .throwObject()
- );
+ return codeBlock;
+ }
+
+ /**
+ * Creates a bytecode block of code that sets the default value for a field from the schema for
+ * {@link InnerNode#constructDefault(String)}.
+ *
+ * @param constructDfltMtd Method definition {@link InnerNode#constructDefault(String)}
+ * defined in {@code *Node} class.
+ * @param schemaField Schema field.
+ * @param schemaFieldDef Schema field definition.
+ * @param specFieldDef Definition of the schema field.: {@code _spec#}.
+ * @return Bytecode block that sets the default value for a field from the schema.
+ */
+ private static BytecodeBlock addNodeConstructDefault(
+ MethodDefinition constructDfltMtd,
+ Field schemaField,
+ FieldDefinition schemaFieldDef,
+ FieldDefinition specFieldDef
+ ) {
+ Variable thisVar = constructDfltMtd.getThis();
+
+ // defaultValue = _spec#.field;
+ BytecodeExpression defaultValue = thisVar.getField(specFieldDef).getField(schemaField);
- return switchBuilder.build();
+ Class<?> schemaFieldType = schemaField.getType();
+
+ // defaultValue = Box.valueOf(defaultValue); // Boxing.
+ if (schemaFieldType.isPrimitive()) {
+ defaultValue = invokeStatic(
+ schemaFieldDef.getType(),
+ "valueOf",
+ schemaFieldDef.getType(),
+ singleton(defaultValue)
+ );
+ }
+
+ // defaultValue = defaultValue.clone();
+ if (schemaFieldType.isArray())
+ defaultValue = defaultValue.invoke("clone", Object.class).cast(schemaFieldType);
+
+ // this.field = defaultValue;
+ return new BytecodeBlock().append(thisVar.setField(schemaFieldDef, defaultValue));
}
/**
- * Get interfaces for {@link InnerNode} definition for a configuration schema.
+ * Creates the bytecode:
+ * <pre><code>
+ * throw new Exception(msg);
+ * </code></pre>
*
- * @param schemaClass Configuration schema class.
- * @param schemaExtensions Internal extensions of the configuration schema.
- * @return Interfaces for {@link InnerNode} definition for a configuration schema.
+ * @param throwableClass Exception class.
+ * @param parameters Exception constructor parameters.
+ * @return Exception throwing bytecode.
*/
- private static ParameterizedType[] nodeClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
- return Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
- .flatMap(cls -> Stream.of(viewClassName(cls), changeClassName(cls)))
- .map(ParameterizedType::typeFromJavaClassName)
- .toArray(ParameterizedType[]::new);
+ private static BytecodeBlock throwException(
+ Class<? extends Throwable> throwableClass,
+ BytecodeExpression... parameters
+ ) {
+ return new BytecodeBlock().append(newInstance(throwableClass, parameters)).throwObject();
}
/**
- * Get interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+ * Returns the name of the configuration schema field.
+ * If the schema contains {@link PolymorphicConfigInstance},
+ * it will return "{@link Field#getName()} + {@code "#"} + {@link PolymorphicConfigInstance#value()}"
+ * otherwise "{@link Field#getName}".
+ *
+ * @param f Configuration schema field.
+ * @return Field name.
+ */
+ private static String fieldName(Field f) {
+ return isPolymorphicConfigInstance(f.getDeclaringClass())
+ ? f.getName() + "#" + polymorphicInstanceId(f.getDeclaringClass()) : f.getName();
+ }
+
+ /**
+ * Creates a string switch builder by {@code typeIdFieldDef}.
+ *
+ * @param mtdDef Method definition.
+ * @param typeIdFieldDef Field definition that contains the id of the polymorphic configuration instance.
+ * @return String switch builder by {@code typeIdFieldDef}.
+ */
+ private static StringSwitchBuilder typeIdSwitchBuilder(MethodDefinition mtdDef, FieldDefinition typeIdFieldDef) {
+ BytecodeExpression typeIdVar = mtdDef.getThis().getField(typeIdFieldDef);
+
+ return new StringSwitchBuilder(mtdDef.getScope())
+ .expression(typeIdVar)
+ .defaultCase(throwException(ConfigurationWrongPolymorphicTypeIdException.class, typeIdVar));
+ }
+
+ /**
+ * Generates bytecode to get a class field like {@code this.field;} or {@code this.field.field;}.
+ *
+ * @param mtdDef Class method definition.
+ * @param fieldDefs Field definitions.
+ * @return Bytecode for getting the field.
+ */
+ private static BytecodeExpression getThisFieldCode(MethodDefinition mtdDef, FieldDefinition... fieldDefs) {
+ assert !nullOrEmpty(fieldDefs);
+
+ // this.field;
+ BytecodeExpression getFieldCode = mtdDef.getThis().getField(fieldDefs[0]);
+
+ // this.field.field; etc.
+ for (int i = 1; i < fieldDefs.length; i++)
+ getFieldCode = getFieldCode.getField(fieldDefs[i]);
+
+ return getFieldCode;
+ }
+
+ /**
+ * Generates bytecode to set a class field like {@code this.field = value;} or {@code this.field.field = value;}.
+ *
+ * @param mtdDef Class method definition.
+ * @param value Value of the field to be set.
+ * @param fieldDefs Field definitions.
+ * @return Bytecode for setting the field value.
+ */
+ private static BytecodeExpression setThisFieldCode(
+ MethodDefinition mtdDef,
+ BytecodeExpression value,
+ FieldDefinition... fieldDefs
+ ) {
+ assert !nullOrEmpty(fieldDefs);
+
+ if (fieldDefs.length == 1) {
+ // this.field = value;
+ return mtdDef.getThis().setField(fieldDefs[0], value);
+ }
+ else {
+ // this.field;
+ BytecodeExpression getFieldCode = mtdDef.getThis().getField(fieldDefs[0]);
+
+ // this.field.field; etc.
+ for (int i = 1; i < fieldDefs.length - 1; i++)
+ getFieldCode = getFieldCode.getField(fieldDefs[i]);
+
+ // this.field.field = value;
+ return getFieldCode.setField(fieldDefs[fieldDefs.length - 1], value);
+ }
+ }
+
+ /**
+ * Returns configuration schema field with {@link PolymorphicId}.
*
* @param schemaClass Configuration schema class.
- * @param schemaExtensions Internal extensions of the configuration schema.
- * @return Interfaces for {@link DynamicConfiguration} definition for a configuration schema.
+ * @return Configuration schema field with {@link PolymorphicId}.
*/
- private ParameterizedType[] configClassInterfaces(Class<?> schemaClass, Set<Class<?>> schemaExtensions) {
- var result = new ArrayList<ParameterizedType>();
+ @Nullable private static Field polymorphicIdField(Class<?> schemaClass) {
+ assert isPolymorphicConfig(schemaClass) : schemaClass.getName();
- Stream.concat(Stream.of(schemaClass), schemaExtensions.stream())
- .map(cls -> typeFromJavaClassName(configurationClassName(cls)))
- .forEach(result::add);
+ for (Field f : schemaClass.getDeclaredFields()) {
+ if (isPolymorphicId(f))
+ return f;
+ }
- if (schemasInfo.get(schemaClass).direct)
- result.add(type(DirectConfigurationProperty.class));
+ return null;
+ }
- return result.toArray(new ParameterizedType[0]);
+ /**
+ * Returns name of the method to change the field.
+ *
+ * @param schemaField Configuration schema field.
+ * @return Name of the method to change the field.
+ */
+ private static String changeMethodName(String schemaField) {
+ return "change" + capitalize(schemaField);
}
/**
- * 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}.
+ * Creates bytecode like: new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
+ *
+ * @param schemaField Schema field with {@link NamedConfigValue}.
+ * @return Bytecode like: new NamedListNode<>(key, ValueNode::new, "polymorphicIdFieldName");
*/
- private void addCfgImplConfigTypeMethod(ClassDefinition classDef, ParameterizedType clazz) {
- classDef.declareMethod(of(PUBLIC), "configType", type(Class.class))
- .getBody()
- .append(constantClass(clazz))
- .retObject();
+ private BytecodeExpression newNamedListNode(Field schemaField) {
+ assert isNamedConfigValue(schemaField) : schemaField;
+
+ Class<?> fieldType = schemaField.getType();
+
+ SchemaClassesInfo fieldClassNames = schemasInfo.get(fieldType);
+
+ NamedConfigValue namedCfgAnnotation = schemaField.getAnnotation(NamedConfigValue.class);
+
+ return newInstance(
+ NamedListNode.class,
+ constantString(namedCfgAnnotation.syntheticKeyName()),
+ newNamedListElementLambda(fieldClassNames.nodeClassName),
+ isPolymorphicConfig(fieldType) ? constantString(polymorphicIdField(fieldType).getName()) : constantNull(String.class)
+ );
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
index ae8359c..cf1584d 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/hocon/HoconObjectConfigurationSource.java
@@ -23,6 +23,7 @@ import java.util.NoSuchElementException;
import com.typesafe.config.ConfigList;
import com.typesafe.config.ConfigObject;
import com.typesafe.config.ConfigValue;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.jetbrains.annotations.Nullable;
@@ -126,6 +127,11 @@ class HoconObjectConfigurationSource implements ConfigurationSource {
);
}
}
+ catch (ConfigurationWrongPolymorphicTypeIdException e) {
+ throw new IllegalArgumentException(
+ "Polymorphic configuration type is not correct: " + e.getMessage()
+ );
+ }
}
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
index 5150385..29cbc3b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConfigurationSource.java
@@ -17,7 +17,11 @@
package org.apache.ignite.internal.configuration.tree;
-/** */
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Configuration source.
+ */
public interface ConfigurationSource {
/**
* Treat current configuration source as a leaf value and try to convert it to the specific class.
@@ -45,4 +49,14 @@ public interface ConfigurationSource {
*/
default void reset() {
}
+
+ /**
+ * Returns the identifier of the polymorphic configuration instance.
+ *
+ * @param fieldName Name of the field in the configuration schema that should store the identifier of the polymorphic configuration instance.
+ * @return Identifier of the polymorphic configuration instance.
+ */
+ @Nullable default String polymorphicTypeId(String fieldName) {
+ return null;
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
index aa68f1e..3f13c2f 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNode.java
@@ -19,7 +19,9 @@ package org.apache.ignite.internal.configuration.tree;
import java.util.NoSuchElementException;
-/** */
+/**
+ * Interface for filling the configuration node.
+ */
public interface ConstructableTreeNode {
/**
* Initializes {@code key} element of the object with the content from the source.
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
index df63a40..d25cb2a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/ConverterToMapVisitor.java
@@ -86,7 +86,7 @@ public class ConverterToMapVisitor implements ConfigurationVisitor<Object> {
deque.push(list);
for (String subkey : node.namedListKeys()) {
- node.get(subkey).accept(subkey, this);
+ node.getInnerNode(subkey).accept(subkey, this);
((Map<String, Object>)list.get(list.size() - 1)).put(node.syntheticKeyName(), subkey);
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
index 058112b..e4711d3 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/InnerNode.java
@@ -19,7 +19,9 @@ package org.apache.ignite.internal.configuration.tree;
import java.util.NoSuchElementException;
-/** */
+/**
+ * Configuration node implementation.
+ */
public abstract class InnerNode implements TraversableTreeNode, ConstructableTreeNode, Cloneable {
/** {@inheritDoc} */
@Override public final <T> T accept(String key, ConfigurationVisitor<T> visitor) {
@@ -173,4 +175,15 @@ public abstract class InnerNode implements TraversableTreeNode, ConstructableTre
throw new IllegalStateException(e);
}
}
+
+ /**
+ * Returns specific {@code Node} of the value.
+ * Overridden for polymorphic configuration to get a specific polymorphic configuration instance.
+ *
+ * @param <NODE> Type of the {@code Node}.
+ * @return Specific {@code Node} of the value.
+ */
+ public <NODE> NODE specificNode() {
+ return (NODE)this;
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
index 2bec423..34e330b 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/tree/NamedListNode.java
@@ -28,47 +28,55 @@ import java.util.function.Consumer;
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.
+ * 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 {@code Node} ({@link InnerNode} or instances of polymorphic configuration)
+ * that is stored in named list node object.
*/
-public final class NamedListNode<N extends InnerNode> implements NamedListChange<N, N>, TraversableTreeNode, ConstructableTreeNode {
+public final class NamedListNode<N> implements NamedListChange<N, N>, TraversableTreeNode, ConstructableTreeNode {
/** Name of a synthetic configuration property that describes the order of elements in a named list. */
public static final String ORDER_IDX = "<order>";
- /**
- * Name of a synthetic configuration property that's used to store "key" of the named list element in the storage.
- */
+ /** Name of a synthetic configuration property that's used to store "key" of the named list element in the storage. */
public static final String NAME = "<name>";
/** Configuration name for the synthetic key. */
private final String syntheticKeyName;
/** Supplier of new node objects when new list element node has to be created. */
- private final Supplier<N> valSupplier;
+ private final Supplier<InnerNode> valSupplier;
- /** Internal container for named list element. Maps keys to named list elements nodes with their internal ids. */
- private final OrderedMap<ElementDescriptor<N>> map;
+ /** Internal container for named list element. Map keys to named list elements nodes with their internal ids. */
+ private final OrderedMap<ElementDescriptor> map;
/** Mapping from internal ids to public keys. */
private final Map<String, String> reverseIdMap;
+ /** Field name with {@link PolymorphicId}. */
+ @Nullable private final String typeIdFieldName;
+
/**
* Default constructor.
*
* @param syntheticKeyName Name of the synthetic configuration value that will represent keys in a specially ordered
* representation syntax.
* @param valSupplier Closure to instantiate values.
+ * @param typeIdFieldName Field name with {@link PolymorphicId}.
*/
- public NamedListNode(String syntheticKeyName, Supplier<N> valSupplier) {
+ public NamedListNode(String syntheticKeyName, Supplier<InnerNode> valSupplier, @Nullable String typeIdFieldName) {
this.syntheticKeyName = syntheticKeyName;
this.valSupplier = valSupplier;
+ this.typeIdFieldName = typeIdFieldName;
map = new OrderedMap<>();
reverseIdMap = new HashMap<>();
}
@@ -81,6 +89,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
private NamedListNode(NamedListNode<N> node) {
syntheticKeyName = node.syntheticKeyName;
valSupplier = node.valSupplier;
+ typeIdFieldName = node.typeIdFieldName;
map = new OrderedMap<>();
reverseIdMap = new HashMap<>(node.reverseIdMap);
@@ -94,20 +103,28 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
}
/** {@inheritDoc} */
- @Override public final List<String> namedListKeys() {
+ @Override public List<String> namedListKeys() {
return Collections.unmodifiableList(map.keys());
}
/** {@inheritDoc} */
- @Override public final N get(String key) {
- ElementDescriptor<N> element = map.get(key);
-
- return element == null ? null : element.value;
+ @Override public N get(String key) {
+ return specificNode(map.get(key));
}
/** {@inheritDoc} */
@Override public N get(int index) throws IndexOutOfBoundsException {
- ElementDescriptor<N> element = map.get(index);
+ return specificNode(map.get(index));
+ }
+
+ /**
+ * Returns {@link InnerNode} associated with the passed key.
+ *
+ * @param key Key string.
+ * @return Requested value.
+ */
+ @Nullable public InnerNode getInnerNode(String key) {
+ ElementDescriptor element = map.get(key);
return element == null ? null : element.value;
}
@@ -124,13 +141,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
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);
return this;
}
@@ -145,13 +162,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
checkNewKey(key);
- ElementDescriptor<N> element = newElementDescriptor();
+ ElementDescriptor element = newElementDescriptor();
map.putByIndex(index, key, element);
reverseIdMap.put(element.internalId, key);
- valConsumer.accept(element.value);
+ valConsumer.accept((N)element.value);
return this;
}
@@ -167,26 +184,26 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
checkNewKey(key);
- ElementDescriptor<N> element = newElementDescriptor();
+ ElementDescriptor element = newElementDescriptor();
map.putAfter(precedingKey, key, element);
reverseIdMap.put(element.internalId, key);
- valConsumer.accept(element.value);
+ valConsumer.accept((N)element.value);
return this;
}
/** {@inheritDoc} */
- @Override public final NamedListChange<N, N> createOrUpdate(String key, Consumer<N> valConsumer) {
+ @Override public NamedListChange<N, N> createOrUpdate(String key, Consumer<N> valConsumer) {
Objects.requireNonNull(key, "key");
Objects.requireNonNull(valConsumer, "valConsumer");
if (map.containsKey(key) && map.get(key).value == null)
throw new IllegalArgumentException("You can't create entity that has just been deleted [key=" + key + ']');
- ElementDescriptor<N> element = map.get(key);
+ ElementDescriptor element = map.get(key);
if (element == null) {
element = newElementDescriptor();
@@ -198,7 +215,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
map.put(key, element);
- valConsumer.accept(element.value);
+ valConsumer.accept((N)element.value);
return this;
}
@@ -211,7 +228,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
if (!map.containsKey(oldKey))
throw new IllegalArgumentException("Element with name " + oldKey + " does not exist.");
- ElementDescriptor<N> element = map.get(oldKey);
+ ElementDescriptor element = map.get(oldKey);
if (element.value == null) {
throw new IllegalArgumentException(
@@ -230,6 +247,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/**
* Checks that this new key can be inserted into the map.
+ *
* @param key New key.
* @throws IllegalArgumentException If key already exists.
*/
@@ -263,13 +281,13 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/**
* Sets an internal id for the value associated with the passed key. Should not be used in arbitrary code. Refer
- * to {@link ConfigurationUtil#fillFromPrefixMap(ConstructableTreeNode, Map)} for further details on the usage.
+ * to {@link ConfigurationUtil#fillFromPrefixMap} for further details on the usage.
*
* @param key Key to update. Should be present in the named list. Nothing will happen if the key is missing.
* @param internalId New id to associate with the key.
*/
public void setInternalId(String key, String internalId) {
- ElementDescriptor<N> element = map.get(key);
+ ElementDescriptor element = map.get(key);
if (element != null) {
reverseIdMap.remove(element.internalId);
@@ -289,7 +307,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
* @throws IllegalArgumentException If {@code key} is not found in the named list.
*/
public String internalId(String key) {
- ElementDescriptor<N> element = map.get(key);
+ ElementDescriptor element = map.get(key);
if (element == null)
throw new IllegalArgumentException("Element with name '" + key + "' does not exist.");
@@ -322,7 +340,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
* @param key Element's key.
*/
public void forceDelete(String key) {
- ElementDescriptor<N> removed = map.remove(key);
+ ElementDescriptor removed = map.remove(key);
if (removed != null)
reverseIdMap.remove(removed.internalId);
@@ -339,10 +357,51 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
/** {@inheritDoc} */
@Override public void construct(String key, ConfigurationSource src, boolean includeInternal) {
+ Objects.requireNonNull(key, "key");
+
if (src == null)
delete(key);
- else
- createOrUpdate(key, src::descend);
+ else {
+ if (map.containsKey(key) && map.get(key).value == null)
+ throw new IllegalArgumentException("You can't create entity that has just been deleted [key=" + key + ']');
+
+ ElementDescriptor element = map.get(key);
+
+ if (element == null) {
+ element = newElementDescriptor();
+
+ reverseIdMap.put(element.internalId, key);
+
+ if (typeIdFieldName != null) {
+ InnerNode polymorphicInnerNode = element.value;
+
+ String polymorphicTypeId = src.polymorphicTypeId(typeIdFieldName);
+
+ if (polymorphicTypeId != null)
+ polymorphicInnerNode.construct(typeIdFieldName, new LeafConfigurationSource(polymorphicTypeId), true);
+ else if (polymorphicInnerNode.traverseChild(typeIdFieldName, leafNodeVisitor(), true) == null) {
+ throw new IllegalStateException("Polymorphic configuration type is not defined: " +
+ polymorphicInnerNode.getClass().getName());
+ }
+ }
+ }
+ else {
+ element = element.copy();
+
+ if (typeIdFieldName != null) {
+ InnerNode polymorphicInnerNode = element.value;
+
+ String polymorphicTypeId = src.polymorphicTypeId(typeIdFieldName);
+
+ if (polymorphicTypeId != null)
+ polymorphicInnerNode.construct(typeIdFieldName, new LeafConfigurationSource(polymorphicTypeId), true);
+ }
+ }
+
+ map.put(key, element);
+
+ src.descend(element.value);
+ }
}
/** {@inheritDoc} */
@@ -355,32 +414,30 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
*
* @return New element instance with initialized defaults.
*/
- private NamedListNode.ElementDescriptor<N> newElementDescriptor() {
- N newElement = valSupplier.get();
+ private NamedListNode.ElementDescriptor newElementDescriptor() {
+ InnerNode newElement = valSupplier.get();
addDefaults(newElement);
- return new ElementDescriptor<>(newElement);
+ return new ElementDescriptor(newElement);
}
/**
* Descriptor for internal named list element representation. Has node itself and its internal id.
- *
- * @param <N> Type of the node.
*/
- private static class ElementDescriptor<N extends InnerNode> {
+ private static class ElementDescriptor {
/** Element's internal id. */
public String internalId;
/** Element node value. */
- public N value;
+ @Nullable public InnerNode value;
/**
* Constructor.
*
* @param value Node instance.
*/
- ElementDescriptor(N value) {
+ ElementDescriptor(InnerNode value) {
this.value = value;
// Remove dashes so that id would be a bit shorter and easier to validate in tests.
// This string won't be visible by end users anyway.
@@ -393,7 +450,7 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
* @param internalId Internal id.
* @param value Node instance.
*/
- private ElementDescriptor(String internalId, N value) {
+ private ElementDescriptor(String internalId, InnerNode value) {
this.internalId = internalId;
this.value = value;
}
@@ -404,8 +461,8 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
* @return New instance with the same internal id but copied node instance.
* @see InnerNode#copy()
*/
- public ElementDescriptor<N> copy() {
- return new ElementDescriptor<>(internalId, (N)value.copy());
+ public ElementDescriptor copy() {
+ return new ElementDescriptor(internalId, value.copy());
}
/**
@@ -413,8 +470,23 @@ public final class NamedListNode<N extends InnerNode> implements NamedListChange
*
* @return New instance with the same internal id and node instance.
*/
- public ElementDescriptor<N> shallowCopy() {
- return new ElementDescriptor<>(internalId, value);
+ public ElementDescriptor shallowCopy() {
+ return new ElementDescriptor(internalId, value);
}
}
+
+ /**
+ * Returns specific {@code Node}.
+ *
+ * @param element Internal named list element representation.
+ * @return Specific {@code Node}.
+ */
+ @Nullable private N specificNode(@Nullable ElementDescriptor element) {
+ if (element == null)
+ return null;
+
+ InnerNode value = element.value;
+
+ return value == null ? null : value.specificNode();
+ }
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
index c8e359d..531672a 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationFlattener.java
@@ -47,7 +47,7 @@ public class ConfigurationFlattener {
oldInnerNodesStack.push(curRoot);
- // Explicit access to the children of super root guarantees that "oldInnerNodesStack" is never empty and thus
+ // Explicit access to the children of super root guarantees that "oldInnerNodesStack" is never empty, and thus
// we don't need null-checks when calling Deque#peek().
updates.traverseChildren(new FlattenerVisitor(oldInnerNodesStack, resMap), true);
@@ -66,7 +66,7 @@ public class ConfigurationFlattener {
int idx = 0;
for (String key : node.namedListKeys()) {
- if (node.get(key) != null)
+ if (node.getInnerNode(key) != null)
res.put(key, idx++);
}
@@ -92,6 +92,12 @@ public class ConfigurationFlattener {
*/
private boolean deletion;
+ /**
+ * Constructor.
+ *
+ * @param oldInnerNodesStack Old nodes stack for recursion.
+ * @param resMap Map with the result.
+ */
FlattenerVisitor(Deque<InnerNode> oldInnerNodesStack, Map<String, Serializable> resMap) {
this.oldInnerNodesStack = oldInnerNodesStack;
this.resMap = resMap;
@@ -120,6 +126,14 @@ public class ConfigurationFlattener {
if (oldNode == null)
visitAsymmetricInnerNode(newNode, false);
+ else if (oldNode.schemaType() != newNode.schemaType()) {
+ // At the moment, we do not separate the general fields from the fields of
+ // specific instances of the polymorphic configuration, so we will assume
+ // that all the fields have changed, perhaps we will fix this later.
+ visitAsymmetricInnerNode(oldNode, true);
+
+ visitAsymmetricInnerNode(newNode, false);
+ }
else {
oldInnerNodesStack.push(oldNode);
@@ -132,7 +146,7 @@ public class ConfigurationFlattener {
}
/** {@inheritDoc} */
- @Override public <N extends InnerNode> Void doVisitNamedListNode(String key, NamedListNode<N> newNode) {
+ @Override public Void doVisitNamedListNode(String key, NamedListNode<?> newNode) {
// Read same named list node from old tree.
NamedListNode<?> oldNode = oldInnerNodesStack.peek().traverseChild(key, ConfigurationUtil.namedListNodeVisitor(), true);
@@ -152,10 +166,10 @@ public class ConfigurationFlattener {
String newNodeInternalId = newNode.internalId(newNodeKey);
withTracking(newNodeInternalId, false, false, () -> {
- InnerNode newNamedElement = newNode.get(newNodeKey);
+ InnerNode newNamedElement = newNode.getInnerNode(newNodeKey);
String oldNodeKey = oldNode.keyByInternalId(newNodeInternalId);
- InnerNode oldNamedElement = oldNode.get(oldNodeKey);
+ InnerNode oldNamedElement = oldNode.getInnerNode(oldNodeKey);
// Deletion of nonexistent element.
if (oldNamedElement == null && newNamedElement == null)
@@ -168,6 +182,14 @@ public class ConfigurationFlattener {
visitAsymmetricInnerNode(oldNamedElement, true);
else if (oldNamedElement == null)
visitAsymmetricInnerNode(newNamedElement, false);
+ else if (newNamedElement.schemaType() != oldNamedElement.schemaType()) {
+ // At the moment, we do not separate the general fields from the fields of
+ // specific instances of the polymorphic configuration, so we will assume
+ // that all the fields have changed, perhaps we will fix this later.
+ visitAsymmetricInnerNode(oldNamedElement, true);
+
+ visitAsymmetricInnerNode(newNamedElement, false);
+ }
else {
oldInnerNodesStack.push(oldNamedElement);
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
index 1a1c3f2..836b600 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationNotificationsUtil.java
@@ -115,6 +115,12 @@ public class ConfigurationNotificationsUtil {
eventConfigs
);
+ // Polymorphic configuration type has changed.
+ // At the moment, we do not separate common fields from fields of a specific polymorphic configuration,
+ // so this may cause errors in the logic below, perhaps we will fix it later.
+ if (oldInnerNode.schemaType() != newInnerNode.schemaType())
+ return;
+
oldInnerNode.traverseChildren(new ConfigurationVisitor<Void>() {
/** {@inheritDoc} */
@Override public Void visitLeafNode(String key, Serializable oldLeaf) {
@@ -238,7 +244,7 @@ public class ConfigurationNotificationsUtil {
eventConfigs.put(newNodeCfg.configType(), new ConfigurationContainer(name, newNodeCfg));
- InnerNode newVal = newNamedList.get(name);
+ InnerNode newVal = newNamedList.getInnerNode(name);
for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
notifyPublicListeners(
@@ -287,7 +293,7 @@ public class ConfigurationNotificationsUtil {
eventConfigs.put(delNodeCfg.configType(), new ConfigurationContainer(name, null));
- InnerNode oldVal = oldNamedList.get(name);
+ InnerNode oldVal = oldNamedList.getInnerNode(name);
for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
notifyPublicListeners(
@@ -347,8 +353,8 @@ public class ConfigurationNotificationsUtil {
new ConfigurationContainer(entry.getValue(), renNodeCfg)
);
- InnerNode oldVal = oldNamedList.get(entry.getKey());
- InnerNode newVal = newNamedList.get(entry.getValue());
+ InnerNode oldVal = oldNamedList.getInnerNode(entry.getKey());
+ InnerNode newVal = newNamedList.getInnerNode(entry.getValue());
for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
notifyPublicListeners(
@@ -379,8 +385,8 @@ public class ConfigurationNotificationsUtil {
updated.retainAll(oldNames);
for (String name : updated) {
- InnerNode oldVal = oldNamedList.get(name);
- InnerNode newVal = newNamedList.get(name);
+ InnerNode oldVal = oldNamedList.getInnerNode(name);
+ InnerNode newVal = newNamedList.getInnerNode(name);
if (oldVal == newVal)
continue;
@@ -594,7 +600,7 @@ public class ConfigurationNotificationsUtil {
configs.put(newNodeCfg.configType(), new ConfigurationContainer(name, newNodeCfg));
- InnerNode newVal = newNamedList.get(name);
+ InnerNode newVal = newNamedList.getInnerNode(name);
for (DynamicConfiguration<InnerNode, ?> anyConfig : anyConfigs) {
notifyPublicListeners(
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
index 7efc849..90ab341 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/ConfigurationUtil.java
@@ -18,6 +18,7 @@
package org.apache.ignite.internal.configuration.util;
import java.io.Serializable;
+import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayDeque;
import java.util.ArrayList;
@@ -25,6 +26,8 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@@ -34,6 +37,7 @@ import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.apache.ignite.configuration.ConfigurationProperty;
+import org.apache.ignite.configuration.ConfigurationWrongPolymorphicTypeIdException;
import org.apache.ignite.configuration.DirectConfigurationProperty;
import org.apache.ignite.configuration.NamedListView;
import org.apache.ignite.configuration.RootKey;
@@ -43,6 +47,9 @@ import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.DirectAccess;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.ConfigurationSource;
@@ -51,8 +58,11 @@ import org.apache.ignite.internal.configuration.tree.ConstructableTreeNode;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
+import org.jetbrains.annotations.Nullable;
+import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
/** */
public class ConfigurationUtil {
@@ -116,12 +126,13 @@ public class ConfigurationUtil {
* @param includeInternal Include internal configuration nodes (private configuration extensions).
* @return Either {@link TraversableTreeNode} or {@link Serializable} depending on the keys and schema.
* @throws KeyNotFoundException If node is not found.
+ * @throws WrongPolymorphicTypeIdException If the type of the polymorphic configuration instance is not correct (unknown).
*/
public static <T> T find(
List<String> keys,
TraversableTreeNode node,
boolean includeInternal
- ) throws KeyNotFoundException {
+ ) throws KeyNotFoundException, WrongPolymorphicTypeIdException {
assert keys instanceof RandomAccess : keys.getClass();
var visitor = new ConfigurationVisitor<T>() {
@@ -151,6 +162,12 @@ public class ConfigurationUtil {
"Configuration value '" + join(keys.subList(0, i)) + "' has not been found"
);
}
+ catch (ConfigurationWrongPolymorphicTypeIdException e) {
+ throw new WrongPolymorphicTypeIdException(
+ "Polymorphic configuration type is not correct: " + e.getMessage(),
+ e
+ );
+ }
}
}
@@ -161,7 +178,7 @@ public class ConfigurationUtil {
else {
String name = keys.get(i++);
- return visitInnerNode(name, node.get(name));
+ return visitInnerNode(name, node.getInnerNode(name));
}
}
};
@@ -235,178 +252,8 @@ public class ConfigurationUtil {
* @throws UnsupportedOperationException if prefix map structure doesn't correspond to actual tree structure.
* This will be fixed when method is actually used in configuration storage intergration.
*/
- public static void fillFromPrefixMap(ConstructableTreeNode node, Map<String, ?> prefixMap) {
- assert node instanceof InnerNode;
-
- /** */
- class LeafConfigurationSource implements ConfigurationSource {
- /** */
- private final Serializable val;
-
- /**
- * @param val Value.
- */
- private LeafConfigurationSource(Serializable val) {
- this.val = val;
- }
-
- /** {@inheritDoc} */
- @Override public <T> T unwrap(Class<T> clazz) {
- assert val == null || clazz.isInstance(val);
-
- return clazz.cast(val);
- }
-
- /** {@inheritDoc} */
- @Override public void descend(ConstructableTreeNode node) {
- throw new UnsupportedOperationException("descend");
- }
- }
-
- /** */
- class InnerConfigurationSource implements ConfigurationSource {
- /** */
- private final Map<String, ?> map;
-
- /**
- * @param map Prefix map.
- */
- private InnerConfigurationSource(Map<String, ?> map) {
- this.map = map;
- }
-
- /** {@inheritDoc} */
- @Override public <T> T unwrap(Class<T> clazz) {
- throw new UnsupportedOperationException("unwrap");
- }
-
- /** {@inheritDoc} */
- @Override public void descend(ConstructableTreeNode node) {
- if (node instanceof NamedListNode) {
- descendToNamedListNode((NamedListNode<?>)node);
-
- return;
- }
-
- for (Map.Entry<String, ?> entry : map.entrySet()) {
- String key = entry.getKey();
- Object val = entry.getValue();
-
- assert val == null || val instanceof Map || val instanceof Serializable;
-
- // Ordering of indexes must be skipped here because they make no sense in this context.
- if (key.equals(NamedListNode.ORDER_IDX) || key.equals(NamedListNode.NAME))
- continue;
-
- if (val == null)
- node.construct(key, null, true);
- else if (val instanceof Map)
- node.construct(key, new InnerConfigurationSource((Map<String, ?>)val), true);
- else {
- assert val instanceof Serializable;
-
- node.construct(key, new LeafConfigurationSource((Serializable)val), true);
- }
- }
- }
-
- /**
- * Specific implementation of {@link #descend(ConstructableTreeNode)} that descends into named list node and
- * sets a proper ordering to named list elements.
- *
- * @param node Named list node under construction.
- */
- private void descendToNamedListNode(NamedListNode<?> node) {
- // This list must be mutable and RandomAccess.
- var orderedKeys = new ArrayList<>(((NamedListView<?>)node).namedListKeys());
-
- for (Map.Entry<String, ?> entry : map.entrySet()) {
- String internalId = entry.getKey();
- Object val = entry.getValue();
-
- assert val == null || val instanceof Map || val instanceof Serializable;
-
- String oldKey = node.keyByInternalId(internalId);
-
- if (val == null) {
- // Given that this particular method is applied to modify existing trees rather than
- // creating new trees, a "hack" is required in this place. "construct" is designed to create
- // "change" objects, thus it would just nullify named list element instead of deleting it.
- node.forceDelete(oldKey);
- }
- else if (val instanceof Map) {
- Map<String, ?> map = (Map<String, ?>)val;
- int sizeDiff = 0;
-
- // For every named list entry modification we must take its index into account.
- // We do this by modifying "orderedKeys" when index is explicitly passed.
- Object idxObj = map.get(NamedListNode.ORDER_IDX);
-
- if (idxObj != null)
- sizeDiff++;
-
- String newKey = (String)map.get(NamedListNode.NAME);
-
- if (newKey != null)
- sizeDiff++;
-
- boolean construct = map.size() != sizeDiff;
-
- if (oldKey == null) {
- node.construct(newKey, new InnerConfigurationSource(map), true);
-
- node.setInternalId(newKey, internalId);
- }
- else if (newKey != null) {
- node.rename(oldKey, newKey);
-
- if (construct)
- node.construct(newKey, new InnerConfigurationSource(map), true);
- }
- else if (construct)
- node.construct(oldKey, new InnerConfigurationSource(map), true);
- // Else it's just index adjustment after new elements insertion.
-
- if (newKey == null)
- newKey = oldKey;
-
- if (idxObj != null) {
- assert idxObj instanceof Integer : val;
-
- int idx = (Integer)idxObj;
-
- if (idx >= orderedKeys.size()) {
- // Updates can come in arbitrary order. This means that array may be too small
- // during batch creation. In this case we have to insert enough nulls before
- // invoking "add" method for actual key.
- orderedKeys.ensureCapacity(idx + 1);
-
- while (idx != orderedKeys.size())
- orderedKeys.add(null);
-
- orderedKeys.add(newKey);
- }
- else
- orderedKeys.set(idx, newKey);
- }
- }
- else {
- assert val instanceof Serializable;
-
- node.construct(oldKey, new LeafConfigurationSource((Serializable)val), true);
- }
- }
-
- node.reorderKeys(orderedKeys.size() > node.size()
- ? orderedKeys.subList(0, node.size())
- : orderedKeys
- );
- }
- }
-
- var src = new InnerConfigurationSource(prefixMap);
-
- src.descend(node);
+ public static void fillFromPrefixMap(InnerNode node, Map<String, ?> prefixMap) {
+ new InnerConfigurationSource(prefixMap).descend(node);
}
/**
@@ -436,7 +283,7 @@ public class ConfigurationUtil {
node.traverseChildren(new ConfigurationVisitor<>() {
/** {@inheritDoc} */
@Override public Object visitLeafNode(String key, Serializable val) {
- // If source value is null then inititalise the same value on the destination node.
+ // If source value is null then initialise the same value on the destination node.
if (val == null)
node.constructDefault(key);
@@ -464,11 +311,11 @@ public class ConfigurationUtil {
namedList = node.traverseChild(key, namedListNodeVisitor(), true);
for (String namedListKey : namedList.namedListKeys()) {
- if (namedList.get(namedListKey) != null) {
+ if (namedList.getInnerNode(namedListKey) != null) {
// Copy the element.
namedList.construct(namedListKey, EMPTY_CFG_SRC, true);
- addDefaults(namedList.get(namedListKey));
+ addDefaults(namedList.getInnerNode(namedListKey));
}
}
@@ -492,7 +339,7 @@ public class ConfigurationUtil {
@Override public Object visitNamedListNode(String key, NamedListNode<?> namedList) {
for (String namedListKey : namedList.namedListKeys()) {
- InnerNode element = namedList.get(namedListKey);
+ InnerNode element = namedList.getInnerNode(namedListKey);
if (element == null)
namedList.forceDelete(namedListKey);
@@ -554,35 +401,6 @@ public class ConfigurationUtil {
}
/**
- * Get and check schemas and their extensions.
- *
- * @param extensions Schema extensions with {@link InternalConfiguration}.
- * @return Internal schema extensions. Mapping: original of the scheme -> internal extensions.
- * @throws IllegalArgumentException If the schema or its extensions are not valid.
- */
- public static Map<Class<?>, Set<Class<?>>> internalSchemaExtensions(Collection<Class<?>> extensions) {
- if (extensions.isEmpty())
- return Map.of();
- else {
- Map<Class<?>, Set<Class<?>>> res = new HashMap<>();
-
- for (Class<?> extension : extensions) {
- if (!extension.isAnnotationPresent(InternalConfiguration.class)) {
- throw new IllegalArgumentException(String.format(
- "Extension should contain @%s: %s",
- InternalConfiguration.class.getSimpleName(),
- extension.getName()
- ));
- }
- else
- res.computeIfAbsent(extension.getSuperclass(), cls -> new HashSet<>()).add(extension);
- }
-
- return res;
- }
- }
-
- /**
* Checks whether configuration schema field represents primitive configuration value.
*
* @param schemaField Configuration Schema class field.
@@ -637,125 +455,431 @@ public class ConfigurationUtil {
}
/**
- * Get the default value of a {@link Value}.
+ * Collect all configuration schemas with {@link ConfigurationRoot}, {@link Config} or {@link PolymorphicConfig}
+ * including all sub configuration schemas for fields with {@link ConfigValue} or {@link NamedConfigValue}.
*
- * @param field Configuration Schema class field.
- * @return Default value.
+ * @param schemaClasses Configuration schemas (starting points) with {@link ConfigurationRoot}, {@link Config}
+ * or {@link PolymorphicConfig}.
+ * @return All configuration schemas with {@link ConfigurationRoot}, {@link Config}, or {@link PolymorphicConfig}.
+ * @throws IllegalArgumentException If the configuration schemas does not contain
+ * {@link ConfigurationRoot}, {@link Config} or {@link PolymorphicConfig}.
*/
- public static Object defaultValue(Field field) {
- assert hasDefault(field) : field;
+ public static Set<Class<?>> collectSchemas(Collection<Class<?>> schemaClasses) {
+ if (schemaClasses.isEmpty())
+ return Set.of();
- try {
- Object o = field.getDeclaringClass().getDeclaredConstructor().newInstance();
+ Set<Class<?>> res = new HashSet<>();
- field.setAccessible(true);
+ Queue<Class<?>> queue = new ArrayDeque<>(Set.copyOf(schemaClasses));
- return field.get(o);
- }
- catch (ReflectiveOperationException e) {
- throw new RuntimeException(e);
+ while (!queue.isEmpty()) {
+ Class<?> cls = queue.poll();
+
+ if (!cls.isAnnotationPresent(ConfigurationRoot.class) && !cls.isAnnotationPresent(Config.class) &&
+ !cls.isAnnotationPresent(PolymorphicConfig.class)) {
+ throw new IllegalArgumentException(String.format(
+ "Configuration schema must contain @%s or @%s or @%s: %s",
+ ConfigurationRoot.class.getSimpleName(),
+ Config.class.getSimpleName(),
+ PolymorphicConfig.class.getSimpleName(),
+ cls.getName()
+ ));
+ }
+ else {
+ res.add(cls);
+
+ for (Field f : cls.getDeclaredFields()) {
+ if ((f.isAnnotationPresent(ConfigValue.class) || f.isAnnotationPresent(NamedConfigValue.class))
+ && !res.contains(f.getType()))
+ queue.add(f.getType());
+ }
+ }
}
+
+ return res;
+ }
+
+ /**
+ * Get the class names of the fields.
+ *
+ * @param fields Fields.
+ * @return Fields class names.
+ */
+ public static List<String> classNames(Field... fields) {
+ return Stream.of(fields).map(Field::getDeclaringClass).map(Class::getName).collect(toList());
+ }
+
+ /**
+ * Extracts the "direct" value from the given property.
+ *
+ * @param property Property to get the value from.
+ * @return "direct" value of the property.
+ * @throws ClassCastException if the property has not been annotated with {@link DirectAccess}.
+ *
+ * @see DirectAccess
+ * @see DirectConfigurationProperty
+ */
+ public static <T> T directValue(ConfigurationProperty<T> property) {
+ return ((DirectConfigurationProperty<T>)property).directValue();
+ }
+
+ /**
+ * Get configuration schemas and their validated internal extensions.
+ *
+ * @param extensions Schema extensions with {@link InternalConfiguration}.
+ * @return Mapping: original of the scheme -> internal schema extensions.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ * @see InternalConfiguration
+ */
+ public static Map<Class<?>, Set<Class<?>>> internalSchemaExtensions(Collection<Class<?>> extensions) {
+ return schemaExtensions(extensions, InternalConfiguration.class);
+ }
+
+ /**
+ * Get polymorphic extensions of configuration schemas.
+ *
+ * @param extensions Schema extensions with {@link PolymorphicConfigInstance}.
+ * @return Mapping: polymorphic scheme -> extensions (instances) of polymorphic configuration.
+ * @throws IllegalArgumentException If the schema extension is invalid.
+ * @see PolymorphicConfig
+ * @see PolymorphicConfigInstance
+ */
+ public static Map<Class<?>, Set<Class<?>>> polymorphicSchemaExtensions(Collection<Class<?>> extensions) {
+ return schemaExtensions(extensions, PolymorphicConfigInstance.class);
}
/**
- * Get merged schema extension fields.
+ * Get configuration schemas and their validated extensions.
+ * Configuration schema is the parent class of the extension.
*
- * @param extensions Configuration schema extensions ({@link InternalConfiguration}).
- * @return Unique fields of the schema and its extensions.
- * @throws IllegalArgumentException If there is a conflict in field names.
+ * @param extensions Schema extensions.
+ * @param annotationClass Annotation class that the extension should have.
+ * @return Mapping: original of the scheme -> schema extensions.
+ * @throws IllegalArgumentException If the schema extension is invalid.
*/
- public static Set<Field> extensionsFields(Collection<Class<?>> extensions) {
+ private static Map<Class<?>, Set<Class<?>>> schemaExtensions(
+ Collection<Class<?>> extensions,
+ Class<? extends Annotation> annotationClass
+ ) {
if (extensions.isEmpty())
- return Set.of();
- else {
- Map<String, Field> res = new HashMap<>();
+ return Map.of();
+
+ Map<Class<?>, Set<Class<?>>> res = new HashMap<>();
+
+ for (Class<?> extension : extensions) {
+ if (!extension.isAnnotationPresent(annotationClass)) {
+ throw new IllegalArgumentException(String.format(
+ "Extension should contain @%s: %s",
+ annotationClass.getSimpleName(),
+ extension.getName()
+ ));
+ }
+ else
+ res.computeIfAbsent(extension.getSuperclass(), cls -> new HashSet<>()).add(extension);
+ }
- for (Class<?> extension : extensions) {
- assert extension.isAnnotationPresent(InternalConfiguration.class) : extension;
+ return res;
+ }
- for (Field field : extension.getDeclaredFields()) {
- String fieldName = field.getName();
+ /**
+ * Collects fields of configuration schema that contain {@link Value}, {@link ConfigValue},
+ * {@link NamedConfigValue} or {@link PolymorphicId}.
+ *
+ * @param schemaClass Configuration schema class.
+ * @return Schema fields.
+ */
+ public static List<Field> schemaFields(Class<?> schemaClass) {
+ return Arrays.stream(schemaClass.getDeclaredFields())
+ .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+ .collect(toList());
+ }
- if (res.containsKey(fieldName)) {
+ /**
+ * Collects fields of configuration schema extensions that contain {@link Value}, {@link ConfigValue},
+ * {@link NamedConfigValue} or {@link PolymorphicId}.
+ *
+ * @param extensions Configuration schema extensions.
+ * @param uniqueByName Checking the uniqueness of fields by {@link Field#getName name}.
+ * @return Schema extensions fields.
+ * @throws IllegalArgumentException If there was a field name conflict for {@code uniqueByName == true}.
+ */
+ public static Collection<Field> extensionsFields(Collection<Class<?>> extensions, boolean uniqueByName) {
+ if (extensions.isEmpty())
+ return List.of();
+
+ if (uniqueByName) {
+ return extensions.stream()
+ .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
+ .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+ .collect(toMap(
+ Field::getName,
+ identity(),
+ (f1, f2) -> {
throw new IllegalArgumentException(String.format(
"Duplicate field names are not allowed [field=%s, classes=%s]",
- field,
- classNames(res.get(fieldName), field)
+ f1.getName(),
+ classNames(f1, f2)
));
- }
- else
- res.put(fieldName, field);
- }
- }
-
- return Set.copyOf(res.values());
+ },
+ LinkedHashMap::new
+ )).values();
+ }
+ else {
+ return extensions.stream()
+ .flatMap(cls -> Arrays.stream(cls.getDeclaredFields()))
+ .filter(f -> isValue(f) || isConfigValue(f) || isNamedConfigValue(f) || isPolymorphicId(f))
+ .collect(toList());
}
}
/**
- * Collect all configuration schemes with {@link ConfigurationRoot} or {@link Config}
- * including all sub configuration schemes for fields with {@link ConfigValue} or {@link NamedConfigValue}.
+ * Checks whether configuration schema field contains {@link PolymorphicId}.
*
- * @param schemaClasses Configuration schemas (starting points) with {@link ConfigurationRoot} or {@link Config}.
- * @return All configuration schemes with {@link ConfigurationRoot} or {@link Config}.
- * @throws IllegalArgumentException If the configuration schemas does not contain
- * {@link ConfigurationRoot} or {@link Config}.
+ * @param schemaField Configuration schema class field.
+ * @return {@code true} if the field contains {@link PolymorphicId}.
*/
- public static Set<Class<?>> collectSchemas(Collection<Class<?>> schemaClasses) {
- if (schemaClasses.isEmpty())
- return Set.of();
- else {
- Set<Class<?>> res = new HashSet<>();
+ public static boolean isPolymorphicId(Field schemaField) {
+ return schemaField.isAnnotationPresent(PolymorphicId.class);
+ }
- Queue<Class<?>> queue = new ArrayDeque<>(Set.copyOf(schemaClasses));
+ /**
+ * Checks whether configuration schema contains {@link PolymorphicConfig}.
+ *
+ * @param schemaClass Configuration schema class.
+ * @return {@code true} if the schema contains {@link PolymorphicConfig}.
+ */
+ public static boolean isPolymorphicConfig(Class<?> schemaClass) {
+ return schemaClass.isAnnotationPresent(PolymorphicConfig.class);
+ }
- while (!queue.isEmpty()) {
- Class<?> cls = queue.poll();
+ /**
+ * Checks whether configuration schema contains {@link PolymorphicConfigInstance}.
+ *
+ * @param schemaClass Configuration schema class.
+ * @return {@code true} if the schema contains {@link PolymorphicConfigInstance}.
+ */
+ public static boolean isPolymorphicConfigInstance(Class<?> schemaClass) {
+ return schemaClass.isAnnotationPresent(PolymorphicConfigInstance.class);
+ }
- if (!cls.isAnnotationPresent(ConfigurationRoot.class) && !cls.isAnnotationPresent(Config.class)) {
- throw new IllegalArgumentException(String.format(
- "Configuration schema must contain @%s or @%s: %s",
- ConfigurationRoot.class.getSimpleName(),
- Config.class.getSimpleName(),
- cls.getName()
- ));
- }
- else {
- res.add(cls);
+ /**
+ * Returns the identifier of the polymorphic configuration.
+ *
+ * @param schemaClass Configuration schema class.
+ * @return Identifier of the polymorphic configuration.
+ * @see PolymorphicConfigInstance#value
+ */
+ public static String polymorphicInstanceId(Class<?> schemaClass) {
+ assert isPolymorphicConfigInstance(schemaClass) : schemaClass.getName();
- for (Field f : cls.getDeclaredFields()) {
- if ((f.isAnnotationPresent(ConfigValue.class) || f.isAnnotationPresent(NamedConfigValue.class))
- && !res.contains(f.getType()))
- queue.add(f.getType());
- }
- }
+ return schemaClass.getAnnotation(PolymorphicConfigInstance.class).value();
+ }
+
+ /**
+ * Prepares a map for further work with it:
+ * 1)If a deleted element of the named list is encountered, then this subtree becomes {@code null};
+ * 2)If a {@code null} leaf is encountered due to a change in the polymorphic configuration, then remove it.
+ *
+ * @param prefixMap Prefix map, constructed from the storage notification data or its subtree.
+ */
+ public static void compressDeletedEntries(Map<String, ?> prefixMap) {
+ for (Iterator<? extends Map.Entry<String, ?>> it = prefixMap.entrySet().iterator(); it.hasNext(); ) {
+ Map.Entry<String, ?> entry = it.next();
+
+ Object value = entry.getValue();
+
+ if (value instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>)value;
+
+ // If an element of the named list is removed then {@link NamedListNode#NAME}
+ // will be {@code null} and the entire subtree can be replaced with {@code null}.
+ if (map.containsKey(NamedListNode.NAME) && map.get(NamedListNode.NAME) == null)
+ entry.setValue(null);
}
+ else if (value == null) {
+ // If there was a change in the type of polymorphic configuration,
+ // then the fields of the old configuration will be {@code null}, so we can get rid of them.
+ it.remove();
+ }
+ }
- return res;
+ // Continue recursively.
+ for (Object value : prefixMap.values()) {
+ if (value instanceof Map)
+ compressDeletedEntries((Map<String, ?>)value);
}
}
/**
- * Get the class names of the fields.
- *
- * @param fields Fields.
- * @return Fields class names.
+ * Leaf configuration source.
*/
- public static List<String> classNames(Field... fields) {
- return Stream.of(fields).map(Field::getDeclaringClass).map(Class::getName).collect(toList());
+ public static class LeafConfigurationSource implements ConfigurationSource {
+ /** Value. */
+ private final Serializable val;
+
+ /**
+ * Constructor.
+ *
+ * @param val Value.
+ */
+ public LeafConfigurationSource(Serializable val) {
+ this.val = val;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ assert val == null || clazz.isInstance(val);
+
+ return clazz.cast(val);
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode node) {
+ throw new UnsupportedOperationException("descend");
+ }
}
/**
- * Extracts the "direct" value from the given property.
- *
- * @param property Property to get the value from.
- * @return "direct" value of the property.
- * @throws ClassCastException if the property has not been annotated with {@link DirectAccess}.
- *
- * @see DirectAccess
- * @see DirectConfigurationProperty
+ * Inner configuration source.
*/
- public static <T> T directValue(ConfigurationProperty<T> property) {
- return ((DirectConfigurationProperty<T>)property).directValue();
+ private static class InnerConfigurationSource implements ConfigurationSource {
+ /** Prefix map. */
+ private final Map<String, ?> map;
+
+ /**
+ * Constructor.
+ *
+ * @param map Prefix map.
+ */
+ private InnerConfigurationSource(Map<String, ?> map) {
+ this.map = map;
+ }
+
+ /** {@inheritDoc} */
+ @Override public <T> T unwrap(Class<T> clazz) {
+ throw new UnsupportedOperationException("unwrap");
+ }
+
+ /** {@inheritDoc} */
+ @Override public void descend(ConstructableTreeNode node) {
+ if (node instanceof NamedListNode) {
+ descendToNamedListNode((NamedListNode<?>)node);
+
+ return;
+ }
+
+ for (Map.Entry<String, ?> entry : map.entrySet()) {
+ String key = entry.getKey();
+ Object val = entry.getValue();
+
+ assert val == null || val instanceof Map || val instanceof Serializable;
+
+ // Ordering of indexes must be skipped here because they make no sense in this context.
+ if (key.equals(NamedListNode.ORDER_IDX) || key.equals(NamedListNode.NAME))
+ continue;
+
+ if (val == null)
+ node.construct(key, null, true);
+ else if (val instanceof Map)
+ node.construct(key, new InnerConfigurationSource((Map<String, ?>)val), true);
+ else
+ node.construct(key, new LeafConfigurationSource((Serializable)val), true);
+ }
+ }
+
+ /** {@inheritDoc} */
+ @Override public @Nullable String polymorphicTypeId(String fieldName) {
+ return (String)map.get(fieldName);
+ }
+
+ /**
+ * Specific implementation of {@link #descend(ConstructableTreeNode)} that descends into named list node and
+ * sets a proper ordering to named list elements.
+ *
+ * @param node Named list node under construction.
+ */
+ private void descendToNamedListNode(NamedListNode<?> node) {
+ // This list must be mutable and RandomAccess.
+ var orderedKeys = new ArrayList<>(((NamedListView<?>)node).namedListKeys());
+
+ for (Map.Entry<String, ?> entry : map.entrySet()) {
+ String internalId = entry.getKey();
+ Object val = entry.getValue();
+
+ assert val == null || val instanceof Map || val instanceof Serializable;
+
+ String oldKey = node.keyByInternalId(internalId);
+
+ if (val == null) {
+ // Given that this particular method is applied to modify existing trees rather than
+ // creating new trees, a "hack" is required in this place. "construct" is designed to create
+ // "change" objects, thus it would just nullify named list element instead of deleting it.
+ node.forceDelete(oldKey);
+ }
+ else if (val instanceof Map) {
+ Map<String, ?> map = (Map<String, ?>)val;
+ int sizeDiff = 0;
+
+ // For every named list entry modification we must take its index into account.
+ // We do this by modifying "orderedKeys" when index is explicitly passed.
+ Object idxObj = map.get(NamedListNode.ORDER_IDX);
+
+ if (idxObj != null)
+ sizeDiff++;
+
+ String newKey = (String)map.get(NamedListNode.NAME);
+
+ if (newKey != null)
+ sizeDiff++;
+
+ boolean construct = map.size() != sizeDiff;
+
+ if (oldKey == null) {
+ node.construct(newKey, new InnerConfigurationSource(map), true);
+
+ node.setInternalId(newKey, internalId);
+ }
+ else if (newKey != null) {
+ node.rename(oldKey, newKey);
+
+ if (construct)
+ node.construct(newKey, new InnerConfigurationSource(map), true);
+ }
+ else if (construct)
+ node.construct(oldKey, new InnerConfigurationSource(map), true);
+ // Else it's just index adjustment after new elements insertion.
+
+ if (newKey == null)
+ newKey = oldKey;
+
+ if (idxObj != null) {
+ assert idxObj instanceof Integer : val;
+
+ int idx = (Integer)idxObj;
+
+ if (idx >= orderedKeys.size()) {
+ // Updates can come in arbitrary order. This means that array may be too small
+ // during batch creation. In this case we have to insert enough nulls before
+ // invoking "add" method for actual key.
+ orderedKeys.ensureCapacity(idx + 1);
+
+ while (idx != orderedKeys.size())
+ orderedKeys.add(null);
+
+ orderedKeys.add(newKey);
+ }
+ else
+ orderedKeys.set(idx, newKey);
+ }
+ }
+ else
+ node.construct(oldKey, new LeafConfigurationSource((Serializable)val), true);
+ }
+
+ node.reorderKeys(orderedKeys.size() > node.size()
+ ? orderedKeys.subList(0, node.size())
+ : orderedKeys
+ );
+ }
}
}
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
index 17d07ae..fc150f5 100644
--- a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/KeysTrackingConfigurationVisitor.java
@@ -99,15 +99,14 @@ public abstract class KeysTrackingConfigurationVisitor<T> implements Configurati
*
* @param key Name of the node retrieved from its holder object.
* @param node Named list inner configuration node.
- * @param <N> Type of element nodes in the named list.
* @return Anything that implementation decides to return.
*/
- protected <N extends InnerNode> T doVisitNamedListNode(String key, NamedListNode<N> node) {
+ protected T doVisitNamedListNode(String key, NamedListNode<?> node) {
for (String namedListKey : node.namedListKeys()) {
int prevPos = startVisit(namedListKey, true, false);
try {
- doVisitInnerNode(namedListKey, node.get(namedListKey));
+ doVisitInnerNode(namedListKey, node.getInnerNode(namedListKey));
}
finally {
endVisit(prevPos);
diff --git a/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java
new file mode 100644
index 0000000..82fb074
--- /dev/null
+++ b/modules/configuration/src/main/java/org/apache/ignite/internal/configuration/util/WrongPolymorphicTypeIdException.java
@@ -0,0 +1,33 @@
+/*
+ * 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.util;
+
+/**
+ * Thrown when the wrong (unknown) type of polymorphic configuration is used.
+ */
+public class WrongPolymorphicTypeIdException extends RuntimeException {
+ /**
+ * Constructor.
+ *
+ * @param message Error message.
+ * @param cause Cause of the exception.
+ */
+ public WrongPolymorphicTypeIdException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
index d8b17f2..93679b4 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationChangerTest.java
@@ -194,6 +194,7 @@ public class ConfigurationChangerTest {
List.of(KEY),
Map.of(MaybeInvalid.class, Set.of(validator)),
storage,
+ List.of(),
List.of()
);
@@ -481,6 +482,6 @@ public class ConfigurationChangerTest {
/** */
private ConfigurationChanger createChanger(RootKey<?, ?> rootKey) {
- return new TestConfigurationChanger(cgen, List.of(rootKey), Map.of(), storage, List.of());
+ return new TestConfigurationChanger(cgen, List.of(rootKey), Map.of(), storage, List.of(), List.of());
}
}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
index 8b65be0..c14f48e 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/ConfigurationRegistryTest.java
@@ -19,8 +19,12 @@ package org.apache.ignite.internal.configuration;
import java.util.List;
import java.util.Map;
+import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.junit.jupiter.api.Test;
@@ -41,17 +45,66 @@ public class ConfigurationRegistryTest {
List.of(SecondRootConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
- List.of(ExtendedFirstRootConfigurationSchema.class)
+ List.of(ExtendedFirstRootConfigurationSchema.class),
+ List.of()
)
);
// Check that everything is fine.
- new ConfigurationRegistry(
+ ConfigurationRegistry configRegistry = new ConfigurationRegistry(
List.of(FirstRootConfiguration.KEY, SecondRootConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
- List.of(ExtendedFirstRootConfigurationSchema.class)
+ List.of(ExtendedFirstRootConfigurationSchema.class),
+ List.of()
);
+
+ configRegistry.stop();
+ }
+
+ /** */
+ @Test
+ void testValidationPolymorphicConfigurationExtensions() {
+ // There is a polymorphic extension that is missing from the schema.
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new ConfigurationRegistry(
+ List.of(ThirdRootConfiguration.KEY),
+ Map.of(),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of(Second0PolymorphicConfigurationSchema.class)
+ )
+ );
+
+ // There are two polymorphic extensions with the same id.
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> new ConfigurationRegistry(
+ List.of(ThirdRootConfiguration.KEY),
+ Map.of(),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of(First0PolymorphicConfigurationSchema.class, ErrorFirst0PolymorphicConfigurationSchema.class)
+ )
+ );
+
+ // Check that everything is fine.
+ ConfigurationRegistry configRegistry = new ConfigurationRegistry(
+ List.of(ThirdRootConfiguration.KEY, FourthRootConfiguration.KEY, FifthRootConfiguration.KEY),
+ Map.of(),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of(
+ First0PolymorphicConfigurationSchema.class,
+ First1PolymorphicConfigurationSchema.class,
+ Second0PolymorphicConfigurationSchema.class,
+ Third0PolymorphicConfigurationSchema.class,
+ Third1PolymorphicConfigurationSchema.class
+ )
+ );
+
+ configRegistry.stop();
}
/**
@@ -83,4 +136,106 @@ public class ConfigurationRegistryTest {
@Value(hasDefault = true)
public String strEx = "str";
}
+
+ /**
+ * Third root configuration.
+ */
+ @ConfigurationRoot(rootName = "third")
+ public static class ThirdRootConfigurationSchema {
+ /** First polymorphic configuration scheme */
+ @ConfigValue
+ public FirstPolymorphicConfigurationSchema polymorphicConfig;
+ }
+
+ /**
+ * Fourth root configuration.
+ */
+ @ConfigurationRoot(rootName = "fourth")
+ public static class FourthRootConfigurationSchema {
+ /** Second polymorphic configuration scheme */
+ @ConfigValue
+ public SecondPolymorphicConfigurationSchema polymorphicConfig;
+ }
+
+ /**
+ * Fifth root configuration.
+ */
+ @ConfigurationRoot(rootName = "fifth")
+ public static class FifthRootConfigurationSchema {
+ /** Third polymorphic configuration scheme */
+ @ConfigValue
+ public ThirdPolymorphicConfigurationSchema polymorphicConfig;
+ }
+
+ /**
+ * Simple first polymorphic configuration scheme.
+ */
+ @PolymorphicConfig
+ public static class FirstPolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId;
+ }
+
+ /**
+ * First {@link FirstPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("first0")
+ public static class First0PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+ }
+
+ /**
+ * Second {@link FirstPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("first1")
+ public static class First1PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+ }
+
+ /**
+ * First error {@link FirstPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("first0")
+ public static class ErrorFirst0PolymorphicConfigurationSchema extends FirstPolymorphicConfigurationSchema {
+ }
+
+ /**
+ * Second polymorphic configuration scheme.
+ */
+ @PolymorphicConfig
+ public static class SecondPolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId
+ public String typeId;
+ }
+
+ /**
+ * First {@link SecondPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("second0")
+ public static class Second0PolymorphicConfigurationSchema extends SecondPolymorphicConfigurationSchema {
+ }
+
+ /**
+ * Third polymorphic configuration scheme.
+ */
+ @PolymorphicConfig
+ public static class ThirdPolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId(hasDefault = true)
+ public String typeId = "third0";
+ }
+
+ /**
+ * First {@link ThirdPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("third0")
+ public static class Third0PolymorphicConfigurationSchema extends ThirdPolymorphicConfigurationSchema {
+ }
+
+ /**
+ * First {@link ThirdPolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("third1")
+ public static class Third1PolymorphicConfigurationSchema extends ThirdPolymorphicConfigurationSchema {
+ }
}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
index 64d2b6c..ad9c82e 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/DirectPropertiesTest.java
@@ -108,7 +108,11 @@ public class DirectPropertiesTest {
/** */
private final ConfigurationRegistry registry = new ConfigurationRegistry(
- List.of(DirectConfiguration.KEY), Map.of(), new TestConfigurationStorage(LOCAL), List.of()
+ List.of(DirectConfiguration.KEY),
+ Map.of(),
+ new TestConfigurationStorage(LOCAL),
+ List.of(),
+ List.of()
);
/** */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
index 4d2de45..250c7fa 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/TestConfigurationChanger.java
@@ -25,6 +25,7 @@ import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.configuration.validation.Validator;
import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.ConfigurationStorage;
@@ -32,6 +33,7 @@ import org.apache.ignite.internal.configuration.tree.InnerNode;
import static java.util.concurrent.CompletableFuture.completedFuture;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
/** Implementation of {@link ConfigurationChanger} to be used in tests. Has no support of listeners. */
public class TestConfigurationChanger extends ConfigurationChanger {
@@ -47,6 +49,8 @@ public class TestConfigurationChanger extends ConfigurationChanger {
* @param storage Configuration storage.
* @param internalSchemaExtensions Internal extensions ({@link InternalConfiguration})
* of configuration schemas ({@link ConfigurationRoot} and {@link Config}).
+ * @param polymorphicSchemaExtensions Polymorphic extensions ({@link PolymorphicConfigInstance})
+ * of configuration schemas.
* @throws IllegalArgumentException If the configuration type of the root keys is not equal to the storage type.
*/
public TestConfigurationChanger(
@@ -54,7 +58,8 @@ public class TestConfigurationChanger extends ConfigurationChanger {
Collection<RootKey<?, ?>> rootKeys,
Map<Class<? extends Annotation>, Set<Validator<?, ?>>> validators,
ConfigurationStorage storage,
- Collection<Class<?>> internalSchemaExtensions
+ Collection<Class<?>> internalSchemaExtensions,
+ Collection<Class<?>> polymorphicSchemaExtensions
) {
super(
(oldRoot, newRoot, revision) -> completedFuture(null),
@@ -65,9 +70,10 @@ public class TestConfigurationChanger extends ConfigurationChanger {
this.cgen = cgen;
- Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(internalSchemaExtensions);
+ Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(internalSchemaExtensions);
+ Map<Class<?>, Set<Class<?>>> polymorphicExtensions = polymorphicSchemaExtensions(polymorphicSchemaExtensions);
- rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), extensions));
+ rootKeys.forEach(key -> cgen.compileRootSchema(key.schemaClass(), internalExtensions, polymorphicExtensions));
}
/** {@inheritDoc} */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
index 72a5c83..22b8a74 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/asm/ConfigurationAsmGeneratorTest.java
@@ -22,11 +22,16 @@ import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import org.apache.ignite.configuration.ConfigurationReadOnlyException;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.ConfigurationChanger;
import org.apache.ignite.internal.configuration.DynamicConfiguration;
@@ -34,13 +39,17 @@ import org.apache.ignite.internal.configuration.TestConfigurationChanger;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.InnerNode;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -49,30 +58,48 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
* Testing the {@link ConfigurationAsmGenerator}.
*/
public class ConfigurationAsmGeneratorTest {
- /** Configuration changer. */
- private static ConfigurationChanger changer;
-
/** Configuration generator. */
private static ConfigurationAsmGenerator generator;
+ /** Configuration changer. */
+ private ConfigurationChanger changer;
+
/** */
@BeforeAll
public static void beforeAll() {
- Collection<Class<?>> extensions = List.of(
+ generator = new ConfigurationAsmGenerator();
+ }
+
+ /** */
+ @AfterAll
+ public static void afterAll() {
+ generator = null;
+ }
+
+ /** */
+ @BeforeEach
+ void beforeEach() {
+ Collection<Class<?>> internalExtensions = List.of(
ExtendedTestRootConfigurationSchema.class,
ExtendedSecondTestRootConfigurationSchema.class,
ExtendedTestConfigurationSchema.class,
ExtendedSecondTestConfigurationSchema.class
);
- generator = new ConfigurationAsmGenerator();
+ Collection<Class<?>> polymorphicExtensions = List.of(
+ FirstPolymorphicInstanceTestConfigurationSchema.class,
+ SecondPolymorphicInstanceTestConfigurationSchema.class,
+ FirstPolymorphicNamedInstanceTestConfigurationSchema.class,
+ SecondPolymorphicNamedInstanceTestConfigurationSchema.class
+ );
changer = new TestConfigurationChanger(
generator,
List.of(TestRootConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
- extensions
+ internalExtensions,
+ polymorphicExtensions
);
changer.start();
@@ -80,17 +107,14 @@ public class ConfigurationAsmGeneratorTest {
}
/** */
- @AfterAll
- public static void after() {
+ @AfterEach
+ void afterEach() {
changer.stop();
-
- generator = null;
- changer = null;
}
/** */
@Test
- void testExtendedRootConfiguration() throws Exception {
+ void testInternalRootConfiguration() throws Exception {
DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
TestRootConfiguration baseRootConfig = (TestRootConfiguration)config;
@@ -134,7 +158,7 @@ public class ConfigurationAsmGeneratorTest {
/** */
@Test
- void testExtendedSubConfiguration() throws Exception {
+ void testInternalSubConfiguration() throws Exception {
DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
TestRootConfiguration rootConfig = (TestRootConfiguration)config;
@@ -175,7 +199,7 @@ public class ConfigurationAsmGeneratorTest {
/** */
@Test
- void testExtendedNamedConfiguration() throws Exception {
+ void testInternalNamedConfiguration() throws Exception {
DynamicConfiguration<?, ?> config = generator.instantiateCfg(TestRootConfiguration.KEY, changer);
TestRootConfiguration rootConfig = (TestRootConfiguration)config;
@@ -231,6 +255,228 @@ public class ConfigurationAsmGeneratorTest {
subInnerNode.construct("i1", null, true);
}
+ /** */
+ @Test
+ void testPolymorphicSubConfiguration() throws Exception {
+ TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+ // Check defaults.
+
+ FirstPolymorphicInstanceTestConfiguration firstCfg = (FirstPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+ assertEquals("first", firstCfg.typeId().value());
+ assertEquals("strVal", firstCfg.strVal().value());
+ assertEquals(0, firstCfg.intVal().value());
+
+ FirstPolymorphicInstanceTestView firstVal = (FirstPolymorphicInstanceTestView)firstCfg.value();
+ assertEquals("first", firstVal.typeId());
+ assertEquals("strVal", firstVal.strVal());
+ assertEquals(0, firstVal.intVal());
+
+ firstVal = (FirstPolymorphicInstanceTestView)rootConfig.value().polymorphicSubCfg();
+ assertEquals("first", firstVal.typeId());
+ assertEquals("strVal", firstVal.strVal());
+ assertEquals(0, firstVal.intVal());
+
+ // Check simple changes.
+
+ firstCfg.strVal().update("strVal1").get(1, SECONDS);
+ firstCfg.intVal().update(1).get(1, SECONDS);
+
+ assertEquals("first", firstCfg.typeId().value());
+ assertEquals("strVal1", firstCfg.strVal().value());
+ assertEquals(1, firstCfg.intVal().value());
+
+ firstCfg.change(c -> ((FirstPolymorphicInstanceTestChange)c).changeIntVal(2).changeStrVal("strVal2")).get(1, SECONDS);
+
+ assertEquals("first", firstCfg.typeId().value());
+ assertEquals("strVal2", firstCfg.strVal().value());
+ assertEquals(2, firstCfg.intVal().value());
+
+ rootConfig.change(c -> c.changePolymorphicSubCfg(
+ c1 -> ((FirstPolymorphicInstanceTestChange)c1).changeIntVal(3).changeStrVal("strVal3")
+ )).get(1, SECONDS);
+
+ assertEquals("first", firstCfg.typeId().value());
+ assertEquals("strVal3", firstCfg.strVal().value());
+ assertEquals(3, firstCfg.intVal().value());
+
+ // Check convert.
+
+ rootConfig.polymorphicSubCfg().change(c -> c.convert(SecondPolymorphicInstanceTestChange.class)).get(1, SECONDS);
+
+ SecondPolymorphicInstanceTestConfiguration secondCfg = (SecondPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+ assertEquals("second", secondCfg.typeId().value());
+ assertEquals("strVal3", secondCfg.strVal().value());
+ assertEquals(0, secondCfg.intVal().value());
+ assertEquals(0L, secondCfg.longVal().value());
+
+ SecondPolymorphicInstanceTestView secondView = (SecondPolymorphicInstanceTestView)secondCfg.value();
+ assertEquals("second", secondView.typeId());
+ assertEquals("strVal3", secondView.strVal());
+ assertEquals(0, secondView.intVal());
+ assertEquals(0L, secondView.longVal());
+
+ rootConfig.polymorphicSubCfg().change(c -> c.convert(FirstPolymorphicInstanceTestChange.class)).get(1, SECONDS);
+
+ firstCfg = (FirstPolymorphicInstanceTestConfiguration)rootConfig.polymorphicSubCfg();
+ assertEquals("first", firstCfg.typeId().value());
+ assertEquals("strVal3", firstCfg.strVal().value());
+ assertEquals(0, firstCfg.intVal().value());
+ }
+
+ /** */
+ @Test
+ void testPolymorphicErrors() throws Exception {
+ TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+ PolymorphicTestConfiguration polymorphicCfg = rootConfig.polymorphicSubCfg();
+
+ // Checks for an error on an attempt to update polymorphicTypeId field.
+ assertThrows(
+ ConfigurationReadOnlyException.class,
+ () -> polymorphicCfg.typeId().update("second").get(1, SECONDS)
+ );
+
+ // Checks for an error on an attempt to read a field of a different type of polymorphic configuration.
+ assertThrows(ExecutionException.class, () -> polymorphicCfg.change(c -> {
+ FirstPolymorphicInstanceTestView firstView = (FirstPolymorphicInstanceTestView)c;
+
+ c.convert(SecondPolymorphicInstanceTestChange.class);
+
+ firstView.intVal();
+ }
+ ).get(1, SECONDS)
+ );
+
+ // Checks for an error on an attempt to change a field of a different type of polymorphic configuration.
+ assertThrows(ExecutionException.class, () -> polymorphicCfg.change(c -> {
+ FirstPolymorphicInstanceTestChange firstChange = (FirstPolymorphicInstanceTestChange)c;
+
+ c.convert(SecondPolymorphicInstanceTestChange.class);
+
+ firstChange.changeIntVal(10);
+ }
+ ).get(1, SECONDS)
+ );
+ }
+
+ /** */
+ @Test
+ void testPolymorphicNamedConfigurationAdd() throws Exception {
+ TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+ // Check add named polymorphic config.
+
+ rootConfig.polymorphicNamedCfg()
+ .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class))
+ .create("1", c1 -> c1.convert(SecondPolymorphicNamedInstanceTestChange.class)
+ .changeIntVal(1)
+ .changeLongVal(1)
+ .changeStrVal("strVal1")
+ )
+ ).get(1, SECONDS);
+
+ FirstPolymorphicNamedInstanceTestConfiguration firstCfg =
+ (FirstPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+ assertEquals("strVal", firstCfg.strVal().value());
+ assertEquals(0, firstCfg.intVal().value());
+
+ SecondPolymorphicNamedInstanceTestConfiguration secondCfg =
+ (SecondPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("1");
+
+ assertEquals("strVal1", secondCfg.strVal().value());
+ assertEquals(1, secondCfg.intVal().value());
+ assertEquals(1L, secondCfg.longVal().value());
+
+ // Check config values.
+ FirstPolymorphicNamedInstanceTestView firstVal = (FirstPolymorphicNamedInstanceTestView)firstCfg.value();
+
+ assertEquals("strVal", firstVal.strVal());
+ assertEquals(0, firstVal.intVal());
+
+ firstVal = (FirstPolymorphicNamedInstanceTestView)rootConfig.polymorphicNamedCfg().value().get("0");
+
+ assertEquals("strVal", firstVal.strVal());
+ assertEquals(0, firstVal.intVal());
+
+ firstVal = (FirstPolymorphicNamedInstanceTestView)rootConfig.value().polymorphicNamedCfg().get("0");
+
+ assertEquals("strVal", firstVal.strVal());
+ assertEquals(0, firstVal.intVal());
+
+ SecondPolymorphicNamedInstanceTestView secondVal = (SecondPolymorphicNamedInstanceTestView)secondCfg.value();
+
+ assertEquals("strVal1", secondVal.strVal());
+ assertEquals(1, secondVal.intVal());
+ assertEquals(1L, secondVal.longVal());
+
+ secondVal = (SecondPolymorphicNamedInstanceTestView)rootConfig.polymorphicNamedCfg().value().get("1");
+
+ assertEquals("strVal1", secondVal.strVal());
+ assertEquals(1, secondVal.intVal());
+ assertEquals(1L, secondVal.longVal());
+
+ secondVal = (SecondPolymorphicNamedInstanceTestView)rootConfig.value().polymorphicNamedCfg().get("1");
+
+ assertEquals("strVal1", secondVal.strVal());
+ assertEquals(1, secondVal.intVal());
+ assertEquals(1L, secondVal.longVal());
+ }
+
+ /** */
+ @Test
+ void testPolymorphicNamedConfigurationChange() throws Exception {
+ TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+ rootConfig.polymorphicNamedCfg()
+ .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class)))
+ .get(1, SECONDS);
+
+ FirstPolymorphicNamedInstanceTestConfiguration firstCfg =
+ (FirstPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+ firstCfg.intVal().update(1).get(1, SECONDS);
+
+ assertEquals(1, firstCfg.intVal().value());
+
+ firstCfg.change(c -> ((FirstPolymorphicNamedInstanceTestChange)c).changeIntVal(2).changeStrVal("strVal2"))
+ .get(1, SECONDS);
+
+ assertEquals(2, firstCfg.intVal().value());
+ assertEquals("strVal2", firstCfg.strVal().value());
+
+ // Check convert.
+
+ rootConfig.polymorphicNamedCfg().get("0")
+ .change(c -> c.convert(SecondPolymorphicNamedInstanceTestChange.class)
+ .changeLongVal(3)
+ .changeIntVal(3)
+ .changeStrVal("strVal3")
+ ).get(1, SECONDS);
+
+ SecondPolymorphicNamedInstanceTestConfiguration secondCfg =
+ (SecondPolymorphicNamedInstanceTestConfiguration)rootConfig.polymorphicNamedCfg().get("0");
+
+ assertEquals(3, secondCfg.intVal().value());
+ assertEquals(3L, secondCfg.longVal().value());
+ assertEquals("strVal3", secondCfg.strVal().value());
+ }
+
+ /** */
+ @Test
+ void testPolymorphicNamedConfigurationRemove() throws Exception {
+ TestRootConfiguration rootConfig = (TestRootConfiguration)generator.instantiateCfg(TestRootConfiguration.KEY, changer);
+
+ rootConfig.polymorphicNamedCfg()
+ .change(c -> c.create("0", c1 -> c1.convert(FirstPolymorphicNamedInstanceTestChange.class)))
+ .get(1, SECONDS);
+
+ rootConfig.polymorphicNamedCfg().change(c -> c.delete("0")).get(1, SECONDS);
+
+ assertNull(rootConfig.polymorphicNamedCfg().get("0"));
+ }
+
/**
* Test root configuration schema.
*/
@@ -251,6 +497,14 @@ public class ConfigurationAsmGeneratorTest {
/** Named configuration field. */
@NamedConfigValue
public TestConfigurationSchema namedCfg;
+
+ /** Polymorphic sub configuration field. */
+ @ConfigValue
+ public PolymorphicTestConfigurationSchema polymorphicSubCfg;
+
+ /** Polymorphic named configuration field. */
+ @NamedConfigValue
+ public PolymorphicNamedTestConfigurationSchema polymorphicNamedCfg;
}
/**
@@ -306,4 +560,80 @@ public class ConfigurationAsmGeneratorTest {
@Value(hasDefault = true)
public int i1 = 0;
}
+
+ /**
+ * Polymorphic configuration scheme.
+ */
+ @PolymorphicConfig
+ public static class PolymorphicTestConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId(hasDefault = true)
+ public String typeId = "first";
+
+ /** String value. */
+ @Value(hasDefault = true)
+ public String strVal = "strVal";
+ }
+
+ /**
+ * First instance of the polymorphic configuration schema.
+ */
+ @PolymorphicConfigInstance("first")
+ public static class FirstPolymorphicInstanceTestConfigurationSchema extends PolymorphicTestConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+ }
+
+ /**
+ * Second instance of the polymorphic configuration schema.
+ */
+ @PolymorphicConfigInstance("second")
+ public static class SecondPolymorphicInstanceTestConfigurationSchema extends PolymorphicTestConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+
+ /** Long value. */
+ @Value(hasDefault = true)
+ public long longVal = 0;
+ }
+
+ /**
+ * Polymorphic named configuration scheme.
+ */
+ @PolymorphicConfig
+ public static class PolymorphicNamedTestConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId(hasDefault = true)
+ public String typeId;
+
+ /** String value. */
+ @Value(hasDefault = true)
+ public String strVal = "strVal";
+ }
+
+ /**
+ * First instance of the named polymorphic configuration schema.
+ */
+ @PolymorphicConfigInstance("first")
+ public static class FirstPolymorphicNamedInstanceTestConfigurationSchema extends PolymorphicNamedTestConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+ }
+
+ /**
+ * Second instance of the named polymorphic configuration schema.
+ */
+ @PolymorphicConfigInstance("second")
+ public static class SecondPolymorphicNamedInstanceTestConfigurationSchema extends PolymorphicNamedTestConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+
+ /** Long value. */
+ @Value(hasDefault = true)
+ public long longVal = 0;
+ }
}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
index e43232a..ed73efa 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/hocon/HoconConverterTest.java
@@ -29,6 +29,9 @@ import com.typesafe.config.ConfigValue;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
@@ -45,6 +48,7 @@ import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
@@ -62,6 +66,10 @@ public class HoconConverterTest {
/** */
@NamedConfigValue(syntheticKeyName = "p")
public HoconPrimitivesConfigurationSchema primitivesList;
+
+ /** Polymorphic config. */
+ @org.apache.ignite.configuration.annotation.ConfigValue
+ public HoconPolymorphicConfigurationSchema polymorphicCfg;
}
/**
@@ -148,6 +156,36 @@ public class HoconConverterTest {
public String stringVal = "";
}
+ /**
+ * Configuration schema for testing the support of polymorphic configuration.
+ */
+ @PolymorphicConfig
+ public static class HoconPolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId(hasDefault = true)
+ public String typeId = "first";
+ }
+
+ /**
+ * Configuration schema for testing the support of polymorphic configuration.
+ */
+ @PolymorphicConfigInstance("first")
+ public static class HoconFirstPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
+ /** Long value. */
+ @Value(hasDefault = true)
+ public int longVal = 0;
+ }
+
+ /**
+ * Configuration schema for testing the support of polymorphic configuration.
+ */
+ @PolymorphicConfigInstance("second")
+ public static class HoconSecondPolymorphicInstanceConfigurationSchema extends HoconPolymorphicConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+ }
+
/** */
private static ConfigurationRegistry registry;
@@ -161,7 +199,11 @@ public class HoconConverterTest {
List.of(HoconRootConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
- List.of()
+ List.of(),
+ List.of(
+ HoconFirstPolymorphicInstanceConfigurationSchema.class,
+ HoconSecondPolymorphicInstanceConfigurationSchema.class
+ )
);
registry.start();
@@ -185,15 +227,16 @@ public class HoconConverterTest {
configuration.change(cfg -> cfg
.changePrimitivesList(list -> list.namedListKeys().forEach(list::delete))
.changeArraysList(list -> list.namedListKeys().forEach(list::delete))
+ .changePolymorphicCfg(c -> {})
).get(1, SECONDS);
}
/** */
@Test
public void toHoconBasic() {
- assertEquals("root{arraysList=[],primitivesList=[]}", asHoconStr(List.of()));
+ assertEquals("root{arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]}", asHoconStr(List.of()));
- assertEquals("arraysList=[],primitivesList=[]", asHoconStr(List.of("root")));
+ assertEquals("arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]", asHoconStr(List.of("root")));
assertEquals("[]", asHoconStr(List.of("root", "arraysList")));
@@ -549,6 +592,35 @@ public class HoconConverterTest {
);
}
+ /** */
+ @Test
+ void testPolymorphicConfig() throws Throwable {
+ // Check defaults.
+ assertEquals("root{arraysList=[],polymorphicCfg{longVal=0,typeId=first},primitivesList=[]}", asHoconStr(List.of()));
+
+ // Check change type.
+ change("root.polymorphicCfg.typeId = second");
+
+ assertInstanceOf(HoconSecondPolymorphicInstanceConfiguration.class, configuration.polymorphicCfg());
+ assertEquals("root{arraysList=[],polymorphicCfg{intVal=0,typeId=second},primitivesList=[]}", asHoconStr(List.of()));
+
+ // Check change field.
+ change("root.polymorphicCfg.intVal = 10");
+ assertEquals("root{arraysList=[],polymorphicCfg{intVal=10,typeId=second},primitivesList=[]}", asHoconStr(List.of()));
+
+ // Check error: unknown typeId.
+ assertThrowsIllegalArgException(
+ () -> change("root.polymorphicCfg.typeId = ERROR"),
+ "Polymorphic configuration type is not correct: ERROR"
+ );
+
+ // Check error: try update field from typeId = first.
+ assertThrowsIllegalArgException(
+ () -> change("root.polymorphicCfg.longVal = 10"),
+ "'root.polymorphicCfg' configuration doesn't have the 'longVal' sub-configuration"
+ );
+ }
+
/**
* Updates the configuration using the provided HOCON string.
*/
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
index 6648f31..9f77c42 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationAnyListenerTest.java
@@ -113,6 +113,7 @@ public class ConfigurationAnyListenerTest {
List.of(RootConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
index de630d0..ca8b18d 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/notifications/ConfigurationListenerTest.java
@@ -91,6 +91,7 @@ public class ConfigurationListenerTest {
List.of(ParentConfiguration.KEY),
Map.of(),
testConfigurationStorage,
+ List.of(),
List.of()
);
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
index c747ee5..0d9cabe 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/sample/UsageTest.java
@@ -53,6 +53,7 @@ public class UsageTest {
List.of(LocalConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
@@ -114,6 +115,7 @@ public class UsageTest {
List.of(NetworkConfiguration.KEY, LocalConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
index c2d97b5..b6dbd91 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtension.java
@@ -54,6 +54,7 @@ import org.junit.platform.commons.support.HierarchyTraversalMode;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -161,7 +162,11 @@ public class ConfigurationExtension implements BeforeEachCallback, AfterEachCall
// classes, extension is designed to mock actual configurations from public API to configure Ignite components.
Class<?> schemaClass = Class.forName(type.getCanonicalName() + "Schema");
- cgen.compileRootSchema(schemaClass, internalSchemaExtensions(List.of(annotation.extensions())));
+ cgen.compileRootSchema(
+ schemaClass,
+ internalSchemaExtensions(List.of(annotation.internalExtensions())),
+ polymorphicSchemaExtensions(List.of(annotation.polymorphicExtensions()))
+ );
// RootKey must be mocked, there's no way to instantiate it using a public constructor.
RootKey rootKey = mock(RootKey.class);
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
index b29c68a..21236ee 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/ConfigurationExtensionTest.java
@@ -94,7 +94,7 @@ class ConfigurationExtensionTest {
/** Tests that internal configuration extensions work properly on injected configuration instance. */
@Test
public void internalConfiguration(
- @InjectConfiguration(extensions = {ExtendedConfigurationSchema.class}) BasicConfiguration cfg
+ @InjectConfiguration(internalExtensions = {ExtendedConfigurationSchema.class}) BasicConfiguration cfg
) throws Exception {
assertThat(cfg, is(instanceOf(ExtendedConfiguration.class)));
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
index e5e5a73..cd3e285 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/testframework/InjectConfiguration.java
@@ -22,6 +22,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
import org.apache.ignite.internal.configuration.ConfigurationChanger;
import org.apache.ignite.internal.configuration.ConfigurationRegistry;
import org.intellij.lang.annotations.Language;
@@ -64,5 +66,13 @@ public @interface InjectConfiguration {
*
* @return Array of configuration schema extensions.
*/
- Class<?>[] extensions() default {};
+ Class<?>[] internalExtensions() default {};
+
+ /**
+ * Array of configuration schema extensions. Every class in the array must be annotated with
+ * {@link PolymorphicConfigInstance} and extend some {@link PolymorphicConfig} schema.
+ *
+ * @return Array of configuration schema extensions.
+ */
+ Class<?>[] polymorphicExtensions() default {};
}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
index 927e047..cd497a0 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConfigurationArrayTest.java
@@ -85,7 +85,7 @@ public class ConfigurationArrayTest {
/** */
@BeforeAll
public static void beforeAll() {
- cgen.compileRootSchema(TestArrayConfigurationSchema.class, Map.of());
+ cgen.compileRootSchema(TestArrayConfigurationSchema.class, Map.of(), Map.of());
}
/** */
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
index 4d72612..fcf60d1 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/ConstructableTreeNodeTest.java
@@ -39,7 +39,7 @@ public class ConstructableTreeNodeTest {
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
- cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class, Map.of());
+ cgen.compileRootSchema(TraversableTreeNodeTest.ParentConfigurationSchema.class, Map.of(), Map.of());
}
@AfterAll
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
index a34d86e..2a70166 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/NamedListNodeTest.java
@@ -99,7 +99,15 @@ public class NamedListNodeTest {
public void before() {
storage = new TestConfigurationStorage(LOCAL);
- changer = new TestConfigurationChanger(cgen, List.of(AConfiguration.KEY), Map.of(), storage, List.of());
+ changer = new TestConfigurationChanger(
+ cgen,
+ List.of(AConfiguration.KEY),
+ Map.of(),
+ storage,
+ List.of(),
+ List.of()
+ );
+
changer.start();
}
@@ -271,7 +279,7 @@ public class NamedListNodeTest {
/** Tests exceptions described in methods signatures. */
@Test
public void errors() throws Exception {
- var b = new NamedListNode<>("name", () -> cgen.instantiateNode(BConfigurationSchema.class));
+ var b = new NamedListNode<>("name", () -> cgen.instantiateNode(BConfigurationSchema.class), null);
b.create("X", x -> {}).create("Y", y -> {});
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
index 95a8d7e..8893fdc 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/tree/TraversableTreeNodeTest.java
@@ -54,7 +54,7 @@ public class TraversableTreeNodeTest {
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
- cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
+ cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of(), Map.of());
}
@AfterAll
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
index cc7cd68..2803e11 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/util/ConfigurationUtilTest.java
@@ -18,19 +18,22 @@
package org.apache.ignite.internal.configuration.util;
import java.io.Serializable;
-import java.lang.reflect.Field;
+import java.util.Arrays;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.function.Consumer;
-import java.util.stream.Stream;
import org.apache.ignite.configuration.RootKey;
import org.apache.ignite.configuration.annotation.Config;
import org.apache.ignite.configuration.annotation.ConfigValue;
import org.apache.ignite.configuration.annotation.ConfigurationRoot;
import org.apache.ignite.configuration.annotation.InternalConfiguration;
import org.apache.ignite.configuration.annotation.NamedConfigValue;
+import org.apache.ignite.configuration.annotation.PolymorphicConfig;
+import org.apache.ignite.configuration.annotation.PolymorphicConfigInstance;
+import org.apache.ignite.configuration.annotation.PolymorphicId;
import org.apache.ignite.configuration.annotation.Value;
import org.apache.ignite.internal.configuration.RootInnerNode;
import org.apache.ignite.internal.configuration.SuperRoot;
@@ -38,6 +41,7 @@ import org.apache.ignite.internal.configuration.asm.ConfigurationAsmGenerator;
import org.apache.ignite.internal.configuration.storage.TestConfigurationStorage;
import org.apache.ignite.internal.configuration.tree.ConverterToMapVisitor;
import org.apache.ignite.internal.configuration.tree.InnerNode;
+import org.apache.ignite.internal.configuration.tree.NamedListNode;
import org.apache.ignite.internal.configuration.tree.TraversableTreeNode;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.AfterAll;
@@ -45,7 +49,7 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static java.util.Collections.singletonMap;
-import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Collectors.toList;
import static org.apache.ignite.configuration.annotation.ConfigurationType.DISTRIBUTED;
import static org.apache.ignite.configuration.annotation.ConfigurationType.LOCAL;
import static org.apache.ignite.internal.configuration.tree.NamedListNode.NAME;
@@ -55,9 +59,11 @@ import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.EM
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.addDefaults;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.checkConfigurationType;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.collectSchemas;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.compressDeletedEntries;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.extensionsFields;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.find;
import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.internalSchemaExtensions;
+import static org.apache.ignite.internal.configuration.util.ConfigurationUtil.polymorphicSchemaExtensions;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.aMapWithSize;
import static org.hamcrest.Matchers.allOf;
@@ -82,7 +88,16 @@ public class ConfigurationUtilTest {
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
- cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of());
+ cgen.compileRootSchema(ParentConfigurationSchema.class, Map.of(), Map.of());
+
+ cgen.compileRootSchema(
+ PolymorphicRootConfigurationSchema.class,
+ Map.of(),
+ Map.of(
+ PolymorphicConfigurationSchema.class,
+ Set.of(FirstPolymorphicInstanceConfigurationSchema.class, SecondPolymorphicInstanceConfigurationSchema.class)
+ )
+ );
}
@AfterAll
@@ -90,8 +105,15 @@ public class ConfigurationUtilTest {
cgen = null;
}
- public static <P extends InnerNode & ParentChange> P newParentInstance() {
- return (P)cgen.instantiateNode(ParentConfigurationSchema.class);
+ /**
+ * Creates new instance of {@code *Node} class corresponding to the given configuration Schema.
+ *
+ * @param schemaClass Configuration schema class.
+ * @param <P> Type of {@link InnerNode}.
+ * @return New instance of {@link InnerNode}.
+ */
+ public static <P extends InnerNode> P newNodeInstance(Class<?> schemaClass) {
+ return (P)cgen.instantiateNode(schemaClass);
}
/** */
@@ -160,9 +182,11 @@ public class ConfigurationUtilTest {
*/
@Test
public void findSuccessfully() {
- var parent = newParentInstance();
+ InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+ ParentChange parentChange = (ParentChange)parentNode;
- parent.changeElements(elements ->
+ parentChange.changeElements(elements ->
elements.createOrUpdate("name", element ->
element.changeChild(child ->
child.changeStr("value")
@@ -171,28 +195,28 @@ public class ConfigurationUtilTest {
);
assertSame(
- parent,
- ConfigurationUtil.find(List.of(), parent, true)
+ parentNode,
+ ConfigurationUtil.find(List.of(), parentNode, true)
);
assertSame(
- parent.elements(),
- ConfigurationUtil.find(List.of("elements"), parent, true)
+ parentChange.elements(),
+ ConfigurationUtil.find(List.of("elements"), parentNode, true)
);
assertSame(
- parent.elements().get("name"),
- ConfigurationUtil.find(List.of("elements", "name"), parent, true)
+ parentChange.elements().get("name"),
+ ConfigurationUtil.find(List.of("elements", "name"), parentNode, true)
);
assertSame(
- parent.elements().get("name").child(),
- ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
+ parentChange.elements().get("name").child(),
+ ConfigurationUtil.find(List.of("elements", "name", "child"), parentNode, true)
);
assertSame(
- parent.elements().get("name").child().str(),
- ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true)
+ parentChange.elements().get("name").child().str(),
+ ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parentNode, true)
);
}
@@ -202,13 +226,15 @@ public class ConfigurationUtilTest {
*/
@Test
public void findNulls() {
- var parent = newParentInstance();
+ InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
- assertNull(ConfigurationUtil.find(List.of("elements", "name"), parent, true));
+ ParentChange parentChange = (ParentChange)parentNode;
- parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+ assertNull(ConfigurationUtil.find(List.of("elements", "name"), parentNode, true));
- assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parent, true));
+ parentChange.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+
+ assertNull(ConfigurationUtil.find(List.of("elements", "name", "child", "str"), parentNode, true));
}
/**
@@ -217,25 +243,27 @@ public class ConfigurationUtilTest {
*/
@Test
public void findUnsuccessfully() {
- var parent = newParentInstance();
+ InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+ ParentChange parentChange = (ParentChange)parentNode;
assertThrows(
KeyNotFoundException.class,
- () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parent, true)
+ () -> ConfigurationUtil.find(List.of("elements", "name", "child"), parentNode, true)
);
- parent.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
+ parentChange.changeElements(elements -> elements.createOrUpdate("name", element -> {}));
assertThrows(
KeyNotFoundException.class,
- () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str0"), parent, true)
+ () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str0"), parentNode, true)
);
- ((NamedElementChange)parent.elements().get("name")).changeChild(child -> child.changeStr("value"));
+ ((NamedElementChange)parentChange.elements().get("name")).changeChild(child -> child.changeStr("value"));
assertThrows(
KeyNotFoundException.class,
- () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parent, true)
+ () -> ConfigurationUtil.find(List.of("elements", "name", "child", "str", "foo"), parentNode, true)
);
}
@@ -270,7 +298,9 @@ public class ConfigurationUtilTest {
*/
@Test
public void fillFromPrefixMapSuccessfully() {
- var parentNode = newParentInstance();
+ InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+ ParentChange parentChange = (ParentChange)parentNode;
ConfigurationUtil.fillFromPrefixMap(parentNode, Map.of(
"elements", Map.of(
@@ -287,8 +317,8 @@ public class ConfigurationUtilTest {
)
));
- assertEquals("value1", parentNode.elements().get("name1").child().str());
- assertEquals("value2", parentNode.elements().get("name2").child().str());
+ assertEquals("value1", parentChange.elements().get("name1").child().str());
+ assertEquals("value2", parentChange.elements().get("name2").child().str());
}
/**
@@ -296,9 +326,11 @@ public class ConfigurationUtilTest {
*/
@Test
public void fillFromPrefixMapSuccessfullyWithRemove() {
- var parentNode = newParentInstance();
+ InnerNode parentNode = newNodeInstance(ParentConfigurationSchema.class);
+
+ ParentChange parentChange = (ParentChange)parentNode;
- parentNode.changeElements(elements ->
+ parentChange.changeElements(elements ->
elements.createOrUpdate("name", element ->
element.changeChild(child -> {})
)
@@ -308,7 +340,7 @@ public class ConfigurationUtilTest {
"elements", singletonMap("name", null)
));
- assertNull(parentNode.elements().get("node"));
+ assertNull(parentChange.elements().get("node"));
}
/**
@@ -316,12 +348,15 @@ public class ConfigurationUtilTest {
*/
@Test
public void flattenedUpdatesMap() {
- var superRoot = new SuperRoot(key -> null, Map.of(ParentConfiguration.KEY, newParentInstance()));
+ var superRoot = new SuperRoot(
+ key -> null,
+ Map.of(ParentConfiguration.KEY, newNodeInstance(ParentConfigurationSchema.class))
+ );
- assertThat(flattenedMap(superRoot, parent -> {}), is(anEmptyMap()));
+ assertThat(flattenedMap(superRoot, ParentConfiguration.KEY, node -> {}), is(anEmptyMap()));
assertThat(
- flattenedMap(superRoot, parent -> parent
+ flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
.changeElements(elements -> elements
.create("name", element -> element
.changeChild(child -> child.changeStr("foo"))
@@ -337,14 +372,14 @@ public class ConfigurationUtilTest {
);
assertThat(
- flattenedMap(superRoot, parent -> parent
+ flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
.changeElements(elements1 -> elements1.delete("void"))
),
is(anEmptyMap())
);
assertThat(
- flattenedMap(superRoot, parent -> parent
+ flattenedMap(superRoot, ParentConfiguration.KEY, node -> ((ParentChange)node)
.changeElements(elements -> elements.delete("name"))
),
is(allOf(
@@ -415,81 +450,88 @@ public class ConfigurationUtilTest {
assertThrows(
IllegalArgumentException.class,
- () -> internalSchemaExtensions(List.of(SimpleRootConfigurationSchema.class))
+ () -> internalSchemaExtensions(List.of(InternalRootConfigurationSchema.class))
);
assertThrows(
IllegalArgumentException.class,
- () -> internalSchemaExtensions(List.of(SimpleConfigurationSchema.class))
+ () -> internalSchemaExtensions(List.of(InternalConfigurationSchema.class))
);
Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
- InternalFirstSimpleRootConfigurationSchema.class,
- InternalSecondSimpleRootConfigurationSchema.class,
- InternalFirstSimpleConfigurationSchema.class,
- InternalSecondSimpleConfigurationSchema.class
+ InternalFirstRootConfigurationSchema.class,
+ InternalSecondRootConfigurationSchema.class,
+ InternalFirstConfigurationSchema.class,
+ InternalSecondConfigurationSchema.class
));
assertEquals(2, extensions.size());
assertEquals(
Set.of(
- InternalFirstSimpleRootConfigurationSchema.class,
- InternalSecondSimpleRootConfigurationSchema.class
+ InternalFirstRootConfigurationSchema.class,
+ InternalSecondRootConfigurationSchema.class
),
- extensions.get(SimpleRootConfigurationSchema.class)
+ extensions.get(InternalRootConfigurationSchema.class)
);
assertEquals(
Set.of(
- InternalFirstSimpleConfigurationSchema.class,
- InternalSecondSimpleConfigurationSchema.class
+ InternalFirstConfigurationSchema.class,
+ InternalSecondConfigurationSchema.class
),
- extensions.get(SimpleConfigurationSchema.class)
+ extensions.get(InternalConfigurationSchema.class)
);
}
/** */
@Test
void testSchemaFields() {
- assertThrows(
- IllegalArgumentException.class,
- () -> extensionsFields(
- List.of(
- InternalExtendedSimpleRootConfigurationSchema.class,
- ErrorInternalExtendedSimpleRootConfigurationSchema.class
- )
- )
+ assertTrue(extensionsFields(List.of(), true).isEmpty());
+ assertTrue(extensionsFields(List.of(), false).isEmpty());
+
+ List<Class<?>> extensions0 = List.of(
+ InternalExtendedRootConfigurationSchema.class,
+ ErrorInternalExtendedRootConfigurationSchema.class
);
- assertTrue(extensionsFields(List.of()).isEmpty());
+ assertThrows(IllegalArgumentException.class, () -> extensionsFields(extensions0, true));
- List<Class<?>> extensions = List.of(
- InternalFirstSimpleRootConfigurationSchema.class,
- InternalSecondSimpleRootConfigurationSchema.class
+ assertEquals(
+ extensions0.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+ List.copyOf(extensionsFields(extensions0, false))
);
- Set<Field> exp = extensions.stream()
- .flatMap(cls -> Stream.of(cls.getDeclaredFields()))
- .collect(toSet());
+ List<Class<?>> extensions1 = List.of(
+ InternalFirstRootConfigurationSchema.class,
+ InternalSecondRootConfigurationSchema.class
+ );
+
+ assertEquals(
+ extensions1.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+ List.copyOf(extensionsFields(extensions1, true))
+ );
- assertEquals(exp, extensionsFields(extensions));
+ assertEquals(
+ extensions1.stream().flatMap(cls -> Arrays.stream(cls.getDeclaredFields())).collect(toList()),
+ List.copyOf(extensionsFields(extensions1, false))
+ );
}
/** */
@Test
void testFindInternalConfigs() {
- Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
- InternalFirstSimpleRootConfigurationSchema.class,
- InternalSecondSimpleRootConfigurationSchema.class,
- InternalFirstSimpleConfigurationSchema.class,
- InternalSecondSimpleConfigurationSchema.class
+ Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(List.of(
+ InternalFirstRootConfigurationSchema.class,
+ InternalSecondRootConfigurationSchema.class,
+ InternalFirstConfigurationSchema.class,
+ InternalSecondConfigurationSchema.class
));
ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
- generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+ generator.compileRootSchema(InternalRootConfigurationSchema.class, internalExtensions, Map.of());
- InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+ InnerNode innerNode = generator.instantiateNode(InternalRootConfigurationSchema.class);
addDefaults(innerNode);
@@ -515,17 +557,17 @@ public class ConfigurationUtilTest {
/** */
@Test
void testGetInternalConfigs() {
- Map<Class<?>, Set<Class<?>>> extensions = internalSchemaExtensions(List.of(
- InternalFirstSimpleRootConfigurationSchema.class,
- InternalSecondSimpleRootConfigurationSchema.class,
- InternalFirstSimpleConfigurationSchema.class,
- InternalSecondSimpleConfigurationSchema.class
+ Map<Class<?>, Set<Class<?>>> internalExtensions = internalSchemaExtensions(List.of(
+ InternalFirstRootConfigurationSchema.class,
+ InternalSecondRootConfigurationSchema.class,
+ InternalFirstConfigurationSchema.class,
+ InternalSecondConfigurationSchema.class
));
ConfigurationAsmGenerator generator = new ConfigurationAsmGenerator();
- generator.compileRootSchema(SimpleRootConfigurationSchema.class, extensions);
+ generator.compileRootSchema(InternalRootConfigurationSchema.class, internalExtensions, Map.of());
- InnerNode innerNode = generator.instantiateNode(SimpleRootConfigurationSchema.class);
+ InnerNode innerNode = generator.instantiateNode(InternalRootConfigurationSchema.class);
addDefaults(innerNode);
@@ -580,7 +622,7 @@ public class ConfigurationUtilTest {
Class<?> schemaClass = InternalWithoutSuperclassConfigurationSchema.class;
RootKey<?, ?> schemaKey = InternalWithoutSuperclassConfiguration.KEY;
- generator.compileRootSchema(schemaClass, Map.of());
+ generator.compileRootSchema(schemaClass, Map.of(), Map.of());
SuperRoot superRoot = new SuperRoot(
s -> new RootInnerNode(schemaKey, generator.instantiateNode(schemaClass))
@@ -614,34 +656,168 @@ public class ConfigurationUtilTest {
assertThrows(IllegalArgumentException.class, () -> collectSchemas(List.of(Object.class)));
+ Set<Class<?>> schemas = Set.of(
+ LocalFirstConfigurationSchema.class,
+ InternalConfigurationSchema.class,
+ PolymorphicConfigurationSchema.class
+ );
+
+ assertEquals(schemas, collectSchemas(schemas));
+
assertEquals(
- Set.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class),
- collectSchemas(List.of(LocalFirstConfigurationSchema.class, SimpleConfigurationSchema.class))
+ Set.of(
+ InternalRootConfigurationSchema.class,
+ PolymorphicRootConfigurationSchema.class,
+ InternalConfigurationSchema.class,
+ PolymorphicConfigurationSchema.class
+ ),
+ collectSchemas(List.of(InternalRootConfigurationSchema.class, PolymorphicRootConfigurationSchema.class))
+ );
+ }
+
+ /** */
+ @Test
+ void testPolymorphicSchemaExtensions() {
+ assertTrue(polymorphicSchemaExtensions(List.of()).isEmpty());
+
+ assertThrows(IllegalArgumentException.class, () -> polymorphicSchemaExtensions(List.of(Object.class)));
+
+ assertThrows(
+ IllegalArgumentException.class,
+ () -> polymorphicSchemaExtensions(List.of(LocalFirstConfigurationSchema.class))
+ );
+
+ Set<Class<?>> extensions = Set.of(
+ FirstPolymorphicInstanceConfigurationSchema.class,
+ SecondPolymorphicInstanceConfigurationSchema.class
);
assertEquals(
- Set.of(SimpleRootConfigurationSchema.class, SimpleConfigurationSchema.class),
- collectSchemas(List.of(SimpleRootConfigurationSchema.class))
+ Map.of(PolymorphicConfigurationSchema.class, extensions),
+ polymorphicSchemaExtensions(extensions)
);
}
+ /** */
+ @Test
+ void testCompressDeletedEntries() {
+ Map<String, String> containsNullLeaf = new HashMap<>();
+
+ containsNullLeaf.put("first", "1");
+ containsNullLeaf.put("second", null);
+
+ Map<String, String> deletedNamedListElement = new HashMap<>();
+
+ deletedNamedListElement.put("third", null);
+ deletedNamedListElement.put(NAME, null);
+
+ Map<String, Object> regular = new HashMap<>();
+
+ regular.put("strVal", "foo");
+ regular.put("intVal", 10);
+
+ Map<String, Object> prefixMap = new HashMap<>();
+
+ prefixMap.put("0", containsNullLeaf);
+ prefixMap.put("1", deletedNamedListElement);
+ prefixMap.put("2", regular);
+
+ Map<String, Object> exp = new HashMap<>();
+
+ exp.put("0", Map.of("first", "1"));
+ exp.put("1", null);
+ exp.put("2", Map.of("strVal", "foo", "intVal", 10));
+
+ compressDeletedEntries(prefixMap);
+
+ assertEquals(exp, prefixMap);
+ }
+
+ /** */
+ @Test
+ void testFlattenedMapPolymorphicConfig() {
+ InnerNode polymorphicRootInnerNode = newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+ addDefaults(polymorphicRootInnerNode);
+
+ RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+ SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, polymorphicRootInnerNode));
+
+ Map<String, Serializable> act = flattenedMap(
+ superRoot,
+ rootKey,
+ node -> ((PolymorphicRootChange)node).changePolymorphicSubCfg(c -> c.convert(SecondPolymorphicInstanceChange.class))
+ );
+
+ Map<String, Serializable> exp = new HashMap<>();
+
+ exp.put("rootPolymorphic.polymorphicSubCfg.typeId", "second");
+ exp.put("rootPolymorphic.polymorphicSubCfg.longVal", 0L);
+
+ exp.put("rootPolymorphic.polymorphicSubCfg.strVal", null);
+ exp.put("rootPolymorphic.polymorphicSubCfg.intVal", 0);
+
+ assertEquals(exp, act);
+ }
+
+ /** */
+ @Test
+ void testFlattenedMapPolymorphicNamedConfig() {
+ InnerNode polymorphicRootInnerNode = newNodeInstance(PolymorphicRootConfigurationSchema.class);
+
+ PolymorphicRootChange polymorphicRootChange = ((PolymorphicRootChange)polymorphicRootInnerNode);
+
+ polymorphicRootChange.changePolymorphicNamedCfg(c -> c.create("0", c1 -> {}));
+
+ addDefaults(polymorphicRootInnerNode);
+
+ RootKey<?, ?> rootKey = PolymorphicRootConfiguration.KEY;
+
+ SuperRoot superRoot = new SuperRoot(key -> null, Map.of(rootKey, polymorphicRootInnerNode));
+
+ Map<String, Serializable> act = flattenedMap(
+ superRoot,
+ rootKey,
+ node -> ((PolymorphicRootChange)node).changePolymorphicNamedCfg(c ->
+ c.createOrUpdate("0", c1 -> c1.convert(SecondPolymorphicInstanceChange.class)))
+ );
+
+ NamedListNode<?> polymorphicNamedCfgListNode = (NamedListNode<?>)polymorphicRootChange.polymorphicNamedCfg();
+ String internalId = polymorphicNamedCfgListNode.internalId("0");
+
+ Map<String, Serializable> exp = new HashMap<>();
+
+ exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".typeId", "second");
+ exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".longVal", 0L);
+
+ exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".strVal", null);
+ exp.put("rootPolymorphic.polymorphicNamedCfg." + internalId + ".intVal", 0);
+
+ assertEquals(exp, act);
+ }
+
/**
* Patches super root and returns flat representation of the changes. Passed {@code superRoot} object will contain
* patched tree when method execution is completed.
*
* @param superRoot Super root to patch.
- * @param patch Closure to cnahge parent node.
+ * @param patch Closure to change inner node.
* @return Flat map with all changes from the patch.
*/
- @NotNull private Map<String, Serializable> flattenedMap(SuperRoot superRoot, Consumer<ParentChange> patch) {
+ @NotNull private Map<String, Serializable> flattenedMap(
+ SuperRoot superRoot,
+ RootKey<?, ?> rootKey,
+ Consumer<InnerNode> patch
+ ) {
// Preserve a copy of the super root to use it as a golden source of data.
SuperRoot originalSuperRoot = superRoot.copy();
- // Make a copy of the root insode of the superRoot. This copy will be used for further patching.
- superRoot.construct(ParentConfiguration.KEY.key(), EMPTY_CFG_SRC, true);
+ // Make a copy of the root inside the superRoot. This copy will be used for further patching.
+ superRoot.construct(rootKey.key(), EMPTY_CFG_SRC, true);
// Patch root node.
- patch.accept((ParentChange)superRoot.getRoot(ParentConfiguration.KEY));
+ patch.accept(superRoot.getRoot(rootKey));
// Create flat diff between two super trees.
return createFlattenedUpdatesMap(originalSuperRoot, superRoot);
@@ -690,8 +866,8 @@ public class ConfigurationUtilTest {
/**
* Simple root configuration schema.
*/
- @ConfigurationRoot(rootName = "testRootSimple")
- public static class SimpleRootConfigurationSchema {
+ @ConfigurationRoot(rootName = "rootInternal")
+ public static class InternalRootConfigurationSchema {
/** String value without default. */
@Value
public String str0;
@@ -702,18 +878,18 @@ public class ConfigurationUtilTest {
/** Sub configuration schema. */
@ConfigValue
- public SimpleConfigurationSchema subCfg;
+ public InternalConfigurationSchema subCfg;
/** Named configuration schema. */
@NamedConfigValue
- public SimpleConfigurationSchema namedCfg;
+ public InternalConfigurationSchema namedCfg;
}
/**
* Simple configuration schema.
*/
@Config
- public static class SimpleConfigurationSchema {
+ public static class InternalConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str00 = "foo";
@@ -731,7 +907,7 @@ public class ConfigurationUtilTest {
* First simple internal schema extension.
*/
@InternalConfiguration
- public static class InternalFirstSimpleConfigurationSchema extends SimpleConfigurationSchema {
+ public static class InternalFirstConfigurationSchema extends InternalConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str01 = "foo";
@@ -741,7 +917,7 @@ public class ConfigurationUtilTest {
* Second simple internal schema extension.
*/
@InternalConfiguration
- public static class InternalSecondSimpleConfigurationSchema extends SimpleConfigurationSchema {
+ public static class InternalSecondConfigurationSchema extends InternalConfigurationSchema {
/** String value with default. */
@Value(hasDefault = true)
public String str02 = "foo";
@@ -751,7 +927,7 @@ public class ConfigurationUtilTest {
* First root simple internal schema extension.
*/
@InternalConfiguration
- public static class InternalFirstSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+ public static class InternalFirstRootConfigurationSchema extends InternalRootConfigurationSchema {
/** Second string value without default. */
@Value
public String str2;
@@ -765,17 +941,17 @@ public class ConfigurationUtilTest {
* Second root simple internal schema extension.
*/
@InternalConfiguration
- public static class InternalSecondSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+ public static class InternalSecondRootConfigurationSchema extends InternalRootConfigurationSchema {
/** Second sub configuration schema. */
@ConfigValue
- public SimpleConfigurationSchema subCfg1;
+ public InternalConfigurationSchema subCfg1;
}
/**
* Internal extended simple root configuration schema.
*/
@InternalConfiguration
- public static class InternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+ public static class InternalExtendedRootConfigurationSchema extends InternalRootConfigurationSchema {
/** String value without default. */
@Value
public String str00;
@@ -786,20 +962,68 @@ public class ConfigurationUtilTest {
/** Sub configuration schema. */
@ConfigValue
- public SimpleConfigurationSchema subCfg0;
+ public InternalConfigurationSchema subCfg0;
/** Named configuration schema. */
@NamedConfigValue
- public SimpleConfigurationSchema namedCfg0;
+ public InternalConfigurationSchema namedCfg0;
}
/**
* Error: Duplicate field.
*/
@InternalConfiguration
- public static class ErrorInternalExtendedSimpleRootConfigurationSchema extends SimpleRootConfigurationSchema {
+ public static class ErrorInternalExtendedRootConfigurationSchema extends InternalRootConfigurationSchema {
/** String value without default. */
@Value
public String str00;
}
+
+ /**
+ * Simple root polymorphic configuration.
+ */
+ @ConfigurationRoot(rootName = "rootPolymorphic")
+ public static class PolymorphicRootConfigurationSchema {
+ /** Polymorphic sub configuration schema. */
+ @ConfigValue
+ public PolymorphicConfigurationSchema polymorphicSubCfg;
+
+ /** Polymorphic named configuration schema. */
+ @NamedConfigValue
+ public PolymorphicConfigurationSchema polymorphicNamedCfg;
+ }
+
+ /**
+ * Simple polymorphic configuration.
+ */
+ @PolymorphicConfig
+ public static class PolymorphicConfigurationSchema {
+ /** Polymorphic type id field. */
+ @PolymorphicId(hasDefault = true)
+ public String typeId = "first";
+
+ /** Long value. */
+ @Value(hasDefault = true)
+ public long longVal = 0;
+ }
+
+ /**
+ * First {@link PolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("first")
+ public static class FirstPolymorphicInstanceConfigurationSchema extends PolymorphicConfigurationSchema {
+ /** String value. */
+ @Value(hasDefault = true)
+ public String strVal = "strVal";
+ }
+
+ /**
+ * Second {@link PolymorphicConfigurationSchema} extension.
+ */
+ @PolymorphicConfigInstance("second")
+ public static class SecondPolymorphicInstanceConfigurationSchema extends PolymorphicConfigurationSchema {
+ /** Integer value. */
+ @Value(hasDefault = true)
+ public int intVal = 0;
+ }
}
diff --git a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
index e394fe3..d1716ae 100644
--- a/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
+++ b/modules/configuration/src/test/java/org/apache/ignite/internal/configuration/validation/ValidationUtilTest.java
@@ -55,7 +55,7 @@ public class ValidationUtilTest {
public static void beforeAll() {
cgen = new ConfigurationAsmGenerator();
- cgen.compileRootSchema(ValidatedRootConfigurationSchema.class, Map.of());
+ cgen.compileRootSchema(ValidatedRootConfigurationSchema.class, Map.of(), Map.of());
}
@AfterAll
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
index 407ca7a..37f388a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/CollectionUtils.java
@@ -31,6 +31,8 @@ import org.jetbrains.annotations.Nullable;
import static java.util.Collections.addAll;
import static java.util.Collections.emptyIterator;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableCollection;
import static java.util.Collections.unmodifiableSet;
/**
@@ -86,22 +88,60 @@ public final class CollectionUtils {
*
* @param set Set.
* @param ts Items.
- * @param <T> Type of the elements of the list.
+ * @param <T> Type of the elements of set and items..
* @return Immutable union of set and items.
*/
@SafeVarargs
public static <T> Set<T> union(@Nullable Set<T> set, @Nullable T... ts) {
if (nullOrEmpty(set))
return ts == null || ts.length == 0 ? Set.of() : Set.of(ts);
- else if (ts == null || ts.length == 0)
+
+ if (ts == null || ts.length == 0)
return unmodifiableSet(set);
- else {
- Set<T> res = new HashSet<>(set);
- addAll(res, ts);
+ Set<T> res = new HashSet<>(set);
- return unmodifiableSet(res);
- }
+ addAll(res, ts);
+
+ return unmodifiableSet(res);
+ }
+
+ /**
+ * Union collections.
+ *
+ * @param collections Collections.
+ * @param <T> Type of the elements of collections.
+ * @return Immutable union of collections.
+ */
+ @SafeVarargs
+ public static <T> Collection<T> union(Collection<T>... collections) {
+ if (collections == null || collections.length == 0)
+ return List.of();
+
+ return new AbstractCollection<>() {
+ /** Total size of the collections. */
+ int size = -1;
+
+ /** {@inheritDoc} */
+ @Override public Iterator<T> iterator() {
+ return concat(collections).iterator();
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ if (size == -1) {
+ int s = 0;
+
+ for (Collection<T> collection : collections) {
+ s += collection.size();
+ }
+
+ size = s;
+ }
+
+ return size;
+ }
+ };
}
/**
@@ -117,31 +157,30 @@ public final class CollectionUtils {
public static <T> Iterable<T> concat(@Nullable Iterable<? extends T>... iterables) {
if (iterables == null || iterables.length == 0)
return Collections::emptyIterator;
- else {
- return () -> new Iterator<T>() {
- /** Current index at {@code iterables}. */
- int i = 0;
- /** Current iterator. */
- Iterator<? extends T> curr = emptyIterator();
+ return () -> new Iterator<>() {
+ /** Current index at {@code iterables}. */
+ int i = 0;
- /** {@inheritDoc} */
- @Override public boolean hasNext() {
- while (!curr.hasNext() && i < iterables.length)
- curr = iterables[i++].iterator();
+ /** Current iterator. */
+ Iterator<? extends T> curr = emptyIterator();
- return curr.hasNext();
- }
+ /** {@inheritDoc} */
+ @Override public boolean hasNext() {
+ while (!curr.hasNext() && i < iterables.length)
+ curr = iterables[i++].iterator();
- /** {@inheritDoc} */
- @Override public T next() {
- if (!hasNext())
- throw new NoSuchElementException();
- else
- return curr.next();
- }
- };
- }
+ return curr.hasNext();
+ }
+
+ /** {@inheritDoc} */
+ @Override public T next() {
+ if (!hasNext())
+ throw new NoSuchElementException();
+ else
+ return curr.next();
+ }
+ };
}
/**
@@ -158,39 +197,69 @@ public final class CollectionUtils {
@Nullable Function<? super T1, ? extends T2> mapper
) {
if (nullOrEmpty(collection))
- return Collections.emptyList();
- else if (mapper == null)
- return (Collection<T2>)collection;
- else {
- return new AbstractCollection<>() {
- /** {@inheritDoc} */
- @Override public Iterator<T2> iterator() {
- Iterator<? extends T1> iterator = collection.iterator();
-
- return new Iterator<>() {
- /** {@inheritDoc} */
- @Override public boolean hasNext() {
- return iterator.hasNext();
- }
-
- /** {@inheritDoc} */
- @Override public T2 next() {
- return mapper.apply(iterator.next());
- }
- };
- }
+ return emptyList();
- /** {@inheritDoc} */
- @Override public int size() {
- return collection.size();
- }
+ if (mapper == null)
+ return unmodifiableCollection((Collection<T2>)collection);
- /** {@inheritDoc} */
- @Override public boolean isEmpty() {
- return collection.isEmpty();
- }
- };
+ return new AbstractCollection<>() {
+ /** {@inheritDoc} */
+ @Override public Iterator<T2> iterator() {
+ Iterator<? extends T1> iterator = collection.iterator();
+
+ return new Iterator<>() {
+ /** {@inheritDoc} */
+ @Override public boolean hasNext() {
+ return iterator.hasNext();
+ }
+
+ /** {@inheritDoc} */
+ @Override public T2 next() {
+ return mapper.apply(iterator.next());
+ }
+ };
+ }
+
+ /** {@inheritDoc} */
+ @Override public int size() {
+ return collection.size();
+ }
+
+ /** {@inheritDoc} */
+ @Override public boolean isEmpty() {
+ return collection.isEmpty();
+ }
+ };
+ }
+
+ /**
+ * Difference of two sets.
+ *
+ * @param a First set.
+ * @param b Second set.
+ * @param <T> Type of the elements.
+ * @return Immutable set of elements of the first without the second.
+ */
+ public static <T> Set<T> difference(@Nullable Set<T> a, @Nullable Set<T> b) {
+ if (nullOrEmpty(a))
+ return Set.of();
+
+ else if (nullOrEmpty(b))
+ return unmodifiableSet(a);
+
+ // Lazy initialization.
+ Set<T> res = null;
+
+ for (T t : a) {
+ if (!b.contains(t)) {
+ if (res == null)
+ res = new HashSet<>();
+
+ res.add(t);
+ }
}
+
+ return res == null ? Set.of() : unmodifiableSet(res);
}
/** Stub. */
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
index 0724214..2484bf6 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/CollectionUtilsTest.java
@@ -17,6 +17,7 @@
package org.apache.ignite.internal.util;
+import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.StreamSupport;
@@ -25,6 +26,7 @@ import org.junit.jupiter.api.Test;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.util.CollectionUtils.concat;
+import static org.apache.ignite.internal.util.CollectionUtils.difference;
import static org.apache.ignite.internal.util.CollectionUtils.union;
import static org.apache.ignite.internal.util.CollectionUtils.viewReadOnly;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -51,17 +53,17 @@ public class CollectionUtilsTest {
/** */
@Test
- void testUnion() {
+ void testSetUnion() {
assertTrue(union(null, null).isEmpty());
assertTrue(union(Set.of(), null).isEmpty());
assertTrue(union(null, new Object[] {}).isEmpty());
assertEquals(Set.of(1), union(Set.of(1), null));
assertEquals(Set.of(1), union(Set.of(1), new Integer[] {}));
- assertEquals(Set.of(1), union(null, new Integer[] {1}));
- assertEquals(Set.of(1), union(Set.of(), new Integer[] {1}));
+ assertEquals(Set.of(1), union(null, 1));
+ assertEquals(Set.of(1), union(Set.of(), 1));
- assertEquals(Set.of(1, 2), union(Set.of(1), new Integer[] {2}));
+ assertEquals(Set.of(1, 2), union(Set.of(1), 2));
}
/** */
@@ -70,7 +72,7 @@ public class CollectionUtilsTest {
assertTrue(viewReadOnly(null, null).isEmpty());
assertTrue(viewReadOnly(List.of(), null).isEmpty());
- assertEquals(List.of(1), viewReadOnly(List.of(1), null));
+ assertEquals(List.of(1), collect(viewReadOnly(List.of(1), null)));
assertEquals(List.of(1), collect(viewReadOnly(List.of(1), identity())));
assertEquals(List.of("1", "2", "3"), collect(viewReadOnly(List.of(1, 2, 3), String::valueOf)));
@@ -88,6 +90,36 @@ public class CollectionUtilsTest {
assertThrows(UnsupportedOperationException.class, () -> viewReadOnly(List.of(1), null).iterator().remove());
}
+ /** */
+ @Test
+ void testSetDifference() {
+ assertTrue(difference(null, Set.of(1, 2, 3, 4)).isEmpty());
+ assertTrue(difference(Set.of(), Set.of(1, 2, 3, 4)).isEmpty());
+
+ assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), null));
+ assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), Set.of()));
+
+ assertEquals(Set.of(1, 2), difference(Set.of(1, 2, 3, 4), Set.of(3, 4)));
+ assertEquals(Set.of(1, 4), difference(Set.of(1, 2, 3, 4), Set.of(2, 3)));
+
+ assertEquals(Set.of(1, 2, 3, 4), difference(Set.of(1, 2, 3, 4), Set.of(5, 6, 7)));
+
+ assertEquals(Set.of(), difference(Set.of(1, 2, 3, 4), Set.of(1, 2, 3, 4)));
+ }
+
+ /** */
+ @Test
+ void testCollectionUnion() {
+ assertTrue(union().isEmpty());
+ assertTrue(union(new Collection[] {}).isEmpty());
+ assertTrue(union(List.of()).isEmpty());
+
+ assertEquals(List.of(1), collect(union(List.of(1), List.of())));
+ assertEquals(List.of(1), collect(union(List.of(), List.of(1))));
+
+ assertEquals(List.of(1, 2), collect(union(List.of(1), List.of(2))));
+ }
+
/**
* Collect of elements.
*
diff --git a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
index 977f0ff..0e5bf10 100644
--- a/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
+++ b/modules/network/src/integrationTest/java/org/apache/ignite/utils/ClusterServiceTestUtils.java
@@ -69,6 +69,7 @@ public class ClusterServiceTestUtils {
Collections.singleton(NetworkConfiguration.KEY),
Map.of(),
new TestConfigurationStorage(ConfigurationType.LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
index 6c35c8f..2f0565c 100644
--- a/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
+++ b/modules/rest/src/test/java/org/apache/ignite/rest/presentation/ConfigurationPresentationTest.java
@@ -71,6 +71,7 @@ public class ConfigurationPresentationTest {
List.of(TestRootConfiguration.KEY),
Map.of(Value.class, Set.of(validator)),
new TestConfigurationStorage(LOCAL),
+ List.of(),
List.of()
);
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
index 8e9c2e8..ff4b683 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/ITDistributedConfigurationPropertiesTest.java
@@ -136,6 +136,7 @@ public class ITDistributedConfigurationPropertiesTest {
List.of(NodeConfiguration.KEY),
Map.of(),
new LocalConfigurationStorage(vaultManager),
+ List.of(),
List.of()
);
@@ -165,6 +166,7 @@ public class ITDistributedConfigurationPropertiesTest {
List.of(DistributedConfiguration.KEY),
Map.of(),
distributedCfgStorage,
+ List.of(),
List.of()
);
}
diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
index f11dcda..6d85d1a 100644
--- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
+++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/configuration/storage/ITDistributedConfigurationStorageTest.java
@@ -108,6 +108,7 @@ public class ITDistributedConfigurationStorageTest {
rootKeys,
Map.of(),
new LocalConfigurationStorage(vaultManager),
+ List.of(),
List.of()
);
diff --git a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
index 9d7ae20..ced4cce 100644
--- a/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
+++ b/modules/runner/src/main/java/org/apache/ignite/internal/app/IgniteImpl.java
@@ -156,6 +156,7 @@ public class IgniteImpl implements Ignite {
),
Map.of(),
new LocalConfigurationStorage(vaultMgr),
+ List.of(),
List.of()
);
@@ -186,7 +187,8 @@ public class IgniteImpl implements Ignite {
),
Map.of(TableValidator.class, Set.of(TableValidatorImpl.INSTANCE)),
new DistributedConfigurationStorage(metaStorageMgr, vaultMgr),
- Collections.singletonList(ExtendedTableConfigurationSchema.class)
+ Collections.singletonList(ExtendedTableConfigurationSchema.class),
+ List.of()
);
baselineMgr = new BaselineManager(
diff --git a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
index 3046f31..31fb497 100644
--- a/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
+++ b/modules/schema/src/test/java/org/apache/ignite/internal/schema/configuration/SchemaConfigurationConverterTest.java
@@ -76,6 +76,7 @@ public class SchemaConfigurationConverterTest {
List.of(TablesConfiguration.KEY, DataStorageConfiguration.KEY),
Map.of(TableValidator.class, Set.of(TableValidatorImpl.INSTANCE)),
new TestConfigurationStorage(DISTRIBUTED),
+ List.of(),
List.of()
);
diff --git a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
index 88c0f90..39286ee 100644
--- a/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
+++ b/modules/table/src/test/java/org/apache/ignite/internal/table/TableManagerTest.java
@@ -133,7 +133,7 @@ public class TableManagerTest extends IgniteAbstractTest {
private Loza rm;
/** Tables configuration. */
- @InjectConfiguration(extensions = {ExtendedTableConfigurationSchema.class})
+ @InjectConfiguration(internalExtensions = {ExtendedTableConfigurationSchema.class})
private TablesConfiguration tblsCfg;
/** Data storage configuration. */