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