From 9e8ab747e4b96312a3ff71a8df2acd227eeb856a Mon Sep 17 00:00:00 2001 From: md_5 Date: Mon, 3 Feb 2020 13:25:47 +1100 Subject: [PATCH] Add JsonConfiguration support to bungeecord-config Thanks @FelixKlauke for the idea in #2364, however this implementation was designed to mirror as much of the existing YamlConfiguration as possible and have Gson as an optional depend. --- api/pom.xml | 6 + config/pom.xml | 8 + .../bungee/config/ConfigurationProvider.java | 16 +- .../md_5/bungee/config/JsonConfiguration.java | 114 +++++++++ .../config/CompoundConfigurationTest.java | 229 ++++++++++++++++++ .../bungee/config/YamlConfigurationTest.java | 143 ----------- 6 files changed, 372 insertions(+), 144 deletions(-) create mode 100644 config/src/main/java/net/md_5/bungee/config/JsonConfiguration.java create mode 100644 config/src/test/java/net/md_5/bungee/config/CompoundConfigurationTest.java delete mode 100644 config/src/test/java/net/md_5/bungee/config/YamlConfigurationTest.java diff --git a/api/pom.xml b/api/pom.xml index 5d782001..a3f6a6a9 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -49,5 +49,11 @@ ${netty.version} compile + + org.yaml + snakeyaml + 1.25 + compile + diff --git a/config/pom.xml b/config/pom.xml index 4b44d27e..95449790 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -19,11 +19,19 @@ Generic java configuration API intended for use with BungeeCord + + com.google.code.gson + gson + 2.8.0 + compile + true + org.yaml snakeyaml 1.25 compile + true diff --git a/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java b/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java index eece8870..0118fca2 100644 --- a/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java +++ b/config/src/main/java/net/md_5/bungee/config/ConfigurationProvider.java @@ -15,7 +15,21 @@ public abstract class ConfigurationProvider static { - providers.put( YamlConfiguration.class, new YamlConfiguration() ); + try + { + providers.put( YamlConfiguration.class, new YamlConfiguration() ); + } catch ( NoClassDefFoundError ex ) + { + // Ignore, no SnakeYAML + } + + try + { + providers.put( JsonConfiguration.class, new JsonConfiguration() ); + } catch ( NoClassDefFoundError ex ) + { + // Ignore, no Gson + } } public static ConfigurationProvider getProvider(Class provider) diff --git a/config/src/main/java/net/md_5/bungee/config/JsonConfiguration.java b/config/src/main/java/net/md_5/bungee/config/JsonConfiguration.java new file mode 100644 index 00000000..8ad9c20e --- /dev/null +++ b/config/src/main/java/net/md_5/bungee/config/JsonConfiguration.java @@ -0,0 +1,114 @@ +package net.md_5.bungee.config; + +import com.google.common.base.Charsets; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.io.Reader; +import java.io.Writer; +import java.lang.reflect.Type; +import java.util.LinkedHashMap; +import java.util.Map; +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = AccessLevel.PACKAGE) +public class JsonConfiguration extends ConfigurationProvider +{ + + private final Gson json = new GsonBuilder().serializeNulls().setPrettyPrinting().registerTypeAdapter( Configuration.class, new JsonSerializer() + { + @Override + public JsonElement serialize(Configuration src, Type typeOfSrc, JsonSerializationContext context) + { + return context.serialize( ( (Configuration) src ).self ); + } + } ).create(); + + @Override + public void save(Configuration config, File file) throws IOException + { + try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) ) + { + save( config, writer ); + } + } + + @Override + public void save(Configuration config, Writer writer) + { + json.toJson( config.self, writer ); + } + + @Override + public Configuration load(File file) throws IOException + { + return load( file, null ); + } + + @Override + public Configuration load(File file, Configuration defaults) throws IOException + { + try ( FileInputStream is = new FileInputStream( file ) ) + { + return load( is, defaults ); + } + } + + @Override + public Configuration load(Reader reader) + { + return load( reader, null ); + } + + @Override + @SuppressWarnings("unchecked") + public Configuration load(Reader reader, Configuration defaults) + { + Map map = json.fromJson( reader, LinkedHashMap.class ); + if ( map == null ) + { + map = new LinkedHashMap<>(); + } + return new Configuration( map, defaults ); + } + + @Override + public Configuration load(InputStream is) + { + return load( is, null ); + } + + @Override + public Configuration load(InputStream is, Configuration defaults) + { + return load( new InputStreamReader( is, Charsets.UTF_8 ), defaults ); + } + + @Override + public Configuration load(String string) + { + return load( string, null ); + } + + @Override + @SuppressWarnings("unchecked") + public Configuration load(String string, Configuration defaults) + { + Map map = json.fromJson( string, LinkedHashMap.class ); + if ( map == null ) + { + map = new LinkedHashMap<>(); + } + return new Configuration( map, defaults ); + } +} diff --git a/config/src/test/java/net/md_5/bungee/config/CompoundConfigurationTest.java b/config/src/test/java/net/md_5/bungee/config/CompoundConfigurationTest.java new file mode 100644 index 00000000..f5573331 --- /dev/null +++ b/config/src/test/java/net/md_5/bungee/config/CompoundConfigurationTest.java @@ -0,0 +1,229 @@ +package net.md_5.bungee.config; + +import java.io.StringReader; +import java.io.StringWriter; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RequiredArgsConstructor +@RunWith(Parameterized.class) +public class CompoundConfigurationTest +{ + + @Parameters(name = "{0}") + public static Iterable data() + { + // CHECKSTYLE:OFF + return Arrays.asList( new Object[][] + { + { + // provider + YamlConfiguration.class, + // testDocument + "" + + "receipt: Oz-Ware Purchase Invoice\n" + + "date: 2012-08-06\n" + + "customer:\n" + + " given: Dorothy\n" + + " family: Gale\n" + + "\n" + + "items:\n" + + " - part_no: A4786\n" + + " descrip: Water Bucket (Filled)\n" + + " price: 1.47\n" + + " quantity: 4\n" + + "\n" + + " - part_no: E1628\n" + + " descrip: High Heeled \"Ruby\" Slippers\n" + + " size: 8\n" + + " price: 100.27\n" + + " quantity: 1\n" + + "\n" + + "bill-to: &id001\n" + + " street: |\n" + + " 123 Tornado Alley\n" + + " Suite 16\n" + + " city: East Centerville\n" + + " state: KS\n" + + "\n" + + "ship-to: *id001\n" + + "\n" + + "specialDelivery: >\n" + + " Follow the Yellow Brick\n" + + " Road to the Emerald City.\n" + + " Pay no attention to the\n" + + " man behind the curtain.", + // numberTest + "" + + "someKey:\n" + + " 1: 1\n" + + " 2: 2\n" + + " 3: 3\n" + + " 4: 4", + // nullTest + "" + + "null:\n" + + " null: object\n" + + " object: null\n" + }, + { + // provider + JsonConfiguration.class, + // testDocument + "" + + "{\n" + + " \"customer\": {\n" + + " \"given\": \"Dorothy\", \n" + + " \"family\": \"Gale\"\n" + + " }, \n" + + " \"ship-to\": {\n" + + " \"city\": \"East Centerville\", \n" + + " \"state\": \"KS\", \n" + + " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n" + + " }, \n" + + " \"bill-to\": {\n" + + " \"city\": \"East Centerville\", \n" + + " \"state\": \"KS\", \n" + + " \"street\": \"123 Tornado Alley\\nSuite 16\\n\"\n" + + " }, \n" + + " \"date\": \"2012-08-06\", \n" + + " \"items\": [\n" + + " {\n" + + " \"part_no\": \"A4786\", \n" + + " \"price\": 1.47, \n" + + " \"descrip\": \"Water Bucket (Filled)\", \n" + + " \"quantity\": 4\n" + + " }, \n" + + " {\n" + + " \"part_no\": \"E1628\", \n" + + " \"descrip\": \"High Heeled \\\"Ruby\\\" Slippers\", \n" + + " \"price\": 100.27, \n" + + " \"quantity\": 1, \n" + + " \"size\": 8\n" + + " }\n" + + " ], \n" + + " \"receipt\": \"Oz-Ware Purchase Invoice\", \n" + + " \"specialDelivery\": \"Follow the Yellow Brick Road to the Emerald City. Pay no attention to the man behind the curtain.\"\n" + + "}", + // numberTest + "" + + "{\n" + + " \"someKey\": {\n" + + " \"1\": 1, \n" + + " \"2\": 2, \n" + + " \"3\": 3, \n" + + " \"4\": 4\n" + + " }\n" + + "}", + // nullTest + "" + + "{\n" + + " \"null\": {\n" + + " \"null\": \"object\", \n" + + " \"object\": null\n" + + " }\n" + + "}" + } + } ); + // CHECKSTYLE:ON + } + // + private final Class provider; + private final String testDocument; + private final String numberTest; + private final String nullTest; + + @Test + public void testConfig() throws Exception + { + Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument ); + testSection( conf ); + + StringWriter sw = new StringWriter(); + ConfigurationProvider.getProvider( provider ).save( conf, sw ); + + // Check nulls were saved, see #1094 + Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) ); + + conf = ConfigurationProvider.getProvider( provider ).load( new StringReader( sw.toString() ) ); + conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back + testSection( conf ); + } + + private void testSection(Configuration conf) + { + Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) ); + // Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() ); + + Configuration customer = conf.getSection( "customer" ); + Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) ); + Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) ); + + List items = conf.getList( "items" ); + Map item = (Map) items.get( 0 ); + Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) ); + + conf.set( "receipt", null ); + Assert.assertEquals( null, conf.get( "receipt" ) ); + Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) ); + + Configuration newSection = conf.getSection( "new.section" ); + newSection.set( "value", "foo" ); + Assert.assertEquals( "foo", conf.get( "new.section.value" ) ); + + conf.set( "other.new.section", "bar" ); + Assert.assertEquals( "bar", conf.get( "other.new.section" ) ); + + Assert.assertTrue( conf.contains( "customer.given" ) ); + Assert.assertTrue( customer.contains( "given" ) ); + + Assert.assertFalse( conf.contains( "customer.foo" ) ); + Assert.assertFalse( customer.contains( "foo" ) ); + } + + @Test + public void testNumberedKeys() + { + Configuration conf = ConfigurationProvider.getProvider( provider ).load( numberTest ); + + Configuration section = conf.getSection( "someKey" ); + for ( String key : section.getKeys() ) + { + // empty + } + } + + @Test + public void testNull() + { + Configuration conf = ConfigurationProvider.getProvider( provider ).load( nullTest ); + + Assert.assertEquals( "object", conf.get( "null.null" ) ); + Assert.assertEquals( "object", conf.getSection( "null" ).get( "null" ) ); + + Assert.assertEquals( null, conf.get( "null.object" ) ); + Assert.assertEquals( "", conf.getString( "null.object" ) ); + } + + @Test + public void testMapAddition() + { + Configuration conf = ConfigurationProvider.getProvider( provider ).load( testDocument ); + + conf.set( "addition", Collections.singletonMap( "foo", "bar" ) ); + + // Order matters + Assert.assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) ); + Assert.assertEquals( "bar", conf.getString( "addition.foo" ) ); + + Assert.assertTrue( conf.get( "addition" ) instanceof Configuration ); + } +} diff --git a/config/src/test/java/net/md_5/bungee/config/YamlConfigurationTest.java b/config/src/test/java/net/md_5/bungee/config/YamlConfigurationTest.java deleted file mode 100644 index a14c22f2..00000000 --- a/config/src/test/java/net/md_5/bungee/config/YamlConfigurationTest.java +++ /dev/null @@ -1,143 +0,0 @@ -package net.md_5.bungee.config; - -import java.io.StringReader; -import java.io.StringWriter; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import org.junit.Assert; -import org.junit.Test; - -public class YamlConfigurationTest -{ - - private static final String TEST_DOCUMENT = "" - + "receipt: Oz-Ware Purchase Invoice\n" - + "date: 2012-08-06\n" - + "customer:\n" - + " given: Dorothy\n" - + " family: Gale\n" - + "\n" - + "items:\n" - + " - part_no: A4786\n" - + " descrip: Water Bucket (Filled)\n" - + " price: 1.47\n" - + " quantity: 4\n" - + "\n" - + " - part_no: E1628\n" - + " descrip: High Heeled \"Ruby\" Slippers\n" - + " size: 8\n" - + " price: 100.27\n" - + " quantity: 1\n" - + "\n" - + "bill-to: &id001\n" - + " street: |\n" - + " 123 Tornado Alley\n" - + " Suite 16\n" - + " city: East Centerville\n" - + " state: KS\n" - + "\n" - + "ship-to: *id001\n" - + "\n" - + "specialDelivery: >\n" - + " Follow the Yellow Brick\n" - + " Road to the Emerald City.\n" - + " Pay no attention to the\n" - + " man behind the curtain."; - private static final String NUMBER_TEST = "" - + "someKey:\n" - + " 1: 1\n" - + " 2: 2\n" - + " 3: 3\n" - + " 4: 4"; - private static final String NULL_TEST = "" - + "null:\n" - + " null: object\n" - + " object: null\n"; - - @Test - public void testConfig() throws Exception - { - Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( TEST_DOCUMENT ); - testSection( conf ); - - StringWriter sw = new StringWriter(); - ConfigurationProvider.getProvider( YamlConfiguration.class ).save( conf, sw ); - - // Check nulls were saved, see #1094 - Assert.assertFalse( "Config contains null", sw.toString().contains( "null" ) ); - - conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( new StringReader( sw.toString() ) ); - conf.set( "receipt", "Oz-Ware Purchase Invoice" ); // Add it back - testSection( conf ); - } - - private void testSection(Configuration conf) - { - Assert.assertEquals( "receipt", "Oz-Ware Purchase Invoice", conf.getString( "receipt" ) ); - // Assert.assertEquals( "date", "2012-08-06", conf.get( "date" ).toString() ); - - Configuration customer = conf.getSection( "customer" ); - Assert.assertEquals( "customer.given", "Dorothy", customer.getString( "given" ) ); - Assert.assertEquals( "customer.given", "Dorothy", conf.getString( "customer.given" ) ); - - List items = conf.getList( "items" ); - Map item = (Map) items.get( 0 ); - Assert.assertEquals( "items[0].part_no", "A4786", item.get( "part_no" ) ); - - conf.set( "receipt", null ); - Assert.assertEquals( null, conf.get( "receipt" ) ); - Assert.assertEquals( "foo", conf.get( "receipt", "foo" ) ); - - Configuration newSection = conf.getSection( "new.section" ); - newSection.set( "value", "foo" ); - Assert.assertEquals( "foo", conf.get( "new.section.value" ) ); - - conf.set( "other.new.section", "bar" ); - Assert.assertEquals( "bar", conf.get( "other.new.section" ) ); - - Assert.assertTrue( conf.contains( "customer.given" ) ); - Assert.assertTrue( customer.contains( "given" ) ); - - Assert.assertFalse( conf.contains( "customer.foo" ) ); - Assert.assertFalse( customer.contains( "foo" ) ); - } - - @Test - public void testNumberedKeys() - { - Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( NUMBER_TEST ); - - Configuration section = conf.getSection( "someKey" ); - for ( String key : section.getKeys() ) - { - // empty - } - } - - @Test - public void testNull() - { - Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( NULL_TEST ); - - Assert.assertEquals( "object", conf.get( "null.null" ) ); - Assert.assertEquals( "object", conf.getSection( "null" ).get( "null" ) ); - - Assert.assertEquals( null, conf.get( "null.object" ) ); - Assert.assertEquals( "", conf.getString( "null.object" ) ); - } - - @Test - public void testMapAddition() - { - Configuration conf = ConfigurationProvider.getProvider( YamlConfiguration.class ).load( TEST_DOCUMENT ); - - conf.set( "addition", Collections.singletonMap( "foo", "bar" ) ); - - // Order matters - Assert.assertEquals( "bar", conf.getSection( "addition" ).getString( "foo" ) ); - Assert.assertEquals( "bar", conf.getString( "addition.foo" ) ); - - Assert.assertTrue( conf.get( "addition" ) instanceof Configuration ); - } -}