diff --git a/spring-configuration-extensions-samples/src/main/java/com/github/egoettelmann/spring/configuration/extensions/samples/config/SampleConfig2.java b/spring-configuration-extensions-samples/src/main/java/com/github/egoettelmann/spring/configuration/extensions/samples/config/SampleConfig2.java new file mode 100644 index 0000000..bfa708f --- /dev/null +++ b/spring-configuration-extensions-samples/src/main/java/com/github/egoettelmann/spring/configuration/extensions/samples/config/SampleConfig2.java @@ -0,0 +1,15 @@ +package com.github.egoettelmann.spring.configuration.extensions.samples.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SampleConfig2 { + + /** + * Custom config value injected by @Value, with default value referencing another config. + */ + @Value("${sample.custom.conf2:${sample.custom.conf}}") + private String customConfig2; + +} diff --git a/spring-configuration-extensions-samples/src/test/resources/additional-spring-configuration-metadata-test-1.json b/spring-configuration-extensions-samples/src/test/resources/additional-spring-configuration-metadata-test-1.json index ded5077..32c76ca 100644 --- a/spring-configuration-extensions-samples/src/test/resources/additional-spring-configuration-metadata-test-1.json +++ b/spring-configuration-extensions-samples/src/test/resources/additional-spring-configuration-metadata-test-1.json @@ -13,6 +13,20 @@ "description": "Config including unicode characters.\n", "sourceType": "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig", "defaultValue": null + }, + { + "name": "sample.custom.conf", + "type": "java.lang.String", + "description": "Custom config value injected by @Value, with default value referencing another config.\n", + "sourceType": "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2", + "defaultValue": null + }, + { + "name": "sample.custom.conf2", + "type": "java.lang.String", + "description": "Custom config value injected by @Value, with default value referencing another config.\n", + "sourceType": "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2", + "defaultValue": "${sample.custom.conf}" } ] } diff --git a/spring-configuration-extensions-samples/src/test/resources/aggregated-spring-configuration-metadata-test-1.json b/spring-configuration-extensions-samples/src/test/resources/aggregated-spring-configuration-metadata-test-1.json index 09e6a64..2a26125 100644 --- a/spring-configuration-extensions-samples/src/test/resources/aggregated-spring-configuration-metadata-test-1.json +++ b/spring-configuration-extensions-samples/src/test/resources/aggregated-spring-configuration-metadata-test-1.json @@ -2,7 +2,7 @@ "properties" : [ { "name" : "sample.app.title", "type" : "java.lang.String", - "description": "Sample app title injected through @ConfigurationProperties", + "description" : "Sample app title injected through @ConfigurationProperties", "defaultValue" : "Sample Application", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", @@ -12,17 +12,31 @@ }, { "name" : "sample.custom.conf", "type" : "java.lang.String", - "description": "Custom config value injected by @Value.\n", + "description" : "Custom config value injected by @Value.\n\nCustom config value injected by @Value, with default value referencing another config.", "defaultValue" : "Custom Config Value", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", "artifactId" : "spring-configuration-extensions-samples", "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig" + }, { + "groupId" : "com.github.egoettelmann", + "artifactId" : "spring-configuration-extensions-samples", + "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2" + } ] + }, { + "name" : "sample.custom.conf2", + "type" : "java.lang.String", + "description" : "Custom config value injected by @Value, with default value referencing another config.\n", + "defaultValue" : "${sample.custom.conf}", + "sourceTypes" : [ { + "groupId" : "com.github.egoettelmann", + "artifactId" : "spring-configuration-extensions-samples", + "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2" } ] }, { "name" : "sample.unicode.chars", "type" : "java.lang.String", - "description": "Config including unicode characters.\n", + "description" : "Config including unicode characters.\n", "defaultValue" : "\u0000\u0001\u0002\u0003\u0004\u0005", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", diff --git a/spring-configuration-extensions-samples/src/test/resources/report-test-1.json b/spring-configuration-extensions-samples/src/test/resources/report-test-1.json index 2b7ab64..956994e 100644 --- a/spring-configuration-extensions-samples/src/test/resources/report-test-1.json +++ b/spring-configuration-extensions-samples/src/test/resources/report-test-1.json @@ -2,13 +2,13 @@ "artifacts" : [ { "groupId" : "com.github.egoettelmann", "artifactId" : "spring-configuration-extensions-samples", - "version" : "0.0.7-SNAPSHOT", + "version" : "0.1.3-SNAPSHOT", "name" : "Spring Configuration Extensions: Samples", "description" : "Samples for illustrating Spring Configuration Extensions and performing tests", "properties" : [ { "name" : "sample.app.title", "type" : "java.lang.String", - "description": "Sample app title injected through @ConfigurationProperties", + "description" : "Sample app title injected through @ConfigurationProperties", "defaultValue" : "Sample Application", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", @@ -18,17 +18,31 @@ }, { "name" : "sample.custom.conf", "type" : "java.lang.String", - "description": "Custom config value injected by @Value.\n", + "description" : "Custom config value injected by @Value.\n\nCustom config value injected by @Value, with default value referencing another config.", "defaultValue" : "Custom Config Value", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", "artifactId" : "spring-configuration-extensions-samples", "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig" + }, { + "groupId" : "com.github.egoettelmann", + "artifactId" : "spring-configuration-extensions-samples", + "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2" + } ] + }, { + "name" : "sample.custom.conf2", + "type" : "java.lang.String", + "description" : "Custom config value injected by @Value, with default value referencing another config.\n", + "defaultValue" : "${sample.custom.conf}", + "sourceTypes" : [ { + "groupId" : "com.github.egoettelmann", + "artifactId" : "spring-configuration-extensions-samples", + "sourceType" : "com.github.egoettelmann.spring.configuration.extensions.samples.config.SampleConfig2" } ] }, { "name" : "sample.unicode.chars", "type" : "java.lang.String", - "description": "Config including unicode characters.\n", + "description" : "Config including unicode characters.\n", "defaultValue" : "\u0000\u0001\u0002\u0003\u0004\u0005", "sourceTypes" : [ { "groupId" : "com.github.egoettelmann", diff --git a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/ValueAnnotationProcessor.java b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/ValueAnnotationProcessor.java index a9093db..bf0b3f6 100644 --- a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/ValueAnnotationProcessor.java +++ b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/ValueAnnotationProcessor.java @@ -14,7 +14,6 @@ import java.io.Writer; import java.util.ArrayList; import java.util.List; -import java.util.Optional; import java.util.Set; @SupportedAnnotationTypes(ElementReader.VALUE_ANNOTATION_CLASS) @@ -45,12 +44,11 @@ public boolean process(Set annotations, RoundEnvironment for (final TypeElement annotation : annotations) { for (final Element element : roundEnv.getElementsAnnotatedWith(annotation)) { try { - final Optional metadata = this.elementReader.read(element); - if (!metadata.isPresent()) { - continue; + final List results = this.elementReader.read(element); + for (final ValueAnnotationMetadata metadata : results) { + this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Found @Value annotation with property '" + metadata.getName() + "'"); + metadataList.add(metadata); } - this.processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, "Found @Value annotation with property '" + metadata.get().getName() + "'"); - metadataList.add(metadata.get()); } catch (final Exception e) { final String errorMessage = "Error while processing @Value annotations on " + element.getSimpleName(); if (this.failOnError) { diff --git a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilder.java b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilder.java index 4774ec5..c7d70df 100644 --- a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilder.java +++ b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilder.java @@ -1,15 +1,11 @@ package com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core; -import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; public class ValueAnnotationMetadataBuilder { - private static final Pattern PATTERN = Pattern.compile("\\$\\{([^}]*)}"); - - private static final String DEFAULT_VALUE_SEPARATOR = ":"; - private final String rawValue; private String type; private String description; @@ -38,43 +34,23 @@ public ValueAnnotationMetadataBuilder sourceType(final String sourceType) { return this; } - public Optional build() { - ValueAnnotationMetadata metadata = new ValueAnnotationMetadata(); - - // Adding metadata - metadata.setType(this.type); - metadata.setDescription(this.description); - metadata.setSourceType(this.sourceType); - - // Extracting property name - final Optional definition = extractPropertyDefinition(this.rawValue); - if (!definition.isPresent()) { - return Optional.empty(); - } - final String definitionValue = definition.get(); - final String[] values = definitionValue.split(DEFAULT_VALUE_SEPARATOR, 2); - metadata.setName(values[0].trim()); - - // Extracting default value of defined - if (values.length > 1) { - metadata.setDefaultValue(values[1].trim()); - } + public List build() { + final List results = new ArrayList<>(); - return Optional.of(metadata); - } - - private static Optional extractPropertyDefinition(final String rawValue) { - final Matcher matcher = PATTERN.matcher(rawValue); - if (!matcher.find()) { - return Optional.empty(); - } + final ValueAnnotationParser parser = new ValueAnnotationParser(); + final Map properties = parser.parse(this.rawValue); - final String match = matcher.group(1); - if (match == null) { - return Optional.empty(); + for (final Map.Entry entry : properties.entrySet()) { + final ValueAnnotationMetadata metadata = new ValueAnnotationMetadata(); + metadata.setType(this.type); + metadata.setDescription(this.description); + metadata.setSourceType(this.sourceType); + metadata.setName(entry.getKey()); + metadata.setDefaultValue(entry.getValue()); + results.add(metadata); } - return Optional.of(match.trim()); + return results; } } diff --git a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParser.java b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParser.java new file mode 100644 index 0000000..80ae062 --- /dev/null +++ b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParser.java @@ -0,0 +1,145 @@ +package com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core; + +import java.util.HashMap; +import java.util.Map; + +/** + * Parser to resolve property placeholders within value annotations + */ +public class ValueAnnotationParser { + + private final String prefix = "${"; + private final String suffix = "}"; + private final String separator = ":"; + private final String opposingSuffix = "{"; + + /** + * Parses a value and returns a map of properties found, with: + * - the key being the property + * - the value being the default value (can be null) + * + * @param value the value to parse + * @return the map of parse properties and their default values + * @throws IllegalArgumentException if the provided value is invalid and cannot be parsed + */ + public Map parse(final String value) throws IllegalArgumentException { + final Map values = new HashMap<>(); + + // Searching first occurrence of PREFIX + int startIdx = value.indexOf(this.prefix); + if (startIdx == -1) { + // No occurrence found + return values; + } + startIdx += this.prefix.length(); + + // Searching end indexes (matching SEPARATOR and SUFFIX positions) + final String afterPrefix = value.substring(startIdx); + int[] indexes = this.findIndexes(afterPrefix); + int separatorIdx = indexes[0]; + int endIdx = indexes[1]; + + // Retrieving property and default value + String property = afterPrefix.substring(0, endIdx).trim(); + String defaultValue = null; + + // Separator found: splitting to retrieve values + if (separatorIdx > -1) { + property = afterPrefix.substring(0, separatorIdx).trim(); + defaultValue = afterPrefix.substring(separatorIdx + this.separator.length(), endIdx).trim(); + } + values.put(property, defaultValue); + + // Parsing default value + if (defaultValue != null) { + values.putAll(this.parse(defaultValue)); + } + + // Parsing remainder recursively + if (endIdx + this.suffix.length() < afterPrefix.length()) { + final String remainder = afterPrefix.substring(endIdx + this.suffix.length()); + values.putAll(this.parse(remainder)); + } + + // Returning values + return values; + } + + /** + * Finds the indexes of the separator and the suffix within the provided string, with: + * - at position 0, the index of the separator (can be -1) + * - at position 1, the index of the ending suffix + * + * @param string the string to search + * @return the indexes + * @throws IllegalArgumentException if no matching ending suffix has been found + */ + private int[] findIndexes(final String string) throws IllegalArgumentException { + int[] result = {-1, -1}; + int depth = 0; + + int index = 0; + while (index < string.length()) { + final String substring = string.substring(index); + + // Prefix encountered: incrementing depth + if (substring.startsWith(this.prefix)) { + // Checking that separator has been encountered + if (result[0] < 0) { + throw new IllegalArgumentException(String.format("Prefix encountered before separator in '%s'", string)); + } + + // Incrementing depth + depth++; + index += this.prefix.length(); + continue; + } + + // Opposing suffix encountered + if (substring.startsWith(this.opposingSuffix)) { + // Checking that separator has been encountered + if (result[0] < 0) { + throw new IllegalArgumentException(String.format("Opposing suffix encountered before separator in '%s'", string)); + } + + // Incrementing depth + depth++; + index += this.opposingSuffix.length(); + continue; + } + + // Separator encountered: checking depth + if (substring.startsWith(this.separator)) { + // No separator found yet: saving index + if (result[0] == -1) { + result[0] = index; + } + + // Continuing + index += this.separator.length(); + continue; + } + + // Suffix encountered: checking depth + if (substring.startsWith(this.suffix)) { + // Depth not 0: decrementing depth, ignoring and continuing + if (depth > 0) { + depth--; + index += this.suffix.length(); + continue; + } + + // End found + result[1] = index; + return result; + } + + // Neither prefix nor suffix found: continuing + index++; + } + + // No end found + throw new IllegalArgumentException(String.format("No ending suffix found in '%s'", string)); + } + +} diff --git a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/reader/ElementReader.java b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/reader/ElementReader.java index 88c2deb..a6b4d08 100644 --- a/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/reader/ElementReader.java +++ b/spring-value-annotation-processor/src/main/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/reader/ElementReader.java @@ -2,6 +2,7 @@ import com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core.ValueAnnotationMetadata; import com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core.ValueAnnotationMetadataBuilder; +import com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core.ValueAnnotationParser; import com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.exceptions.ValueAnnotationException; import javax.lang.model.element.AnnotationMirror; @@ -9,6 +10,7 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.util.Elements; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -24,7 +26,7 @@ public ElementReader(final Elements elementUtils) { this.elementUtils = elementUtils; } - public Optional read(final Element element) { + public List read(final Element element) { try { return ValueAnnotationMetadataBuilder.of(this.extractValue(element)) .type(element.asType().toString()) diff --git a/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderComplianceTest.java b/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderComplianceTest.java index 10e575d..f447768 100644 --- a/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderComplianceTest.java +++ b/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderComplianceTest.java @@ -1,10 +1,9 @@ package com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core; import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import java.util.Optional; +import java.util.List; /** * Tests defined in this class are based on following examples: @@ -19,10 +18,10 @@ public class ValueAnnotationMetadataBuilderComplianceTest { */ @Test public void testComplianceBasic1() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("string value") .build(); - Assertions.assertFalse(metadata.isPresent(), "Metadata should be null"); + Assertions.assertTrue(metadata.isEmpty(), "Metadata should be empty"); } /** @@ -30,12 +29,12 @@ public void testComplianceBasic1() { */ @Test public void testComplianceBasic2() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("${value.from.file}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("value.from.file", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("value.from.file", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -43,12 +42,12 @@ public void testComplianceBasic2() { */ @Test public void testComplianceBasic3() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("${systemValue}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("systemValue", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("systemValue", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -56,12 +55,12 @@ public void testComplianceBasic3() { */ @Test public void testComplianceBasic4() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("${priority}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("priority", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("priority", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -69,12 +68,12 @@ public void testComplianceBasic4() { */ @Test public void testComplianceBasic5() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("${listOfValues}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("listOfValues", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("listOfValues", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -84,10 +83,10 @@ public void testComplianceBasic5() { */ @Test public void testComplianceSpel1() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{systemProperties['priority']}") .build(); - Assertions.assertFalse(metadata.isPresent(), "Metadata should be null"); + Assertions.assertTrue(metadata.isEmpty(), "Metadata should be empty"); } /** @@ -97,10 +96,10 @@ public void testComplianceSpel1() { */ @Test public void testComplianceSpel2() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{systemProperties['unknown'] ?: 'some default'}") .build(); - Assertions.assertFalse(metadata.isPresent(), "Metadata should be null"); + Assertions.assertTrue(metadata.isEmpty(), "Metadata should be empty"); } /** @@ -110,10 +109,10 @@ public void testComplianceSpel2() { */ @Test public void testComplianceSpel3() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{someBean.someValue}") .build(); - Assertions.assertFalse(metadata.isPresent(), "Metadata should be null"); + Assertions.assertTrue(metadata.isEmpty(), "Metadata should be empty"); } /** @@ -121,12 +120,12 @@ public void testComplianceSpel3() { */ @Test public void testComplianceSpel4() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{'${listOfValues}'.split(',')}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("listOfValues", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("listOfValues", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -134,12 +133,12 @@ public void testComplianceSpel4() { */ @Test public void testComplianceMap1() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${valuesMap}}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("valuesMap", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("valuesMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -150,12 +149,12 @@ public void testComplianceMap1() { */ @Test public void testComplianceMap2() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${valuesMap}.key1}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("valuesMap", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("valuesMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** @@ -166,54 +165,53 @@ public void testComplianceMap2() { */ @Test public void testComplianceMap3() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${valuesMap}['unknownKey']}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("valuesMap", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("valuesMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } /** * @Value("#{${unknownMap : {key1: '1', key2: '2'}}}") */ - @Disabled("Disabled until value parser has been reviewed") @Test public void testComplianceMap4() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${unknownMap : {key1: '1', key2: '2'}}}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("unknownMap", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("{key1: '1', key2: '2'}", metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("unknownMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertEquals("{key1: '1', key2: '2'}", metadata.get(0).getDefaultValue(), "Wrong default value"); } @Test public void testComplianceMap5() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${valuesMap}['unknownKey'] ?: 5}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("valuesMap", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("valuesMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } @Test public void testComplianceMap6() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{${valuesMap}.?[value>'1']}") .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("valuesMap", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Wrong default value"); + Assertions.assertEquals(1, metadata.size(), "Metadata has wrong size"); + Assertions.assertEquals("valuesMap", metadata.get(0).getName(), "Wrong name"); + Assertions.assertNull(metadata.get(0).getDefaultValue(), "Wrong default value"); } @Test public void testComplianceMap7() { - final Optional metadata = ValueAnnotationMetadataBuilder + final List metadata = ValueAnnotationMetadataBuilder .of("#{systemProperties}") .build(); - Assertions.assertFalse(metadata.isPresent(), "Metadata should be null"); + Assertions.assertTrue(metadata.isEmpty(), "Metadata should be empty"); } } diff --git a/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderTest.java b/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderTest.java deleted file mode 100644 index d4efae6..0000000 --- a/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationMetadataBuilderTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core; - -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -public class ValueAnnotationMetadataBuilderTest { - - @Test - public void testSimpleDefinition() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("${custom.property}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Default value should not be defined"); - } - - @Test - public void testSimpleDefinitionWithDefaultValue() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("${custom.property:defaultVal}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("defaultVal", metadata.get().getDefaultValue(), "Wrong default value"); - } - - @Test - public void testDefinitionWithSpecialCharacters() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("${custom.property-path.upperCase.with_underscores.dot}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property-path.upperCase.with_underscores.dot", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Default value should not be defined"); - } - - @Test - public void testSpelDefinition() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{${custom.property}}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Default value should not be defined"); - } - - @Test - public void testSpelDefinitionWithMethodCall() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{'${custom.property}'.split(',')}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Default value should not be defined"); - } - - @Test - public void testSpelDefinitionWithSpace() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{new ${custom.property}()}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertNull(metadata.get().getDefaultValue(), "Default value should not be defined"); - } - - @Test - public void testSpelDefinitionWithMethodCallAndDefaultValue() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{'${custom.property:defaultVal}'.split(',')}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("defaultVal", metadata.get().getDefaultValue(), "Wrong default value"); - } - - @Test - public void testSpelDefinitionWithSpaceAndDefaultValue() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{new ${custom.property:defaultVal}()}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("defaultVal", metadata.get().getDefaultValue(), "Wrong default value"); - } - - @Disabled("Disabled until value parser supports extracting multiple values") - @Test - public void testSpelDefinitionWithMultipleValues() { - final Optional metadata = ValueAnnotationMetadataBuilder - .of("#{new FakeConstructor(${custom.property1:defaultVal}, ${custom.property2:defaultVal})}") - .build(); - Assertions.assertTrue(metadata.isPresent(), "Metadata should not be null"); - Assertions.assertEquals("custom.property1", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("custom.property2", metadata.get().getName(), "Wrong name"); - Assertions.assertEquals("defaultVal", metadata.get().getDefaultValue(), "Wrong default value"); - } - -} diff --git a/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParserTest.java b/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParserTest.java new file mode 100644 index 0000000..c8923a5 --- /dev/null +++ b/spring-value-annotation-processor/src/test/java/com/github/egoettelmann/spring/configuration/extensions/annotationprocessor/value/core/ValueAnnotationParserTest.java @@ -0,0 +1,141 @@ +package com.github.egoettelmann.spring.configuration.extensions.annotationprocessor.value.core; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +public class ValueAnnotationParserTest { + + private final ValueAnnotationParser parser = new ValueAnnotationParser(); + + @Test + public void testSimpleDefinition() { + final Map values = this.parser.parse("${custom.property}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertNull(values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSimpleDefinitionWithDefaultValue() { + final Map values = this.parser.parse("${custom.property:defaultVal}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertEquals("defaultVal", values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testNestedDefinition() { + final Map values = this.parser.parse("${custom.property1:${custom.property2}}"); + Assertions.assertEquals(2, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property1"), "Parsed values should contain 'a'"); + Assertions.assertEquals("${custom.property2}", values.get("custom.property1"), "Wrong parsed default value for 'a'"); + Assertions.assertTrue(values.containsKey("custom.property2"), "Parsed values should contain 'b'"); + Assertions.assertNull(values.get("custom.property2"), "Wrong parsed default value for 'b'"); + } + + @Test + public void testDoubleNestedDefinition() { + final Map values = this.parser.parse("${a:${b:${c}}}"); + Assertions.assertEquals(3, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("a"), "Parsed values should contain 'a'"); + Assertions.assertEquals("${b:${c}}", values.get("a"), "Wrong parsed default value for 'a'"); + Assertions.assertTrue(values.containsKey("b"), "Parsed values should contain 'b'"); + Assertions.assertEquals("${c}", values.get("b"), "Wrong parsed default value for 'b'"); + Assertions.assertTrue(values.containsKey("c"), "Parsed values should contain 'c'"); + Assertions.assertNull(values.get("c"), "Wrong parsed default value for 'c'"); + } + + @Test + public void testDoubleNestedDefinitionWithDefaultValue() { + final Map values = this.parser.parse("${a:${b:${c:defaultVal}}}"); + Assertions.assertEquals(3, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("a"), "Parsed values should contain 'a'"); + Assertions.assertEquals("${b:${c:defaultVal}}", values.get("a"), "Wrong parsed default value for 'a'"); + Assertions.assertTrue(values.containsKey("b"), "Parsed values should contain 'b'"); + Assertions.assertEquals("${c:defaultVal}", values.get("b"), "Wrong parsed default value for 'b'"); + Assertions.assertTrue(values.containsKey("c"), "Parsed values should contain 'c'"); + Assertions.assertEquals("defaultVal", values.get("c"), "Wrong parsed default value for 'c'"); + } + + @Test + public void testDefinitionWithSpecialCharacters() { + final Map values = this.parser.parse("${custom.property-path.upperCase.with_underscores.dot}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property-path.upperCase.with_underscores.dot"), "Wrong parsed value"); + Assertions.assertNull(values.get("custom.property-path.upperCase.with_underscores.dot"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinition() { + final Map values = this.parser.parse("#{${custom.property}}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertNull(values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinitionWithMethodCall() { + final Map values = this.parser.parse("#{'${custom.property}'.split(',')}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertNull(values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinitionWithSpace() { + final Map values = this.parser.parse("#{new ${custom.property}()}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertNull(values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinitionWithMethodCallAndDefaultValue() { + final Map values = this.parser.parse("#{'${custom.property:defaultVal}'.split(',')}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertEquals("defaultVal", values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinitionWithSpaceAndDefaultValue() { + final Map values = this.parser.parse("#{new ${custom.property:defaultVal}()}"); + Assertions.assertEquals(1, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property"), "Wrong parsed value"); + Assertions.assertEquals("defaultVal", values.get("custom.property"), "Wrong parsed default value"); + } + + @Test + public void testSpelDefinitionWithMultipleValues() { + final Map values = this.parser.parse("#{new FakeConstructor(${custom.property1:defaultVal}, ${custom.property2:defaultVal})}"); + Assertions.assertEquals(2, values.size(), "Wrong number of parsed values"); + Assertions.assertTrue(values.containsKey("custom.property1"), "Wrong parsed value"); + Assertions.assertEquals("defaultVal", values.get("custom.property1"), "Wrong parsed default value"); + Assertions.assertTrue(values.containsKey("custom.property2"), "Wrong parsed value"); + Assertions.assertEquals("defaultVal", values.get("custom.property2"), "Wrong parsed default value"); + } + + @Test + public void testInvalidDefinition() { + final IllegalArgumentException thrown = Assertions.assertThrows(IllegalArgumentException.class, () -> { + this.parser.parse("${${a}}"); + }); + } + + @Test + public void testInvalidDefinition2() { + final IllegalArgumentException thrown = Assertions.assertThrows(IllegalArgumentException.class, () -> { + this.parser.parse("${{a}}"); + }); + } + + @Test + public void testInvalidDefinition3() { + final IllegalArgumentException thrown = Assertions.assertThrows(IllegalArgumentException.class, () -> { + this.parser.parse("${{a}"); + }); + } + +}