aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/cc
diff options
context:
space:
mode:
authornextdaydelivery <79922345+nxtdaydelivery@users.noreply.github.com>2022-09-03 12:50:40 +0100
committerGitHub <noreply@github.com>2022-09-03 13:50:40 +0200
commit4c60b452efda24794c0faa5f300fdd830edcd383 (patch)
tree06c4d425bdc405af0189f4707ba1302b864f7328 /src/main/java/cc
parentb7169237c543bf549a4b03dcc9a23b516da635eb (diff)
downloadOneConfig-4c60b452efda24794c0faa5f300fdd830edcd383.tar.gz
OneConfig-4c60b452efda24794c0faa5f300fdd830edcd383.tar.bz2
OneConfig-4c60b452efda24794c0faa5f300fdd830edcd383.zip
deprecator + Stack trace utilities (#121)
* deprecator * check for more system packages because iterators n stuff * add LogScanner, new loader platform features, spam protection * oop~ * move to Preferences * diamond review * add back the exception * try-catch things and associate OneConfig mods with an ActiveMod * ok maybe not + api my beloved
Diffstat (limited to 'src/main/java/cc')
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java7
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java25
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java64
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java22
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/platform/Platform.java5
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java219
-rw-r--r--src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java3
7 files changed, 330 insertions, 15 deletions
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java b/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java
index 61517ab..f77639f 100644
--- a/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java
+++ b/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java
@@ -29,15 +29,18 @@ package cc.polyfrost.oneconfig.config.data;
import cc.polyfrost.oneconfig.config.Config;
import cc.polyfrost.oneconfig.config.elements.OptionPage;
import cc.polyfrost.oneconfig.config.migration.Migrator;
-import cc.polyfrost.oneconfig.internal.config.OneConfigConfig;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class Mod implements Comparable<Mod> {
+ @NotNull
public final String name;
public final ModType modType;
+ @Nullable
public final String modIcon;
+ @Nullable
public final Migrator migrator;
+ @NotNull
public final OptionPage defaultPage;
public Config config;
@@ -47,7 +50,7 @@ public class Mod implements Comparable<Mod> {
* @param modIcon Path to icon of the mod (png or svg format)
* @param migrator Migrator class to port the old config
*/
- public Mod(String name, ModType modType, @Nullable String modIcon, @Nullable Migrator migrator) {
+ public Mod(@NotNull String name, ModType modType, @Nullable String modIcon, @Nullable Migrator migrator) {
this.name = name;
this.modType = modType;
this.modIcon = modIcon;
diff --git a/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java b/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java
index bc0e3a9..874abd2 100644
--- a/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java
+++ b/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java
@@ -35,10 +35,17 @@ import cc.polyfrost.oneconfig.platform.Platform;
import cc.polyfrost.oneconfig.utils.TickDelay;
public class Preferences extends InternalConfig {
+
+ @Dropdown(
+ name = "Release Channel",
+ options = {"Releases", "Pre-Releases"}
+ )
+ public static int updateChannel = 0;
+
@Switch(
- name = "Enable Blur"
+ name = "Debug Mode"
)
- public static boolean enableBlur = true;
+ public static boolean DEBUG = false;
@KeyBind(
name = "OneConfig Keybind",
@@ -46,23 +53,21 @@ public class Preferences extends InternalConfig {
)
public static OneKeyBind oneConfigKeyBind = new OneKeyBind(UKeyboard.KEY_RSHIFT);
- @Dropdown(
- name = "Release Channel",
- options = {"Releases", "Pre-Releases"},
- size = 2
+ @Switch(
+ name = "Enable Blur",
+ subcategory = "GUI Settings"
)
- public static int updateChannel = 0;
+ public static boolean enableBlur = true;
@Switch(
name = "Use custom GUI scale",
- subcategory = "GUI Scale",
- size = 2
+ subcategory = "GUI Settings"
)
public static boolean enableCustomScale = false;
@Slider(
name = "Custom GUI scale",
- subcategory = "GUI Scale",
+ subcategory = "GUI Settings",
min = 0.5f,
max = 5f
)
diff --git a/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java b/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java
new file mode 100644
index 0000000..e8d048c
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java
@@ -0,0 +1,64 @@
+/*
+ * 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>
+ */
+
+package cc.polyfrost.oneconfig.internal.utils;
+
+import cc.polyfrost.oneconfig.utils.LogScanner;
+import cc.polyfrost.oneconfig.utils.Notifications;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Class used by OneConfig for deprecation related utilities.
+ */
+public final class Deprecator {
+ private static final List<String> warned = new ArrayList<>();
+
+ /**
+ * mark a method as deprecated. When a method has this call, it will
+ * throw a new exception to grab the name (or package) of the mod that called said method. <br>
+ * This will then send a notification detailing this to the user, and throw an UnsupportedOperationException to print a stack to the log.
+ */
+ public static void markDeprecated() {
+ try {
+ throw new Exception("This method is deprecated");
+ } catch (Exception e) {
+ String culprit = LogScanner.identifyCallerFromStacktrace(e).stream().map(activeMod -> activeMod.name).findFirst().orElse("Unknown");
+ // sometimes it blames OneConfig as well so
+ if(culprit.equals("OneConfig")) return;
+ if (!warned.contains(culprit)) {
+ warned.add(culprit);
+ Notifications.INSTANCE.send("Deprecation Warning", "The mod '" + culprit + "' is using a deprecated method, and will no longer work in the future. Please report this to the mod author.");
+ try {
+ throw new UnsupportedOperationException("Method " + e.getStackTrace()[1].getClassName() + "." + e.getStackTrace()[1].getMethodName() + "() is deprecated; but is still being used by mod " + culprit + "!");
+ } catch (UnsupportedOperationException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java b/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java
index b271602..a9fc24e 100644
--- a/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java
+++ b/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java
@@ -26,20 +26,38 @@
package cc.polyfrost.oneconfig.platform;
+import cc.polyfrost.oneconfig.libs.checker.nullness.qual.Nullable;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.util.List;
+
public interface LoaderPlatform {
boolean isModLoaded(String id);
+
boolean hasActiveModContainer();
- ActiveMod getActiveModContainer();
+
+ @Nullable ActiveMod getActiveModContainer();
+
+ @Nullable ActiveMod toActiveMod(@Nullable Object in);
+
+ /**
+ * Note: the list may contain null elements
+ */
+ @NotNull
+ List<ActiveMod> getLoadedMods();
class ActiveMod {
public final String name;
public final String id;
public final String version;
+ public final File source;
- public ActiveMod(String name, String id, String version) {
+ public ActiveMod(String name, String id, String version, File source) {
this.name = name;
this.id = id;
this.version = version;
+ this.source = source;
}
}
}
diff --git a/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java b/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java
index 1298214..b4cbd28 100644
--- a/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java
+++ b/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java
@@ -30,7 +30,7 @@ import java.util.ServiceLoader;
/**
* Contains various platform-specific utilities for OneConfig.
- *
+ * <p>
* This is meant for internal usage, however other mods may use these (unless otherwise stated).
*/
public interface Platform {
@@ -71,12 +71,15 @@ public interface Platform {
int getMinecraftVersion();
+ boolean isDevelopmentEnvironment();
+
Loader getLoader();
class PlatformHolder {
private PlatformHolder() {
}
+
static PlatformHolder INSTANCE = new PlatformHolder();
Platform platform = ServiceLoader.load(Platform.class, Platform.class.getClassLoader()).iterator().next();
MousePlatform mousePlatform = ServiceLoader.load(MousePlatform.class, MousePlatform.class.getClassLoader()).iterator().next();
diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java b/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java
new file mode 100644
index 0000000..5b448a1
--- /dev/null
+++ b/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java
@@ -0,0 +1,219 @@
+/*
+ * 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>
+ */
+
+/* COPYRIGHT NOTICE: MIT License
+ * Copyright (c) 2021 Fudge and NEC contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in all
+ * copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+package cc.polyfrost.oneconfig.utils;
+
+import cc.polyfrost.oneconfig.internal.config.Preferences;
+import cc.polyfrost.oneconfig.platform.LoaderPlatform;
+import cc.polyfrost.oneconfig.platform.Platform;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.jetbrains.annotations.NotNull;
+
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.CodeSource;
+import java.util.*;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Collectors;
+
+/**
+ * Adapted from <a href="https://github.com/natanfudge/Not-Enough-Crashes">NotEnoughCrashes</a> under the <a href="https://opensource.org/licenses/MIT">MIT License</a>
+ */
+public class LogScanner {
+ static final Logger LOGGER = LogManager.getLogger("OneConfig Log Scanner");
+
+ /**
+ * Return a set of ActiveMods that have been blamed for the given stacktrace.
+ * This will be an empty set if no mods are blamed.
+ */
+ @NotNull
+ public static Set<LoaderPlatform.ActiveMod> identifyFromStacktrace(Throwable e) {
+ Set<LoaderPlatform.ActiveMod> mods = new HashSet<>();
+ // Include suppressed exceptions too
+ visitChildrenThrowables(e, throwable -> {
+ for (LoaderPlatform.ActiveMod newMod : identifyFromThrowable(throwable)) {
+ if (mods.stream().noneMatch(mod -> mod.id.equals(newMod.id))) {
+ mods.add(newMod);
+ }
+ }
+ });
+ return mods;
+ }
+
+ /**
+ * Attempt to get the caller from a given stacktrace.
+ *
+ * @return A singleton mod set that contains the caller, or an empty set if no caller is found.
+ */
+ @NotNull
+ public static Set<LoaderPlatform.ActiveMod> identifyCallerFromStacktrace(Throwable e) {
+ // first is this method name, second is the method it called, third is what called it
+ StackTraceElement target = null;
+ int i = 0;
+ for (StackTraceElement element : e.getStackTrace()) {
+ // ignore the first two
+ if (i > 2) {
+ // remove any that are native, or called from a system package
+ if (!element.isNativeMethod() && !element.getClassName().startsWith("sun.") && !element.getClassName().startsWith("java.")
+ && !element.getClassName().startsWith("javax.") && !element.getClassName().startsWith("jdk.") && !element.getClassName().startsWith("com.sun.")) {
+ target = element;
+ break;
+ }
+ }
+ i++;
+ }
+ if (target == null) {
+ return Collections.emptySet();
+ }
+ Set<LoaderPlatform.ActiveMod> classMods = identifyFromClass(target.getClassName());
+ return classMods.isEmpty() ? Collections.emptySet() : classMods;
+ }
+
+ private static void visitChildrenThrowables(Throwable e, Consumer<Throwable> visitor) {
+ visitor.accept(e);
+ for (Throwable child : e.getSuppressed()) visitChildrenThrowables(child, visitor);
+ }
+
+ private static Set<LoaderPlatform.ActiveMod> identifyFromThrowable(Throwable e) {
+ Set<String> involvedClasses = new LinkedHashSet<>();
+ while (e != null) {
+ for (StackTraceElement element : e.getStackTrace()) {
+ involvedClasses.add(element.getClassName());
+ }
+ e = e.getCause();
+ }
+
+ Set<LoaderPlatform.ActiveMod> mods = new LinkedHashSet<>();
+ for (String className : involvedClasses) {
+ Set<LoaderPlatform.ActiveMod> classMods = identifyFromClass(className);
+ mods.addAll(classMods);
+ }
+ return mods;
+ }
+
+ private static void debug(Supplier<String> message) {
+ if (Preferences.DEBUG) LOGGER.info(message.get());
+ }
+
+ // TODO: get a list of mixin transformers that affected the class and blame those too
+
+ /**
+ * Return a set of ActiveMods that have been associated with the given class.
+ */
+ @NotNull
+ public static Set<LoaderPlatform.ActiveMod> identifyFromClass(String className) {
+ List<LoaderPlatform.ActiveMod> modMap = Platform.getLoaderPlatform().getLoadedMods();
+ // Skip identification for Mixin, one's mod copy of the library is shared with all other mods
+ if (className.startsWith("org.spongepowered.asm.mixin.")) {
+ debug(() -> "Ignoring class " + className + " for identification because it is a mixin class");
+ return Collections.emptySet();
+ }
+
+ try {
+ // Get the URL of the class (don't initialize classes, though)
+ Class<?> clazz = Class.forName(className, false, LogScanner.class.getClassLoader());
+ CodeSource codeSource = clazz.getProtectionDomain().getCodeSource();
+ if (codeSource == null) {
+ debug(() -> "Ignoring class " + className + " for identification because the code source could not be found");
+ return Collections.emptySet(); // Some internal native sun classes
+ }
+ URL url = codeSource.getLocation();
+
+ if (url == null) {
+ LOGGER.warn("Failed to identify mod for " + className);
+ return Collections.emptySet();
+ }
+
+ // Transform JAR URL to a file URL
+ if (url.toURI().toString().startsWith("jar:")) {
+ url = new URL(url.toURI().toString().substring(4, url.toURI().toString().lastIndexOf("!")));
+ }
+ if (url.toURI().toString().endsWith(".class") && Platform.getInstance().isDevelopmentEnvironment()) {
+ LOGGER.error("The mod you are currently developing caused this issue, or another class file. Returning 'this'.");
+ LOGGER.error("Class: " + className);
+ return Collections.singleton(new LoaderPlatform.ActiveMod("this", "this", "Unknown", null));
+ }
+ Set<LoaderPlatform.ActiveMod> mods = getModsAt(Paths.get(url.toURI()), modMap);
+ if (!mods.isEmpty()) {
+ //noinspection OptionalGetWithoutIsPresent
+ debug(() -> "Successfully placed blame of '" + className + "' on '"
+ + mods.stream().findFirst().get().name + "'");
+ }
+ return mods;
+ } catch (URISyntaxException | ClassNotFoundException | NoClassDefFoundError | MalformedURLException e) {
+ debug(() -> "Ignoring class " + className + " for identification because an error occurred");
+ return Collections.emptySet(); // we cannot do it
+ }
+ }
+
+ @NotNull
+ private static Set<LoaderPlatform.ActiveMod> getModsAt(Path path, List<LoaderPlatform.ActiveMod> modMap) {
+ Set<LoaderPlatform.ActiveMod> mods = modMap.stream().filter(m -> m.source.toPath().equals(path)).collect(Collectors.toSet());
+ if (!mods.isEmpty()) return mods;
+ else if (Platform.getInstance().isDevelopmentEnvironment()) {
+ // For some reason, in dev, the mod being tested has the 'resources' folder as the origin instead of the 'classes' folder.
+ String resourcesPathString = path.toString().replace("\\", "/")
+ // Make it work with Architectury as well
+ .replace("common/build/classes/java/main", "fabric/build/resources/main")
+ .replace("common/build/classes/kotlin/main", "fabric/build/resources/main")
+ .replace("classes/java/main", "resources/main")
+ .replace("classes/kotlin/main", "resources/main");
+ Path resourcesPath = Paths.get(resourcesPathString);
+ return modMap.stream().filter(m -> m.source.toPath().equals(resourcesPath)).collect(Collectors.toSet());
+ } else {
+ debug(() -> "Mod at path '" + path.toAbsolutePath() + "' is at fault," +
+ " but it could not be found in the map of mod paths: " /*+ modMap*/);
+ return Collections.emptySet();
+ }
+ }
+}
diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java b/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java
index bc6e9c0..72b5527 100644
--- a/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java
+++ b/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java
@@ -26,6 +26,7 @@
package cc.polyfrost.oneconfig.utils;
+import cc.polyfrost.oneconfig.internal.utils.Deprecator;
import cc.polyfrost.oneconfig.renderer.RenderManager;
import cc.polyfrost.oneconfig.renderer.font.Font;
@@ -47,7 +48,9 @@ public final class TextUtils {
* @param font The font to use.
* @return The array of lines.
*/
+ @Deprecated
public static ArrayList<String> wrapText(long vg, String text, float maxWidth, float fontSize, Font font) {
+ Deprecator.markDeprecated();
ArrayList<String> wrappedText = new ArrayList<>();
text += " ";
int prevIndex = 0;