aboutsummaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/Config.java19
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ExclusionUtils.java (renamed from src/main/java/cc/polyfrost/oneconfig/config/gson/ExclusionUtils.java)2
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/NonProfileSpecificExclusionStrategy.java (renamed from src/main/java/cc/polyfrost/oneconfig/config/gson/NonProfileSpecificExclusionStrategy.java)2
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ProfileExclusionStrategy.java (renamed from src/main/java/cc/polyfrost/oneconfig/config/gson/ProfileExclusionStrategy.java)2
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/GsonContext.java123
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/InterfaceAdapterFactory.java192
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserialization.java63
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserializes.java72
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonSerialization.java70
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/Reflection.java513
10 files changed, 1051 insertions, 7 deletions
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/Config.java b/src/main/java/cc/polyfrost/oneconfig/config/Config.java
index eb74d2a..87ade90 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/Config.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/Config.java
@@ -34,8 +34,9 @@ import cc.polyfrost.oneconfig.config.data.PageLocation;
import cc.polyfrost.oneconfig.config.elements.BasicOption;
import cc.polyfrost.oneconfig.config.elements.OptionPage;
import cc.polyfrost.oneconfig.config.elements.OptionSubcategory;
-import cc.polyfrost.oneconfig.config.gson.NonProfileSpecificExclusionStrategy;
-import cc.polyfrost.oneconfig.config.gson.ProfileExclusionStrategy;
+import cc.polyfrost.oneconfig.config.gson.exclusion.NonProfileSpecificExclusionStrategy;
+import cc.polyfrost.oneconfig.config.gson.exclusion.ProfileExclusionStrategy;
+import cc.polyfrost.oneconfig.config.gson.gsoninterface.InterfaceAdapterFactory;
import cc.polyfrost.oneconfig.gui.elements.config.ConfigKeyBind;
import cc.polyfrost.oneconfig.gui.OneConfigGui;
import cc.polyfrost.oneconfig.gui.elements.config.ConfigPageButton;
@@ -63,8 +64,18 @@ import java.util.function.Supplier;
public class Config {
public final transient HashMap<String, BasicOption> optionNames = new HashMap<>();
transient protected final String configFile;
- transient protected final Gson gson = new GsonBuilder().setExclusionStrategies(new ProfileExclusionStrategy()).excludeFieldsWithModifiers(Modifier.TRANSIENT).setPrettyPrinting().create();
- transient protected final Gson nonProfileSpecificGson = new GsonBuilder().setExclusionStrategies(new NonProfileSpecificExclusionStrategy()).excludeFieldsWithModifiers(Modifier.TRANSIENT).setPrettyPrinting().create();
+ transient protected final Gson gson = new GsonBuilder()
+ .setExclusionStrategies(new ProfileExclusionStrategy())
+ .registerTypeAdapterFactory(new InterfaceAdapterFactory())
+ .excludeFieldsWithModifiers(Modifier.TRANSIENT)
+ .setPrettyPrinting()
+ .create();
+ transient protected final Gson nonProfileSpecificGson = new GsonBuilder()
+ .setExclusionStrategies(new NonProfileSpecificExclusionStrategy())
+ .registerTypeAdapterFactory(new InterfaceAdapterFactory())
+ .excludeFieldsWithModifiers(Modifier.TRANSIENT)
+ .setPrettyPrinting()
+ .create();
transient protected final HashMap<Field, Object> defaults = new HashMap<>();
transient public Mod mod;
public boolean enabled;
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/ExclusionUtils.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ExclusionUtils.java
index 031efa6..efad110 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/gson/ExclusionUtils.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ExclusionUtils.java
@@ -24,7 +24,7 @@
* <https://polyfrost.cc/legal/oneconfig/additional-terms>
*/
-package cc.polyfrost.oneconfig.config.gson;
+package cc.polyfrost.oneconfig.config.gson.exclusion;
public class ExclusionUtils {
protected static boolean isSuperClassOf(Class<?> clazz, Class<?> parentClass) {
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/NonProfileSpecificExclusionStrategy.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/NonProfileSpecificExclusionStrategy.java
index 82dd1c5..ae4e7ac 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/gson/NonProfileSpecificExclusionStrategy.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/NonProfileSpecificExclusionStrategy.java
@@ -24,7 +24,7 @@
* <https://polyfrost.cc/legal/oneconfig/additional-terms>
*/
-package cc.polyfrost.oneconfig.config.gson;
+package cc.polyfrost.oneconfig.config.gson.exclusion;
import cc.polyfrost.oneconfig.config.Config;
import cc.polyfrost.oneconfig.config.annotations.Exclude;
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/ProfileExclusionStrategy.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ProfileExclusionStrategy.java
index 3f0a97b..e15a6ec 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/gson/ProfileExclusionStrategy.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/exclusion/ProfileExclusionStrategy.java
@@ -24,7 +24,7 @@
* <https://polyfrost.cc/legal/oneconfig/additional-terms>
*/
-package cc.polyfrost.oneconfig.config.gson;
+package cc.polyfrost.oneconfig.config.gson.exclusion;
import cc.polyfrost.oneconfig.config.Config;
import cc.polyfrost.oneconfig.config.annotations.Exclude;
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/GsonContext.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/GsonContext.java
new file mode 100644
index 0000000..e27a362
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/GsonContext.java
@@ -0,0 +1,123 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonIOException;
+import com.google.gson.TypeAdapter;
+import com.google.gson.internal.bind.JsonTreeReader;
+import com.google.gson.internal.bind.JsonTreeWriter;
+
+import java.io.IOException;
+import java.lang.reflect.Type;
+
+/**
+ * @author mintern
+ */
+public class GsonContext<T> {
+ private final Gson gson;
+ private final InterfaceAdapterFactory.InterfaceTypeAdapter<T> constructingAdapter;
+
+ public GsonContext(Gson g, InterfaceAdapterFactory.InterfaceTypeAdapter<T> ita) {
+ gson = g;
+ constructingAdapter = ita;
+ }
+
+ public JsonElement toJsonTree(Object obj) {
+ return gson.toJsonTree(obj);
+ }
+
+ public JsonElement thisToJsonTree(T obj) throws JsonIOException {
+ JsonTreeWriter writer = new JsonTreeWriter();
+ try {
+ constructingAdapter.getDelegate().write(writer, obj);
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ return writer.get();
+ }
+
+ public <C> C fromJsonTree(JsonElement json, Class<C> type) {
+ return gson.fromJson(json, type);
+ }
+
+ public <C> C fromJsonTree(JsonElement json, Type typeOfC) {
+ return (C) gson.fromJson(json, typeOfC);
+ }
+
+ public T thisFromJsonTree(JsonElement json) throws JsonIOException {
+ try {
+ return constructingAdapter.getDelegate().read(new JsonTreeReader(json));
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+
+ public <C extends T> C thisFromJsonTree(JsonElement json, Type typeOfC) {
+ TypeAdapter<C> adapter = constructingAdapter.getNextAdapter(typeOfC);
+ try {
+ return adapter.read(new JsonTreeReader(json));
+ } catch (IOException e) {
+ throw new JsonIOException(e);
+ }
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/InterfaceAdapterFactory.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/InterfaceAdapterFactory.java
new file mode 100644
index 0000000..8f7d9d6
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/InterfaceAdapterFactory.java
@@ -0,0 +1,192 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.google.gson.*;
+import com.google.gson.internal.Streams;
+import com.google.gson.reflect.TypeToken;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Type;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author mintern
+ */
+public class InterfaceAdapterFactory implements TypeAdapterFactory {
+ @Override
+ public <T> TypeAdapter<T> create(Gson gson, TypeToken<T> tt) {
+ Class<T> rawType = Reflection.classOfType(tt.getRawType());
+ boolean serializes = JsonSerialization.class.isAssignableFrom(rawType);
+ Constructor<JsonDeserializes<T>> deserializerConstructor = null;
+ Class<JsonDeserializes<T>>[] typeParameters = Reflection.getTypeParameters(rawType, JsonDeserialization.class);
+ if (typeParameters != null) {
+ deserializerConstructor = Reflection.getConstructor(typeParameters[0]);
+ }
+ if (serializes || deserializerConstructor != null) {
+ return new InterfaceTypeAdapter(serializes, deserializerConstructor, gson, tt, this);
+ }
+ return null;
+ }
+
+ public static class InterfaceTypeAdapter<T> extends TypeAdapter<T> {
+ // This map ensures that only one deserializer of each type exists.
+ private static final Map<Class, JsonDeserializes<?>> deserializerInstances = new HashMap();
+
+ // Fields set in the constructor
+ private final boolean selfSerializing;
+ private final Constructor<JsonDeserializes<T>> deserializerConstructor;
+ private final Gson gson;
+ private final TypeToken<T> typeToken;
+ private final TypeAdapterFactory thisFactory;
+
+ // Adapters that follow this one in the chain for the indicated type
+ private final Map<Type, TypeAdapter> nextAdapters = new HashMap();
+
+ // Lazily-initialized fields. Call their corresponding getters in
+ // order to access them.
+ private TypeAdapter<T> delegate;
+ private GsonContext gsonContext;
+
+ private InterfaceTypeAdapter(
+ boolean serializes,
+ Constructor<JsonDeserializes<T>> dsc,
+ Gson g,
+ TypeToken<T> tt,
+ TypeAdapterFactory factory) {
+ selfSerializing = serializes;
+ if (dsc != null) {
+ dsc.setAccessible(true);
+ }
+ deserializerConstructor = dsc;
+ gson = g;
+ typeToken = tt;
+ thisFactory = factory;
+ }
+
+ @Override
+ public void write(JsonWriter writer, T value) throws IOException {
+ if (!selfSerializing) {
+ getDelegate().write(writer, value);
+ } else if (value == null) {
+ writer.nullValue();
+ } else {
+ JsonElement tree = ((JsonSerialization) value).toJsonTree(gsonContext());
+ Streams.write(tree, writer);
+ }
+ }
+
+ @Override
+ public T read(JsonReader reader) throws IOException {
+ if (deserializerConstructor == null) {
+ return getDelegate().read(reader);
+ }
+ JsonElement json = Streams.parse(reader);
+ if (json.isJsonNull()) {
+ return null;
+ }
+ return (T) deserializer().fromJsonTree(json, typeToken.getType(), gsonContext());
+ }
+
+ synchronized TypeAdapter<T> getDelegate() {
+ if (delegate == null) {
+ delegate = gson.getDelegateAdapter(thisFactory, typeToken);
+ }
+ return delegate;
+ }
+
+ private synchronized GsonContext gsonContext() {
+ if (gsonContext == null) {
+ gsonContext = new GsonContext(gson, this);
+ }
+ return gsonContext;
+ }
+
+ synchronized <C extends T> TypeAdapter<C> getNextAdapter(Type typeOfC) {
+ TypeAdapter<C> nextAdapter = nextAdapters.get(typeOfC);
+ if (nextAdapter == null) {
+ nextAdapter = gson.getDelegateAdapter(thisFactory, (TypeToken<C>) TypeToken.get(typeOfC));
+ nextAdapters.put(typeOfC, nextAdapter);
+ }
+ return nextAdapter;
+ }
+
+ private JsonDeserializes<T> deserializer() {
+ synchronized (deserializerInstances) {
+ Class<JsonDeserializes<T>> c = deserializerConstructor.getDeclaringClass();
+ JsonDeserializes<T> deserializer = (JsonDeserializes<T>) deserializerInstances.get(c);
+ if (deserializer == null) {
+ try {
+ deserializer = deserializerConstructor.newInstance();
+ } catch (Exception e) {
+ throw new JsonParseException(e);
+ }
+ deserializerInstances.put(c, deserializer);
+ }
+ return deserializer;
+ }
+ }
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserialization.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserialization.java
new file mode 100644
index 0000000..436d897
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserialization.java
@@ -0,0 +1,63 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+public interface JsonDeserialization<D extends JsonDeserializes<? extends JsonDeserialization<D>>> {
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserializes.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserializes.java
new file mode 100644
index 0000000..ecdf8e5
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonDeserializes.java
@@ -0,0 +1,72 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.google.gson.JsonElement;
+
+import java.lang.reflect.Type;
+
+/**
+ * @author mintern
+ */
+public interface JsonDeserializes<T> {
+ T fromJsonTree(JsonElement json, Type type, GsonContext<T> context);
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonSerialization.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonSerialization.java
new file mode 100644
index 0000000..3cae64d
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/JsonSerialization.java
@@ -0,0 +1,70 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import com.google.gson.JsonElement;
+
+/**
+ * @author mintern
+ */
+public interface JsonSerialization<T extends JsonSerialization<T>> {
+ public JsonElement toJsonTree(GsonContext<T> context);
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/Reflection.java b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/Reflection.java
new file mode 100644
index 0000000..ee50b64
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/config/gson/gsoninterface/Reflection.java
@@ -0,0 +1,513 @@
+package cc.polyfrost.oneconfig.config.gson.gsoninterface;
+
+/*
+ * This file is part of OneConfig.
+ * OneConfig - Next Generation Config Library for Minecraft: Java Edition
+ * Copyright (C) 2021, 2022 Polyfrost.
+ *
+ * <https://polyfrost.cc> <https://github.com/Polyfrost/>
+ *
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * OneConfig is licensed under the terms of version 3 of the GNU Lesser
+ * General Public License as published by the Free Software Foundation, AND
+ * under the Additional Terms Applicable to OneConfig, as published by Polyfrost,
+ * either version 1.0 of the Additional Terms, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License. If not, see <https://www.gnu.org/licenses/>. You should
+ * have also received a copy of the Additional Terms Applicable
+ * to OneConfig, as published by Polyfrost. If not, see
+ * <https://polyfrost.cc/legal/oneconfig/additional-terms>
+
+ * This file contains an adaptation of code from gson-interface
+ * Project found at <https://github.com/mintern/gson-interface>
+ * For the avoidance of doubt, this file is still licensed under the terms
+ * of OneConfig's Licensing.
+ *
+ * LICENSE NOTICE FOR ADAPTED CODE
+ *
+ * Copyright (C) 2012, Brandon Mintern, EasyESI, Berkeley, CA
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither gson-interface nor the names of its contributors may be used
+ * to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
+ * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL BRANDON MINTERN OR EASYESI BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.EmptyStackException;
+
+/**
+ * Provides various static helper methods that add a high-level interface to
+ * introspection and reflection.
+ *
+ * @author mintern
+ */
+public class Reflection {
+ /**
+ * A wrapper for Class.newInstance() that throws an unchecked Exception.
+ * It also ensures that even private constructors can be called.
+ *
+ * @param c the class on which to call newInstance()
+ * @return the object returned from c.newInstance()
+ * @throws IllegalArgumentException if there was an exception
+ * constructing the instance
+ */
+ public static <T> T newInstance(Class<T> c) {
+ try {
+ return constructAnyway(c.getDeclaredConstructor());
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Gets the aClass constructor with the given paramaters, throwing an
+ * unchecked exception in the case of errors.
+ *
+ * @param aClass the class whose constructor should be fetched.
+ * @param params the parameters to the desired constructor
+ * @return the constructor for aClass that accepts params, or null if
+ * there is no such constructor
+ */
+ public static <T> Constructor<T> getConstructor(Class<T> aClass, Class... params) {
+ try {
+ return aClass.getDeclaredConstructor(params);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Invokes the given constructor with the given args even if it's not
+ * accessible.
+ *
+ * @param <T> the type of value to be constructed
+ * @param constructor the constructor to invoke (see getConstructor)
+ * @param args the args to send to the constructor
+ * @return a new instance of type T
+ * @throws IllegalArgumentException if there was an exception while
+ * setting the constructor to be accessible or invoking it
+ */
+ public static <T> T constructAnyway(Constructor<T> constructor, Object... args)
+ throws IllegalArgumentException {
+ try {
+ boolean wasAccessible = constructor.isAccessible();
+ constructor.setAccessible(true);
+ try {
+ T instance = constructor.newInstance(args);
+ return instance;
+ } finally {
+ constructor.setAccessible(wasAccessible);
+ }
+ } catch (Exception e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Checks whether a class is abstract.
+ *
+ * @param c the class to check
+ * @return true iff c is an interface or abstract class
+ */
+ public static boolean isAbstract(Class c) {
+ return c.isInterface() || Modifier.isAbstract(c.getModifiers());
+ }
+
+ /**
+ * Obtain the value of a field even if it is not public.
+ *
+ * @param field the field value to obtain
+ * @param fieldObj an instantiated object which defines field
+ * @return the value of field in fieldObj
+ * @throws IllegalArgumentException on any error
+ */
+ public static Object getFieldValue(Field field, Object fieldObj)
+ throws IllegalArgumentException {
+ try {
+ boolean wasAccessible = field.isAccessible();
+ field.setAccessible(true);
+ try {
+ return field.get(fieldObj);
+ } finally {
+ field.setAccessible(wasAccessible);
+ }
+ } catch (IllegalAccessException e) {
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * Returns a field on which field.get(...) can be called, even if the field
+ * is private.
+ *
+ * @param aClass the class defining the desired field
+ * @param fieldName the name of the desired field
+ * @return a field that is accessible regardless of its modifiers
+ * @throws IllegalArgumentException on any error
+ */
+ public static Field getAccessibleField(Class aClass, String fieldName)
+ throws IllegalArgumentException {
+ try {
+ Field field = aClass.getDeclaredField(fieldName);
+ field.setAccessible(true);
+ return field;
+ } catch (Exception e) {
+ // NoSuchFieldException, SecurityException
+ throw new IllegalArgumentException(e);
+ }
+ }
+
+ /**
+ * For a class implClass that instantiates a generic class or interface
+ * genClass, return the types that genClass is instantiated with. This
+ * method has a runtime linear in the size of the type hierarchy, so if
+ * the results are used at runtime, it is recommended to cache them.
+ * <p>
+ * An example of its usage:
+ * <p>
+ * import java.util.*;
+ * public class IdToNameMap extends HashMap<Integer, String> {
+ * public static void main (String[] args) {
+ * for (Class c: getTypeParameters(IdToNameMap.class, Map.class) {
+ * System.out.println(c);
+ * }
+ * }
+ * }
+ * <p>
+ * Calling main() would print out:
+ * <p>
+ * Integer.class
+ * String.class
+ * <p>
+ * Note that this method ascends and descends the class hierarchy to
+ * determine the best bounds possible on all type parameters. For
+ * example, even if the class hierarchy is:
+ * <p>
+ * public class IdMap<V> extends HashMap<Integer, V> {}
+ * public class IdToNameMap extends IdMap<String> {}
+ * <p>
+ * the main() call would print the same thing. For type parameters that
+ * remain unbounded, the tightest bound available is printed. So:
+ * <p>
+ * getTypeParameters(EnumSet.class, Set.class)
+ * <p>
+ * would return Enum.class. Sometimes the tightest bound available is
+ * Object.class.
+ * <p>
+ * This method should NOT be called with implClass or genClass as a
+ * primitive class. This is not sensible, anyway.
+ *
+ * @param implClass a class instantiating a generic class or interface
+ * @param genClass the generic class or interface which has type
+ * parameters you want to know
+ * @return the array of instantiated parameters. If implClass does not
+ * extend or implement genClass, null is returned. For any genClass
+ * type parameters not instantiated by implClass, we return the
+ * parameters' upper bounds.
+ */
+ public static Class[] getTypeParameters(Class implClass, Class genClass) {
+ return getTypeParameters(implClass, genClass, new Stack());
+ }
+
+ /**
+ * @param genClass typically a Class with generic type parameters
+ * @return the array of Class bounds on those parameters, or an empty
+ * Class array if genClass is not generic.
+ */
+ public static Class[] getParameterBounds(Class genClass) {
+ Type[] parameters = genClass.getTypeParameters();
+ Class[] paramClasses = new Class[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ paramClasses[i] = classOfType(parameters[i]);
+ }
+ return paramClasses;
+ }
+
+ /**
+ * @param type any Type object
+ * @return the most specific class of type that we can determine
+ */
+ public static Class classOfType(Type type) {
+ if (type instanceof Class) {
+ return (Class) type;
+ } else if (type instanceof ParameterizedType) {
+ return classOfType(((ParameterizedType) type).getRawType());
+ } else if (type instanceof TypeVariable) {
+ return classOfType(((TypeVariable) type).getBounds()[0]);
+ } else if (type instanceof WildcardType) {
+ return classOfType(((WildcardType) type).getUpperBounds()[0]);
+ } else {
+ // this should never happen in principle, but just in case...
+ return Object.class;
+ }
+ }
+
+ // Internal helper methods used above.
+
+ /**
+ * See public ... getTypeParameters. This method and the others below
+ * that implement it work by ascending and descending the type hierarchy
+ * in order to gather the desired information. As we ascend the type
+ * hierarchy looking for genClass, we push the classes that have led us
+ * there onto classStack. Later, if we find genClass but the associated
+ * implClass is generic, we descend the classStack to figure out how
+ * implClass is instantiated. Eventually, each type parameter is either
+ * fully instantiated, in which case we use the instantiation class, or
+ * the type parameter is not instantiated, in which case we return its
+ * upper bound, which in some cases will be Object.class. @param
+ * implClass the class extending/implementing genClass @param genClass
+ * a class with generic type parameters
+ *
+ * @param classStack the stack of subclasses we've visited before
+ * reaching this implClass
+ * @return the tightest bounds implClass places on those type
+ * parameters
+ */
+ private static Class[] getTypeParameters(Class implClass, Class genClass, Stack<Class> classStack) {
+ if (genClass.isInterface()) {
+ return getInterfaceParameters(implClass, genClass, classStack);
+ }
+ return getSuperParameters(implClass, genClass, classStack);
+ }
+
+ /**
+ * This method implements getTypeParameters when genClass is an
+ * interface. Since a class can implement multiple interfaces and since
+ * they must be retrieved differently than with a superclass, we keep
+ * the functions separate.
+ * <p>
+ * At each level, we check each interface against genClass. If none of
+ * them match, we look at any interface's implemented interfaces, and so
+ * on all the way up until we reach an interface with no implemented
+ * interfaces.
+ * <p>
+ * If we still haven't found genClass at this point, we ascend into
+ * implClass's superclass and perform the same work.
+ * <p>
+ * As mentioned in private ... getTypeParameters, any time we ascend the
+ * type hierarchy, we push the implClass onto the classStack so that we
+ * can later descend the class hierarchy to resolve type parameters
+ * which are set further down the tree.
+ */
+ private static Class[] getInterfaceParameters(Class implClass, Class genClass, Stack<Class> classStack) {
+ Type[] interfaces = implClass.getGenericInterfaces();
+ // Check each of the interfaces implemented by implClass to see if
+ // one matches genClass.
+ for (Type iface : interfaces) {
+ Class[] result = getParametersIfMatches(iface, implClass, genClass, classStack);
+ if (result != null) {
+ // We found it.
+ return result;
+ }
+ }
+ // None of the implemented interfaces matched genClass. Ascend each
+ // interface's type hierarchy looking for genClass.
+ for (Type iface : interfaces) {
+ Class interfaceClass = classOfType(iface);
+ classStack.push(implClass);
+ Class[] result = getInterfaceParameters(interfaceClass, genClass, classStack);
+ classStack.pop();
+ if (result != null) {
+ return result;
+ }
+ }
+ // We visited the entire interface hierarchy of implClass. Ascend to
+ // implClass's superclass and look for the interface there.
+ Class superclass = implClass.getSuperclass();
+ if (superclass == null) {
+ // implClass is an interface or Object, so it has no superclass.
+ // We didn't find genClass along this path.
+ return null;
+ }
+ // The ascent.
+ classStack.push(implClass);
+ Class[] result = getInterfaceParameters(superclass, genClass, classStack);
+ classStack.pop();
+ // The result may still be null at this point, but we've visited the
+ // entire type hierarchy and haven't found genClass. In these cases, we
+ // want to return null, anyway.
+ return result;
+ }
+
+ /**
+ * This method implements getTypeParameters when genClass is not an
+ * interface, and therefore should be found as a superclass of
+ * implClass. Since a class can only have one superclass, we simply
+ * check that superclass and then ascend the type hierarchy if it is not
+ * a match.
+ */
+ private static Class[] getSuperParameters(Class implClass, Class genClass, Stack<Class> classStack) {
+ Type supertype = implClass.getGenericSuperclass();
+ if (supertype == null) {
+ // Base case; implClass is Object; we didn't find genClass.
+ return null;
+ }
+ Class[] result = getParametersIfMatches(supertype, implClass, genClass, classStack);
+ if (result != null) {
+ // We found it.
+ return result;
+ }
+ // The superclass of implClass didn't match genClass. Ascend class
+ // hierarchy.
+ classStack.push(implClass);
+ result = getSuperParameters(classOfType(supertype), genClass, classStack);
+ classStack.pop();
+ // We either have our result or null. Return.
+ return result;
+ }
+
+ /**
+ * If the Class represented by type matches genClass, return the
+ * genClass type parameters. If it is not a match, returns null.
+ *
+ * @param type the type being considered, with all information present
+ * in implClass
+ * @param implClass the current class, for which type was a superclass
+ * or an implemented interface
+ * @param genClass the class we are looking for (to be compared against
+ * type)
+ * @param classStack
+ * @return null if type is not of Class genClass, genClass
+ * instantiated type parameters otherwise. Class[0] if genClasses has
+ * no type parameters
+ */
+ private static Class[] getParametersIfMatches(Type type, Class implClass, Class genClass, Stack<Class> classStack) {
+ if (type == genClass) {
+ // type matches genClass and it's a Class Type; if genClass has
+ // type parameters, they have not been instantiated by
+ // implClass. Simply return the bounds on genClass's
+ // parameters, if it has any parameters. If it doesn't have
+ // parameters, we'll return Class[0]
+ return getParameterBounds(genClass);
+ }
+ if (type instanceof ParameterizedType && classOfType(type) == genClass) {
+ // We've found our parameterized genClass. For each type,
+ // convert it to its corresponding class. If it's a TypeVariable
+ // and it matches a type argument in implClass, descend down the
+ // classStack to determine THAT parameter type. Otherwise, or
+ // if the TypeVariable can't be resolved, simply perform a dumb
+ // conversion on the type.
+ Type[] types = ((ParameterizedType) type).getActualTypeArguments();
+ Class[] classes = new Class[types.length];
+ // The following two types declared here to avoid repeated work
+ Type[] implParams = null;
+ Class[] implInstantiatedParams = null;
+ // This loop is probably the most complex bit in the code. An
+ // instructive example is where we have a class hierarchy like:
+ // IdNameMap extends IdMap<String>
+ // IdMap<E> extends HashMap<Integer, E>
+ // getTypeParameters(IdNameMap.class, HashMap.class)
+ // When we reach this point:
+ // types = [Integer, E]
+ // implClass = IdMap
+ // classes = []
+ // classStack = [IdNameMap]
+ // The outer loop iterates over types. After its first iteration
+ // (for which the if-test fails), classes=[Integer]. In the
+ // second iteration, E is a TypeVariable and the classStack is
+ // not empty. We fetch IdMap's TypeParameters:
+ // implParams = [E]
+ // The inner loop iterates over implParams, finding that when j
+ // = 0, implParams[j] matches types[i] (both are E). We then
+ // call getTypeParameters(IdNameMap, IdMap, []) to determine the
+ // type of E, which turns out to be String:
+ // implInstantiatedParams = [String]
+ // We update classes:
+ // classes = [Integer, String]
+ // and we continue to the next iteration of the outer loop,
+ // where it turns out to terminate. Finally, we return classes.
+ types:
+ for (int i = 0; i < types.length; i++) {
+ if (types[i] instanceof TypeVariable && !classStack.isEmpty()) {
+ if (implParams == null) {
+ implParams = implClass.getTypeParameters();
+ }
+ for (int j = 0; j < implParams.length; j++) {
+ if (((TypeVariable) types[i]).equals(implParams[j])) {
+ if (implInstantiatedParams == null) {
+ Class subClass = classStack.pop();
+ // the descent
+ implInstantiatedParams = getTypeParameters(subClass, implClass, classStack);
+ classStack.push(subClass);
+ }
+ classes[i] = implInstantiatedParams[j];
+ continue types;
+ }
+ }
+ // If we reach this point (we were unable to resolve the
+ // parameter), type[i] will be resolved to its bound in the
+ // line below.
+ }
+ classes[i] = classOfType(types[i]);
+ }
+ return classes;
+ }
+ // type did not match genClass
+ return null;
+ }
+
+ /**
+ * A non-threadsafe alternative to java.util.Stack.
+ * Since the Reflection code is single threaded, using this should be
+ * faster.
+ *
+ * @param E the type of elements in the stack
+ */
+ public static class Stack<E> extends ArrayList<E> {
+ /**
+ * Push an item onto the stack. The next call to pop() will return
+ * the most-recently push()ed item.
+ *
+ * @param elt the element to add to the stack
+ * @return this; convenient for chaining: stack.push(...).push(...)
+ */
+ public Stack<E> push(E elt) {
+ add(elt);
+ return this;
+ }
+
+ /**
+ * Pop from this and return the item that was most recently pushed
+ * onto this. This should never be called on an empty stack.
+ *
+ * @return the item that was pop()ed
+ * @throws EmptyStackException if this is empty
+ */
+ public E pop() {
+ try {
+ return remove(size() - 1);
+ } catch (IndexOutOfBoundsException e) {
+ throw new EmptyStackException();
+ }
+ }
+ }
+}