diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java index 43386a95841..5b0bedf7186 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/json-shade/java/org/springframework/boot/configurationprocessor/json/JSONObject.java @@ -18,8 +18,8 @@ package org.springframework.boot.configurationprocessor.json; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.Map; // Note: this class was written without inspecting the non-free org.json source code. @@ -111,7 +111,7 @@ public class JSONObject { * Creates a {@code JSONObject} with no name/value mappings. */ public JSONObject() { - this.nameValuePairs = new HashMap<>(); + this.nameValuePairs = new LinkedHashMap<>(); } /** diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JSONOrderedObject.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JSONOrderedObject.java deleted file mode 100644 index bda84dd8c58..00000000000 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JSONOrderedObject.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2012-2018 the original author or authors. - * - * Licensed 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.springframework.boot.configurationprocessor.metadata; - -import java.util.Iterator; -import java.util.LinkedHashSet; -import java.util.Set; - -import org.springframework.boot.configurationprocessor.json.JSONException; -import org.springframework.boot.configurationprocessor.json.JSONObject; - -/** - * Extension to {@link JSONObject} that remembers the order of inserts. - * - * @author Stephane Nicoll - * @author Phillip Webb - */ -@SuppressWarnings("rawtypes") -class JSONOrderedObject extends JSONObject { - - private Set keys = new LinkedHashSet<>(); - - @Override - public JSONObject put(String key, Object value) throws JSONException { - this.keys.add(key); - return super.put(key, value); - } - - @Override - public Iterator keys() { - return this.keys.iterator(); - } - -} diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java index 2aa14d3b54e..874c022744c 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonConverter.java @@ -18,7 +18,10 @@ package org.springframework.boot.configurationprocessor.metadata; import java.lang.reflect.Array; import java.util.Collection; +import java.util.Comparator; +import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import org.springframework.boot.configurationprocessor.json.JSONArray; import org.springframework.boot.configurationprocessor.json.JSONObject; @@ -32,10 +35,15 @@ import org.springframework.boot.configurationprocessor.metadata.ItemMetadata.Ite */ class JsonConverter { + private static final ItemMetadataComparator ITEM_COMPARATOR = new ItemMetadataComparator(); + public JSONArray toJsonArray(ConfigurationMetadata metadata, ItemType itemType) throws Exception { JSONArray jsonArray = new JSONArray(); - for (ItemMetadata item : metadata.getItems()) { + List items = metadata.getItems().stream() + .filter((item) -> item.isOfItemType(itemType)).sorted(ITEM_COMPARATOR) + .collect(Collectors.toList()); + for (ItemMetadata item : items) { if (item.isOfItemType(itemType)) { jsonArray.put(toJsonObject(item)); } @@ -52,7 +60,7 @@ class JsonConverter { } public JSONObject toJsonObject(ItemMetadata item) throws Exception { - JSONObject jsonObject = new JSONOrderedObject(); + JSONObject jsonObject = new JSONObject(); jsonObject.put("name", item.getName()); putIfPresent(jsonObject, "type", item.getType()); putIfPresent(jsonObject, "description", item.getDescription()); @@ -81,7 +89,7 @@ class JsonConverter { } private JSONObject toJsonObject(ItemHint hint) throws Exception { - JSONObject jsonObject = new JSONOrderedObject(); + JSONObject jsonObject = new JSONObject(); jsonObject.put("name", hint.getName()); if (!hint.getValues().isEmpty()) { jsonObject.put("values", getItemHintValues(hint)); @@ -101,7 +109,7 @@ class JsonConverter { } private JSONObject getItemHintValue(ItemHint.ValueHint value) throws Exception { - JSONObject result = new JSONOrderedObject(); + JSONObject result = new JSONObject(); putHintValue(result, value.getValue()); putIfPresent(result, "description", value.getDescription()); return result; @@ -117,10 +125,10 @@ class JsonConverter { private JSONObject getItemHintProvider(ItemHint.ValueProvider provider) throws Exception { - JSONObject result = new JSONOrderedObject(); + JSONObject result = new JSONObject(); result.put("name", provider.getName()); if (provider.getParameters() != null && !provider.getParameters().isEmpty()) { - JSONObject parameters = new JSONOrderedObject(); + JSONObject parameters = new JSONObject(); for (Map.Entry entry : provider.getParameters().entrySet()) { parameters.put(entry.getKey(), extractItemValue(entry.getValue())); } @@ -160,4 +168,34 @@ class JsonConverter { return defaultValue; } + private static class ItemMetadataComparator implements Comparator { + + @Override + public int compare(ItemMetadata o1, ItemMetadata o2) { + if (o1.isOfItemType(ItemType.GROUP)) { + return compareGroup(o1, o2); + } + return compareProperty(o1, o2); + } + + private int compareGroup(ItemMetadata o1, ItemMetadata o2) { + return o1.getName().compareTo(o2.getName()); + } + + private int compareProperty(ItemMetadata o1, ItemMetadata o2) { + if (isDeprecated(o1) && !isDeprecated(o2)) { + return 1; + } + if (isDeprecated(o2) && !isDeprecated(o1)) { + return -1; + } + return o1.getName().compareTo(o2.getName()); + } + + private boolean isDeprecated(ItemMetadata item) { + return item.getDeprecation() != null; + } + + } + } diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java index 4230638bd44..8271bd88bd3 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/main/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshaller.java @@ -45,7 +45,7 @@ public class JsonMarshaller { public void write(ConfigurationMetadata metadata, OutputStream outputStream) throws IOException { try { - JSONObject object = new JSONOrderedObject(); + JSONObject object = new JSONObject(); JsonConverter converter = new JsonConverter(); object.put("groups", converter.toJsonArray(metadata, ItemType.GROUP)); object.put("properties", converter.toJsonArray(metadata, ItemType.PROPERTY)); diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java index 98d66f7af71..613b6db9267 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/ConfigurationMetadataAnnotationProcessorTests.java @@ -22,6 +22,8 @@ import java.io.IOException; import java.time.Duration; import java.util.Arrays; import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; import org.junit.Before; import org.junit.Rule; @@ -822,9 +824,26 @@ public class ConfigurationMetadataAnnotationProcessorTests { ConfigurationMetadata metadata = compile(SimpleProperties.class, SimpleConflictingProperties.class); assertThat(metadata.getItems()).hasSize(6); - assertThat(metadata).has(Metadata.withProperty("simple.flag", Boolean.class) - .fromSource(SimpleProperties.class).withDescription("A simple flag.") - .withDeprecation(null, null).withDefaultValue(true)); + List items = metadata.getItems().stream() + .filter((item) -> item.getName().equals("simple.flag")) + .collect(Collectors.toList()); + assertThat(items).hasSize(2); + ItemMetadata matchingProperty = items.stream() + .filter((item) -> item.getType().equals(Boolean.class.getName())) + .findFirst().orElse(null); + assertThat(matchingProperty).isNotNull(); + assertThat(matchingProperty.getDefaultValue()).isEqualTo(true); + assertThat(matchingProperty.getSourceType()) + .isEqualTo(SimpleProperties.class.getName()); + assertThat(matchingProperty.getDescription()).isEqualTo("A simple flag."); + ItemMetadata nonMatchingProperty = items.stream() + .filter((item) -> item.getType().equals(String.class.getName())) + .findFirst().orElse(null); + assertThat(nonMatchingProperty).isNotNull(); + assertThat(nonMatchingProperty.getDefaultValue()).isEqualTo("hello"); + assertThat(nonMatchingProperty.getSourceType()) + .isEqualTo(SimpleConflictingProperties.class.getName()); + assertThat(nonMatchingProperty.getDescription()).isNull(); } @Test diff --git a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java index a9b0b8b78df..f19f2f73344 100644 --- a/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java +++ b/spring-boot-project/spring-boot-tools/spring-boot-configuration-processor/src/test/java/org/springframework/boot/configurationprocessor/metadata/JsonMarshallerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2017 the original author or authors. + * Copyright 2012-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.boot.configurationprocessor.metadata; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; +import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Collections; @@ -82,4 +83,51 @@ public class JsonMarshallerTests { .withProvider("second")); } + @Test + public void marshallOrderItems() throws IOException { + ConfigurationMetadata metadata = new ConfigurationMetadata(); + metadata.add(ItemHint.newHint("fff")); + metadata.add(ItemHint.newHint("eee")); + metadata.add(ItemMetadata.newProperty("com.example.bravo", "bbb", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newProperty("com.example.bravo", "aaa", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newProperty("com.example.alpha", "ddd", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newProperty("com.example.alpha", "ccc", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newGroup("com.acme.bravo", + "com.example.AnotherTestProperties", null, null)); + metadata.add(ItemMetadata.newGroup("com.acme.alpha", "com.example.TestProperties", + null, null)); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JsonMarshaller marshaller = new JsonMarshaller(); + marshaller.write(metadata, outputStream); + String json = new String(outputStream.toByteArray()); + assertThat(json).containsSubsequence("\"groups\"", "\"com.acme.alpha\"", + "\"com.acme.bravo\"", "\"properties\"", "\"com.example.alpha.ccc\"", + "\"com.example.alpha.ddd\"", "\"com.example.bravo.aaa\"", + "\"com.example.bravo.bbb\"", "\"hints\"", "\"eee\"", "\"fff\""); + } + + @Test + public void marshallPutDeprecatedItemsAtTheEnd() throws IOException { + ConfigurationMetadata metadata = new ConfigurationMetadata(); + metadata.add(ItemMetadata.newProperty("com.example.bravo", "bbb", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newProperty("com.example.bravo", "aaa", null, null, + null, null, null, new ItemDeprecation(null, null, "warning"))); + metadata.add(ItemMetadata.newProperty("com.example.alpha", "ddd", null, null, + null, null, null, null)); + metadata.add(ItemMetadata.newProperty("com.example.alpha", "ccc", null, null, + null, null, null, new ItemDeprecation(null, null, "warning"))); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + JsonMarshaller marshaller = new JsonMarshaller(); + marshaller.write(metadata, outputStream); + String json = new String(outputStream.toByteArray()); + assertThat(json).containsSubsequence("\"properties\"", + "\"com.example.alpha.ddd\"", "\"com.example.bravo.bbb\"", + "\"com.example.alpha.ccc\"", "\"com.example.bravo.aaa\""); + } + }