From 949e4ab1938fe8b9866082d6d914407f2495aaae Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Tue, 11 Nov 2025 20:09:19 -0300 Subject: [PATCH 01/42] style: minor formatting and indentation changes --- .../modfile/DependencyChecker.java | 18 +++++++++--- .../modinfo/StandardBasicModInfo.java | 11 +++++-- .../basicmodinfoparser/util/ParserUtils.java | 29 ++++++++++++++----- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java index 9433b9f..17f1da3 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java @@ -54,15 +54,25 @@ public static Pair> checkDependencies(S boolean isFabricBased = loaderInfo.getPlatform() == Platform.FABRIC || loaderInfo.getPlatform() == Platform.QUILT; if (isFabricBased) { javaInfo = new StandardBasicModInfo( - "java", "Java", + "java", + "Java", LooseSemanticVersion.parse(javaVersion).orElse(null), - "Java", new ArrayList<>(), null, Platform.FABRIC + "Java", + new ArrayList<>(), + null, + Platform.FABRIC, + null ); } BasicModInfo gameInfo = new StandardBasicModInfo( - "minecraft", "Minecraft", + "minecraft", + "Minecraft", (isFabricBased ? LooseSemanticVersion.parse(gameVersion) : MavenVersion.parse(gameVersion)).orElse(null), - "Minecraft", new ArrayList<>(), null, loaderInfo.getPlatform() + "Minecraft", + new ArrayList<>(), + null, + loaderInfo.getPlatform(), + null ); Map dependencyMap = new HashMap<>(); diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java index 18f8f03..ee5473a 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java @@ -43,8 +43,15 @@ public class StandardBasicModInfo implements BasicModInfo { private final String iconPath; private final Platform platform; - public StandardBasicModInfo(@Nullable String id, @Nullable String name, @Nullable Version version, @Nullable String description, - @Nullable List dependencies, @Nullable String iconPath, @NotNull Platform platform) { + public StandardBasicModInfo( + @Nullable String id, + @Nullable String name, + @Nullable Version version, + @Nullable String description, + @Nullable List dependencies, + @Nullable String iconPath, + @NotNull Platform platform + ) { this.id = id; this.name = name; this.version = version; diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java index 999e2f7..ddebdf5 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java @@ -265,9 +265,16 @@ public static void parseFabricDependencies(List dependencyList, Json * @param platform The platform the mod is on. * @return A {@link BasicModInfo} object containing the mod information and its dependencies. */ - public static BasicModInfo createForgeModInfoFromJsonObject(JsonObject jsonObject, String modIdKey, - String displayNameKey, String versionKey, - String descriptionKey, String logoFileKey, List dependencies, Platform platform) { + public static BasicModInfo createForgeModInfoFromJsonObject( + JsonObject jsonObject, + String modIdKey, + String displayNameKey, + String versionKey, + String descriptionKey, + String logoFileKey, + List dependencies, + Platform platform + ) { Predicate isStringPredicate = element -> element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); @@ -297,10 +304,18 @@ public static BasicModInfo createForgeModInfoFromJsonObject(JsonObject jsonObjec * @param breaks The list of {@link Dependency} objects that the current mod is incompatible with. * @return A {@link BasicModInfo} object containing the mod information and its dependencies. */ - public static > BasicModInfo createFabricModInfoFromJsonObject(JsonObject jsonObject, String modIdKey, - String displayNameKey, Version version, - String descriptionKey, String logoFileKey, List dependencies, - List breaks, List> providedMods, Platform platform) { + public static > BasicModInfo createFabricModInfoFromJsonObject( + JsonObject jsonObject, + String modIdKey, + String displayNameKey, + Version version, + String descriptionKey, + String logoFileKey, + List dependencies, + List breaks, + List> providedMods, + Platform platform + ) { Predicate isStringPredicate = element -> element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); From 3848ef2815dea42b3dd38f124e36c44c3375e22c Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:34:16 -0300 Subject: [PATCH 02/42] feat: create `ModInfoKeys.java` --- .../platform/modinfo/model/ModInfoKeys.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java new file mode 100644 index 0000000..7e1281e --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java @@ -0,0 +1,91 @@ +package me.andreasmelone.basicmodinfoparser.platform.modinfo.model; + +import java.util.Objects; + +/** + * Holds information about the available keys inside a mod's information file and their respective names. + * + * @see me.andreasmelone.basicmodinfoparser.platform.Platform + */ +public class ModInfoKeys { + public final String modIdKey; + public final String displayNameKey; + public final String versionKey; + public final String descriptionKey; + public final String logoFileKey; + + public ModInfoKeys( + String modIdKey, + String displayNameKey, + String versionKey, + String descriptionKey, + String logoFileKey + ) { + this.modIdKey = modIdKey; + this.displayNameKey = displayNameKey; + this.versionKey = versionKey; + this.descriptionKey = descriptionKey; + this.logoFileKey = logoFileKey; + } + + /** + * Helper method to create a forge-and-neoforge-compliant {@link ModInfoKeys} + * + * @return a forge-and-neoforge-compliant {@link ModInfoKeys} + */ + public static ModInfoKeys forgeKeys() { + return new ModInfoKeys( + "modId", + "displayName", + "version", + "description", + "logoFile" + ); + } + + /** + * Helper method to create a fabric-and-quilt-compliant {@link ModInfoKeys} + * + * @return a fabric-and-quilt-compliant {@link ModInfoKeys} + */ + public static ModInfoKeys fabricKeys() { + return new ModInfoKeys( + "id", + "name", + "version", + "description", + "icon" + ); + } + + @Override + public boolean equals(Object other) { + if (other == null) return false; + if (this == other) return true; + if (this.getClass() != other.getClass()) return false; + + ModInfoKeys castOther = (ModInfoKeys) other; + + return castOther.modIdKey.equals(modIdKey) + && castOther.displayNameKey.equals(displayNameKey) + && castOther.versionKey.equals(versionKey) + && castOther.descriptionKey.equals(descriptionKey) + && castOther.logoFileKey.equals(logoFileKey); + } + + @Override + public int hashCode() { + return Objects.hash(modIdKey, displayNameKey, versionKey, descriptionKey, logoFileKey); + } + + @Override + public String toString() { + return "ModInfoKeys{" + + "modIdKey=" + modIdKey + + ", displayNameKey=" + displayNameKey + + ", versionKey=" + versionKey + + ", descriptionKey=" + descriptionKey + + ", logoFileKey=" + logoFileKey + + "}"; + } +} From 6d7309e713f33da6c4efb1504ebe22fc4ee1cf55 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Fri, 14 Nov 2025 12:40:15 -0300 Subject: [PATCH 03/42] feat: added a `modInfoKeys` parameter to the `Platform` constructor --- .../basicmodinfoparser/platform/Platform.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java index a82f4a6..9e7d5cd 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java @@ -37,6 +37,7 @@ import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; import me.andreasmelone.basicmodinfoparser.platform.modinfo.FabricModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; +import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; import me.andreasmelone.basicmodinfoparser.util.ModInfoParseException; import me.andreasmelone.basicmodinfoparser.util.ParserUtils; import org.jetbrains.annotations.NotNull; @@ -51,13 +52,24 @@ import java.util.Optional; import java.util.stream.StreamSupport; +import static me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys.fabricKeys; +import static me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys.forgeKeys; import static me.andreasmelone.basicmodinfoparser.util.ParserUtils.*; public enum Platform { /** * Legacy Forge platform, which uses the {@code mcmod.info} file containing JSON data. */ - FORGE_LEGACY("mcmod.info") { + FORGE_LEGACY( + new ModInfoKeys( + "modid", + "name", + "version", + "description", + "logoFile" + ), + "mcmod.info" + ) { @Override protected BasicModInfo[] parseFileData(String fileData) { JsonArray topArray = GSON.fromJson(fileData, JsonArray.class); @@ -96,7 +108,7 @@ protected BasicModInfo[] parseFileData(String fileData) { /** * Forge platform, which uses the {@code mods.toml} file with TOML data. */ - FORGE("META-INF/mods.toml") { + FORGE(forgeKeys(), "META-INF/mods.toml") { @Override protected BasicModInfo[] parseFileData(String fileData) { return ParserUtils.parseForgelikeInfo(fileData, this); @@ -119,7 +131,7 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { /** * NeoForge platform, which uses the {@code neoforge.mods.toml} file with TOML data, similarly to {@link Platform#FORGE}. */ - NEOFORGE("META-INF/neoforge.mods.toml") { + NEOFORGE(forgeKeys(), "META-INF/neoforge.mods.toml") { @Override protected @NotNull BasicModInfo[] parseFileData(String fileData) { return ParserUtils.parseForgelikeInfo(fileData, this); @@ -142,7 +154,7 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { /** * Fabric platform, which uses the {@code fabric.mod.json} file. As the extension suggests, it stores data in JSON format. */ - FABRIC("fabric.mod.json") { + FABRIC(fabricKeys(), "fabric.mod.json") { @Override protected BasicModInfo[] parseFileData(String fileData) { JsonElement root = GSON.fromJson(fileData, JsonElement.class); @@ -199,7 +211,7 @@ protected BasicModInfo[] parseFileData(String fileData) { /** * Quilt platform, which uses the {@code quilt.mod.json} file. As the extensions suggests, it stores data in the JSON format. */ - QUILT("quilt.mod.json") { + QUILT(fabricKeys(), "quilt.mod.json") { @Override protected BasicModInfo[] parseFileData(String fileData) { JsonObject jsonObj = GSON.fromJson(fileData, JsonObject.class); @@ -323,9 +335,12 @@ protected BasicModInfo[] parseFileData(String fileData) { } }; + protected final ModInfoKeys modInfoKeys; + private final String[] infoFilePaths; - private Platform(String... infoFilePaths) { + Platform(ModInfoKeys modInfoKeys, String... infoFilePaths) { + this.modInfoKeys = modInfoKeys; this.infoFilePaths = infoFilePaths; } From 722d2fc33d190728930d3e7692f0af20f0f8f259 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:14:33 -0300 Subject: [PATCH 04/42] feat: added `dependencyKeys` field to `ModInfoKeys.java` --- .../platform/modinfo/model/ModInfoKeys.java | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java index 7e1281e..c553ef2 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java @@ -13,19 +13,22 @@ public class ModInfoKeys { public final String versionKey; public final String descriptionKey; public final String logoFileKey; + public final String[] dependencyKeys; public ModInfoKeys( String modIdKey, String displayNameKey, String versionKey, String descriptionKey, - String logoFileKey + String logoFileKey, + String[] dependencyKeys ) { this.modIdKey = modIdKey; this.displayNameKey = displayNameKey; this.versionKey = versionKey; this.descriptionKey = descriptionKey; this.logoFileKey = logoFileKey; + this.dependencyKeys = dependencyKeys; } /** @@ -39,7 +42,8 @@ public static ModInfoKeys forgeKeys() { "displayName", "version", "description", - "logoFile" + "logoFile", + new String[]{"dependencies"} ); } @@ -54,7 +58,12 @@ public static ModInfoKeys fabricKeys() { "name", "version", "description", - "icon" + "icon", + new String[]{ + "depends", + "breaks", + "provides" + } ); } From 81fff565ce9c9d417cfaabbd675d85cc855e6643 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Fri, 14 Nov 2025 20:34:51 -0300 Subject: [PATCH 05/42] doc: added documentation to `ModInfoKeys.java` --- .../platform/modinfo/model/ModInfoKeys.java | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java index c553ef2..313b53b 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java @@ -3,16 +3,44 @@ import java.util.Objects; /** - * Holds information about the available keys inside a mod's information file and their respective names. + * Holds information about the available keys inside a mod's information file. * * @see me.andreasmelone.basicmodinfoparser.platform.Platform */ public class ModInfoKeys { + /** + * The key name to access a mod's ID. + */ public final String modIdKey; + + /** + * The key name to access a mod's display name. + */ public final String displayNameKey; + + /** + * The key name to access a mod's version. + */ public final String versionKey; + + /** + * The key name to access a mod's description. + */ public final String descriptionKey; + + /** + * The key name to access a mod's logo. + */ public final String logoFileKey; + + /** + * The key names to access a mod's dependencies. Loaders such as Fabric may provide multiple + * keys to declare dependencies and compatibility, so an array is necessary. + * + * @see + * Fabric's documentation on dependency management + * + */ public final String[] dependencyKeys; public ModInfoKeys( @@ -61,8 +89,10 @@ public static ModInfoKeys fabricKeys() { "icon", new String[]{ "depends", + "recommends", + "suggests", "breaks", - "provides" + "conflicts" } ); } From c6f8923db4add85cd69d8744cb60452296a8259a Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Fri, 14 Nov 2025 23:21:13 -0300 Subject: [PATCH 06/42] feat: create functions `createModInfoFrom(JsonObject, Platform, BiFunction)` and `parseLegacyForgesDependencies(String[], JsonObject)` As of now, they're still not in use --- .../basicmodinfoparser/platform/Platform.java | 6 +- .../platform/modinfo/model/ModInfoKeys.java | 6 ++ .../basicmodinfoparser/util/ParserUtils.java | 94 ++++++++++++++++++- 3 files changed, 101 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java index 9e7d5cd..721e093 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java @@ -66,7 +66,9 @@ public enum Platform { "name", "version", "description", - "logoFile" + "logoFile", + "authors", + new String[]{"dependencies"} ), "mcmod.info" ) { @@ -385,7 +387,7 @@ public Optional getInfoFileContent(IZipFile zip) throws IOException { IZipEntry infoFileEntry = zip.findEntry(infoFilePath); if (infoFileEntry == null) continue; try (InputStream entry = zip.openEntry(infoFileEntry)) { - if(entry == null) return Optional.empty(); + if (entry == null) return Optional.empty(); return Optional.of(readEverythingAsString(entry)); } } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java index 313b53b..28b415c 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java @@ -33,6 +33,8 @@ public class ModInfoKeys { */ public final String logoFileKey; + public final String authorsKey; + /** * The key names to access a mod's dependencies. Loaders such as Fabric may provide multiple * keys to declare dependencies and compatibility, so an array is necessary. @@ -49,6 +51,7 @@ public ModInfoKeys( String versionKey, String descriptionKey, String logoFileKey, + String authorsKey, String[] dependencyKeys ) { this.modIdKey = modIdKey; @@ -56,6 +59,7 @@ public ModInfoKeys( this.versionKey = versionKey; this.descriptionKey = descriptionKey; this.logoFileKey = logoFileKey; + this.authorsKey = authorsKey; this.dependencyKeys = dependencyKeys; } @@ -71,6 +75,7 @@ public static ModInfoKeys forgeKeys() { "version", "description", "logoFile", + "authors", new String[]{"dependencies"} ); } @@ -87,6 +92,7 @@ public static ModInfoKeys fabricKeys() { "version", "description", "icon", + "authors", new String[]{ "depends", "recommends", diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java index ddebdf5..49158b2 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java @@ -24,6 +24,7 @@ package me.andreasmelone.basicmodinfoparser.util; import com.google.gson.Gson; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import me.andreasmelone.basicmodinfoparser.platform.BasicModInfo; @@ -37,6 +38,7 @@ import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import me.andreasmelone.basicmodinfoparser.platform.modinfo.FabricModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; +import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; import org.jetbrains.annotations.NotNull; import org.tomlj.Toml; import org.tomlj.TomlArray; @@ -48,9 +50,8 @@ import java.io.InputStream; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; +import java.util.*; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.stream.StreamSupport; @@ -141,6 +142,93 @@ public static String getValidString(JsonObject obj, String key) { .orElse(null); } + /** + * Creates a {@link BasicModInfo} object from a {@link JsonObject}. + *

+ * This method parses a JSON object, extracts the required fields (modId, displayName, version, + * and description), and creates a new {@link BasicModInfo} object along with any given dependencies. + *

+ * + * @param modObject The {@link JsonObject} containing the mod information. + * @param platform The {@link Platform} this mod info belongs to. + * @param dependenciesParser A {@link BiFunction} that takes an array of dependency keys and the + * {@link JsonObject} to parse dependencies from, returning a list of {@link Dependency}. + * @return A {@link BasicModInfo} object containing the mod information and its dependencies. + */ + public static BasicModInfo createModInfoFrom( + @NotNull JsonObject modObject, + @NotNull Platform platform, + @NotNull BiFunction> dependenciesParser + ) { + // Get miscellaneous information + final ModInfoKeys modInfoKeys = platform.getModInfoKeys(); + final Predicate isStringPredicate = element -> element.isJsonPrimitive() && element.getAsJsonPrimitive() + .isString(); + String modId = getValidString(modObject, modInfoKeys.modIdKey); + String name = getValidString(modObject, modInfoKeys.displayNameKey, isStringPredicate); + String description = getValidString(modObject, modInfoKeys.descriptionKey, isStringPredicate); + String version = getValidString(modObject, modInfoKeys.versionKey, isStringPredicate); + String logo = getValidString(modObject, modInfoKeys.logoFileKey, isStringPredicate); + Optional mavenVersion = MavenVersion.parse(version); + + // Get authors + ArrayList authorsList = new ArrayList<>(); + if (modObject.has(modInfoKeys.authorsKey)) { + JsonArray authorsJson = modObject.getAsJsonArray(modInfoKeys.authorsKey); + authorsList.ensureCapacity(authorsJson.size()); + + for (JsonElement element : authorsJson) { + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) + authorsList.add(element.getAsString()); + } + } + + // Get dependencies + List dependencyList = dependenciesParser.apply( + modInfoKeys.dependencyKeys, + modObject + ); + + return new StandardBasicModInfo( + modId, + name, + mavenVersion.orElse(null), + description, + dependencyList, + logo, + platform, + authorsList + ); + } + + /** + * Parses all the dependencies that are present in {@code modObject} and that are under the + * given {@code dependencyKeys}. + * + * @param dependencyKeys The keys under which the dependencies are located in the {@code modObject}. + * @param modObject The {@link JsonObject} containing the mod's information. + * @return A list of {@link Dependency} objects representing the parsed dependencies. + */ + public static List parseLegacyForgeDependencies(String[] dependencyKeys, JsonObject modObject) { + List dependencies = new ArrayList<>(); + for (String dependencyKey : dependencyKeys) { + // If no key is found, we continue + if (!modObject.has(dependencyKey) || !modObject.get(dependencyKey).isJsonArray()) { + continue; + } + + JsonArray dependenciesArray = modObject.getAsJsonArray(); + for (JsonElement element : dependenciesArray) { + // Not a string = continue + if (!element.isJsonPrimitive() || !element.getAsJsonPrimitive().isString()) continue; + Optional dependency = parseLegacyForgeDependency(element.getAsString()); + dependency.ifPresent(dependencies::add); + } + } + + return dependencies; + } + /** * Parses a dependency string into a {@link ForgeDependency} object. *

From a1567a4c5f10283d48168b43a9d4a2015cb6c658 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Sat, 15 Nov 2025 17:17:19 -0300 Subject: [PATCH 07/42] feat: create `DataAdapter` and its implementations to make accessing file information centralized --- .../util/adapter/DataAdapter.java | 98 +++++++++++ .../util/adapter/JsonAdapter.java | 154 ++++++++++++++++++ .../util/adapter/TomlAdapter.java | 107 ++++++++++++ 3 files changed, 359 insertions(+) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java new file mode 100644 index 0000000..bea7e57 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java @@ -0,0 +1,98 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 RaydanOMGr + * + * 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 me.andreasmelone.basicmodinfoparser.util.adapter; + +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +/** + * An adapter interface for standardizing access to data from different sources, + * such as JSON or TOML. This allows for a unified way of retrieving values, + * regardless of the underlying data format. + * + * @param The type of the underlying data source (e.g., JsonObject, TomlTable). + * @param The type of the array-like data structure in the source (e.g., JsonArray, TomlArray). + */ +public interface DataAdapter { + /** + * Retrieves a string value for a given key. + * + * @param key The key to look up. + * @return An {@link Optional} containing the string value, or empty if not found. + */ + Optional getString(String key); + + /** + * Retrieves a boolean value for a given key. + * + * @param key The key to look up. + * @return An {@link Optional} containing the boolean value, or empty if not found. + */ + Optional getBoolean(String key); + + /** + * Retrieves an array-like data structure for a given key. + * + * @param key The key to look up. + * @return An {@link Optional} containing the array-like data, or empty if not found. + */ + Optional getArray(String key); + + /** + * Retrieves a list of objects from an array-like data structure. + * + * @param key The key of the array to retrieve. + * @param mapper A function to map each element of the array to the desired object type. + * @param The type of the objects in the resulting list. + * @return A {@link List} of mapped objects, or an empty list if the array is not found. + */ + List getList(String key, Function, V> mapper); + + /** + * Checks if a value for the given key exists. + * + * @param key The key to check. + * @return {@code true} if the key exists, otherwise {@code false}. + */ + boolean has(String key); + + /** + * Returns the underlying data source object. + * + * @return The raw data source object. + */ + T getBackingObject(); + + /** + * Retrieves a list of strings from a key that can be either a single string or an array of strings. + * + * @param key The key to look up. + * @return A {@link List} of strings, or an empty list if not found. + */ + default List getListOrString(String key) { + return Collections.emptyList(); + } +} diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java new file mode 100644 index 0000000..2097290 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java @@ -0,0 +1,154 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 RaydanOMGr + * + * 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 me.andreasmelone.basicmodinfoparser.util.adapter; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; + +/** + * A {@link DataAdapter} implementation for {@link JsonObject}. This class + * provides a standardized way to access data from a JSON structure, allowing + * for consistent data retrieval regardless of the underlying format. + */ +public class JsonAdapter implements DataAdapter { + private final JsonObject jsonObject; + + /** + * Constructs a {@link JsonAdapter} with the given {@link JsonObject}. + * + * @param jsonObject The JSON object to adapt. + */ + public JsonAdapter(JsonObject jsonObject) { + this.jsonObject = jsonObject; + } + + @Override + public Optional getString(String key) { + if (jsonObject.has(key) && jsonObject.get(key).isJsonPrimitive()) { + return Optional.of(jsonObject.get(key).getAsString()); + } + return Optional.empty(); + } + + @Override + public Optional getBoolean(String key) { + if (jsonObject.has(key) && jsonObject.get(key).isJsonPrimitive()) { + return Optional.of(jsonObject.get(key).getAsBoolean()); + } + return Optional.empty(); + } + + @Override + public Optional getArray(String key) { + if (jsonObject.has(key) && jsonObject.get(key).isJsonArray()) { + return Optional.of(jsonObject.getAsJsonArray(key)); + } + return Optional.empty(); + } + + @Override + public List getList(String key, Function, V> mapper) { + List list = new ArrayList<>(); + getArray(key).ifPresent(array -> { + for (JsonElement element : array) { + if (element.isJsonObject()) { + list.add(mapper.apply(new JsonAdapter(element.getAsJsonObject()))); + } + } + }); + return list; + } + + @Override + public boolean has(String key) { + return jsonObject.has(key); + } + + @Override + public JsonObject getBackingObject() { + return jsonObject; + } + + @Override + public List getListOrString(String key) { + // When searching for author names, these are the situations we + // need to take into account: + // + // ["example", "example2", ...] + // (From Legacy Forge, Fabric and Quilt) + // + // [{ "name": "example" }, { "name": "example2" }, ...] + // (Also from Fabric and Quilt) + // + // { "Name": "Role", "Name2": "Role2", ... } (Only in Quilt) + // + // The first two ways of representing author names are interchangeable in Fabric, + // so an array could have both types at the same time! + + if (!has(key)) return Collections.emptyList(); + + JsonElement element = jsonObject.get(key); + List result = new ArrayList<>(); + + if (!element.isJsonArray() && !element.isJsonObject()) return Collections.emptyList(); + + // Since both on Forge Legacy (https://docs.minecraftforge.net/en/1.13.x/gettingstarted/structuring/) + // and Fabric (https://wiki.fabricmc.net/documentation:fabric_mod_json#metadata) the + // author names are inside a json array, we can assume that, if the element is an object, + // we are dealing with Quilt syntax + if (element.isJsonObject()) { + JsonObject asObject = element.getAsJsonObject(); + result.addAll(asObject.keySet()); + return result; + } + + JsonArray asJsonArray = element.getAsJsonArray(); + for (JsonElement jsonElement : asJsonArray) { + // Case number 1: "authors": ["example"] + if (jsonElement.isJsonPrimitive()) { + // Since we found an author name, we can add it to the list + result.add(jsonElement.getAsString()); + continue; + } + + // Case number 2: "authors": [{ "name": "example" }, {"..."}] + if (jsonElement.isJsonObject()) { + JsonObject asObject = asJsonArray.get(0).getAsJsonObject(); + if (asObject.has("name")) { + // Since we found an author name, we can add it to the list + result.add(asObject.get("name").getAsString()); + } + } + } + + return result; + } +} \ No newline at end of file diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java new file mode 100644 index 0000000..e95ab68 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java @@ -0,0 +1,107 @@ +/* + * MIT License + * + * Copyright (c) 2024-2025 RaydanOMGr + * + * 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 me.andreasmelone.basicmodinfoparser.util.adapter; + +import org.tomlj.TomlArray; +import org.tomlj.TomlTable; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +/** + * A {@link DataAdapter} implementation for {@link TomlTable}. This class + * provides a standardized way to access data from a TOML structure, allowing + * for consistent data retrieval regardless of the underlying format. + */ +public class TomlAdapter implements DataAdapter { + private final TomlTable tomlTable; + + /** + * Constructs a {@link TomlAdapter} with the given {@link TomlTable}. + * + * @param tomlTable The TOML table to adapt. + */ + public TomlAdapter(TomlTable tomlTable) { + this.tomlTable = tomlTable; + } + + @Override + public Optional getString(String key) { + return Optional.ofNullable(tomlTable.getString(key)); + } + + @Override + public Optional getBoolean(String key) { + return Optional.ofNullable(tomlTable.getBoolean(key)); + } + + @Override + public Optional getArray(String key) { + return Optional.ofNullable(tomlTable.getArray(key)); + } + + @Override + public List getList(String key, Function, V> mapper) { + List list = new ArrayList<>(); + getArray(key).ifPresent(array -> { + for (int i = 0; i < array.size(); i++) { + TomlTable table = array.getTable(i); + if (table != null) { + list.add(mapper.apply(new TomlAdapter(table))); + } + } + }); + return list; + } + + @Override + public boolean has(String key) { + return tomlTable.contains(key); + } + + @Override + public TomlTable getBackingObject() { + return tomlTable; + } + + @Override + public List getListOrString(String key) { + if (!has(key)) { + return Collections.emptyList(); + } + + Optional arrayOptional = getArray(key); + return arrayOptional.map(tomlArray -> tomlArray.toList().stream() + .map(Object::toString) + .collect(Collectors.toList())) + .orElseGet(() -> getString(key) + .map(Collections::singletonList) + .orElse(Collections.emptyList())); + + } +} From 68a7683bf8c21d4ca7f43d4500a6754104d8da60 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Tue, 18 Nov 2025 21:17:14 -0300 Subject: [PATCH 08/42] clean: remove unused method --- .../platform/dependency/forge/MavenVersion.java | 5 ----- .../platform/dependency/version/Version.java | 5 ----- 2 files changed, 10 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java index 6d0e572..c4344f5 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java @@ -138,11 +138,6 @@ public String getStringRepresentation() { return this.stringRepresentation; } - @Override - public Class getType() { - return MavenVersion.class; - } - public interface VersionSegment extends Comparable { default boolean isEqual(@NotNull VersionSegment other) { return this.compareTo(other) == 0; diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java index 681e781..f6da351 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java @@ -35,9 +35,4 @@ default Optional optional() { * @return this version as a human-readable string */ String getStringRepresentation(); - - /** - * @return the type of {@link Version} - */ - Class getType(); } From 670c1f57ac1bdfabf763cfc1824d8f7ad01154d3 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:28:34 -0300 Subject: [PATCH 09/42] refactor: Unify and improve version handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit refactors the version parsing and comparison logic to be more consistent and object-oriented. Previously, each version type (MavenVersion, LooseSemanticVersion) had its own separate way of being parsed, which was inflexible and led to duplicated logic. To address this, the Version interface has been converted into an abstract base class. This allows different versioning schemes to share common behavior and properties, making the system cleaner and easier to extend. • A shared parse method is now part of the Version base class, replacing the previous static methods in each implementation. • The comparison logic (compareTo) has been updated to work with the new abstract Version class, ensuring different version types can still be compared correctly and safely. • Common data, like the original version string, is now handled in the base class, reducing boilerplate. This is not an ideal, let alone definitive, solution for the problemas that came with the previous versions of these classes. I was thinking of implementing the `Factory` design pattern to better address initialization logic, but since I'm in a rush at the moment, this solution will be enough. --- .../fabric/LooseSemanticVersion.java | 71 ++++++----- .../dependency/forge/MavenVersion.java | 118 +++++++++--------- .../platform/dependency/version/Version.java | 19 ++- 3 files changed, 114 insertions(+), 94 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java index c4a098f..745d340 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java @@ -23,6 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.fabric; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import org.jetbrains.annotations.NotNull; @@ -33,20 +34,21 @@ /** * Represents a looser version of the SemVer 2.0, which is accepted by fabric */ -public class LooseSemanticVersion implements Version { +public class LooseSemanticVersion extends Version { private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.+*]+"); private static final Pattern REGEX = Pattern.compile("^([0-9xX*]+(?:\\.[0-9xX*]+)*)(-.*?)?(\\+.+)?$", Pattern.MULTILINE); - private final String stringRepresentation; - private final int[] versionParts; - private final List wildcardPositions; - private final String preReleaseSuffix; - private final Integer preReleaseNumber; - private final String buildMetadata; - private final boolean usesWildcards; + private int[] versionParts; + private List wildcardPositions; + private String preReleaseSuffix; + private Integer preReleaseNumber; + private String buildMetadata; + private boolean usesWildcards; - public LooseSemanticVersion(String stringRepresentation, int[] versionParts, List wildcardPositions, String preReleaseSuffix, Integer preReleaseNumber, String buildMetadata, boolean usesWildcards) { - this.stringRepresentation = stringRepresentation; + public LooseSemanticVersion() { /* Empty constructor */ } + + private LooseSemanticVersion(String stringRepresentation, int[] versionParts, List wildcardPositions, String preReleaseSuffix, Integer preReleaseNumber, String buildMetadata, boolean usesWildcards) { + super(stringRepresentation); this.versionParts = versionParts; this.wildcardPositions = wildcardPositions; this.preReleaseSuffix = preReleaseSuffix; @@ -109,21 +111,26 @@ public LooseSemanticVersion increasePatch(int amount) { } @Override - public int compareTo(@NotNull LooseSemanticVersion other) { - int suffixless = partComparison(other); + public int compareTo(@NotNull Version other) { + if (!(other instanceof LooseSemanticVersion)) { + return -1; + } + + LooseSemanticVersion castOther = (LooseSemanticVersion) other; + int suffixless = partComparison(castOther); - if (isNull(preReleaseSuffix) && isNull(other.preReleaseSuffix)) { + if (isNull(preReleaseSuffix) && isNull(castOther.preReleaseSuffix)) { return suffixless; } else if (suffixless == 0) { if (isNull(preReleaseSuffix)) return 1; - if (isNull(other.preReleaseSuffix)) return -1; + if (isNull(castOther.preReleaseSuffix)) return -1; - int suffix = suffixComparison(other); + int suffix = suffixComparison(castOther); - if (this.preReleaseNumber == null && other.preReleaseNumber != null) return -1; - if (this.preReleaseNumber != null && other.preReleaseNumber == null) return 1; - if (this.preReleaseNumber != null && other.preReleaseNumber != null && suffix == 0) - return Integer.compare(this.preReleaseNumber, other.preReleaseNumber); + if (this.preReleaseNumber == null && castOther.preReleaseNumber != null) return -1; + if (this.preReleaseNumber != null && castOther.preReleaseNumber == null) return 1; + if (this.preReleaseNumber != null && castOther.preReleaseNumber != null && suffix == 0) + return Integer.compare(this.preReleaseNumber, castOther.preReleaseNumber); return suffix; } @@ -197,11 +204,12 @@ public int hashCode() { return Objects.hash(Arrays.hashCode(versionParts), wildcardPositions, preReleaseSuffix, preReleaseNumber, buildMetadata, usesWildcards); } - public static Optional parse(String ver) { - return parse(ver, false); + @Override + public Optional parse(String versionString) { + return parse(versionString, false); } - public static Optional parse(String ver, boolean wildcards) { + public Optional parse(String ver, boolean wildcards) { if (ver == null || ver.isEmpty() || !ALPHANUMERIC.matcher(ver).matches()) return Optional.empty(); Matcher matcher = REGEX.matcher(ver); @@ -249,21 +257,18 @@ public static Optional parse(String ver, boolean wildcards } } - return new LooseSemanticVersion( - ver, - versionInts, wildcardPositions, - prerelease, - prereleaseNumber, - metadata, wildcards).optional(); + this.wildcardPositions = wildcardPositions; + this.preReleaseSuffix = prerelease; + this.preReleaseNumber = prereleaseNumber; + this.buildMetadata = metadata; + this.versionParts = versionInts; + this.usesWildcards = wildcards; + + return Optional.of(this); } @Override public String getStringRepresentation() { return this.stringRepresentation; } - - @Override - public Class getType() { - return LooseSemanticVersion.class; - } } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java index c4344f5..76b09dd 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java @@ -36,23 +36,73 @@ *

* The format is quite complex, so this is not a fully compliant implementation. */ -public class MavenVersion implements Version { +public class MavenVersion extends Version { private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.]+"); private static final Pattern STRING_PARSER = Pattern.compile("^(\\d*)?(\\D+)(\\d*)?$"); + private VersionSegment[] versionSegments; - private final String stringRepresentation; - private final VersionSegment[] versionSegments; + @Override + public Optional parse(String versionString) { + if (versionString == null || versionString.isEmpty() || !ALPHANUMERIC.matcher(versionString).matches()) return Optional.empty(); + + List segments = new ArrayList<>(); + + String noHyphens = versionString.replace("-", "."); + String[] splitByDot = noHyphens.split("\\."); + for (String segment : splitByDot) { + Matcher matcher = STRING_PARSER.matcher(segment); + if (!matcher.matches()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(segment))); + } catch (NumberFormatException ignored) { + // + } + + continue; + } - public MavenVersion(String stringRepresentation, VersionSegment[] versionSegments) { - this.stringRepresentation = stringRepresentation; - this.versionSegments = versionSegments; + String firstNumber = matcher.group(1); + String string = matcher.group(2); + String secondNumber = matcher.group(3); + + if (firstNumber != null && !firstNumber.isEmpty()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(firstNumber))); + } catch (NumberFormatException ignored) { + } + } + if (string != null && !string.isEmpty()) { + VersionSegment.QualifierVersionSegment.Qualifier qualifier = VersionSegment.QualifierVersionSegment.Qualifier.getByName(string); + if (qualifier == null) { + segments.add(new VersionSegment.StringVersionSegment(string)); + } else { + segments.add(new VersionSegment.QualifierVersionSegment(qualifier)); + } + } + if (secondNumber != null && !secondNumber.isEmpty()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(secondNumber))); + } catch (NumberFormatException ignored) { + } + } + } + + this.stringRepresentation = versionString; + this.versionSegments = segments.toArray(new VersionSegment[0]); + return Optional.of(this); } @Override - public int compareTo(@NotNull MavenVersion other) { - int lowestAmount = Math.min(this.versionSegments.length, other.versionSegments.length); + public int compareTo(@NotNull Version other) { + if (!(other instanceof MavenVersion)) { + return -1; + } + + MavenVersion castOther = (MavenVersion) other; + + int lowestAmount = Math.min(this.versionSegments.length, castOther.versionSegments.length); for (int i = 0; i < lowestAmount; i++) { - int cmp = this.versionSegments[i].compareTo(other.versionSegments[i]); + int cmp = this.versionSegments[i].compareTo(castOther.versionSegments[i]); if (cmp != 0) { return cmp; } @@ -62,59 +112,15 @@ public int compareTo(@NotNull MavenVersion other) { for (int i = lowestAmount; i < this.versionSegments.length; i++) { if (this.versionSegments[i].isGreater(VersionSegment.NumberVersionSegment.ZERO)) return 1; } - } else if (other.versionSegments.length > lowestAmount) { - for (int i = lowestAmount; i < other.versionSegments.length; i++) { - if (other.versionSegments[i].isGreater(VersionSegment.NumberVersionSegment.ZERO)) return -1; + } else if (castOther.versionSegments.length > lowestAmount) { + for (int i = lowestAmount; i < castOther.versionSegments.length; i++) { + if (castOther.versionSegments[i].isGreater(VersionSegment.NumberVersionSegment.ZERO)) return -1; } } return 0; } - public static Optional parse(String version) { - if (version == null || version.isEmpty() || !ALPHANUMERIC.matcher(version).matches()) return Optional.empty(); - - List segments = new ArrayList<>(); - - String noHyphens = version.replace("-", "."); - String[] splitByDot = noHyphens.split("\\."); - for (String segment : splitByDot) { - Matcher matcher = STRING_PARSER.matcher(segment); - if (matcher.matches()) { - String firstNumber = matcher.group(1); - String string = matcher.group(2); - String secondNumber = matcher.group(3); - - if (firstNumber != null && !firstNumber.isEmpty()) { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(firstNumber))); - } catch (NumberFormatException ignored) { - } - } - if (string != null && !string.isEmpty()) { - VersionSegment.QualifierVersionSegment.Qualifier qualifier = VersionSegment.QualifierVersionSegment.Qualifier.getByName(string); - if (qualifier == null) { - segments.add(new VersionSegment.StringVersionSegment(string)); - } else { - segments.add(new VersionSegment.QualifierVersionSegment(qualifier)); - } - } - if (secondNumber != null && !secondNumber.isEmpty()) { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(secondNumber))); - } catch (NumberFormatException ignored) { - } - } - } else { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(segment))); - } catch (NumberFormatException ignored) { - } - } - } - - return Optional.of(new MavenVersion(version, segments.toArray(new VersionSegment[0]))); - } @Override public String toString() { diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java index f6da351..99afb0f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java @@ -25,14 +25,23 @@ import java.util.Optional; -public interface Version> extends Comparable { - @SuppressWarnings("unchecked") - default Optional optional() { - return Optional.of((T) this); +public abstract class Version implements Comparable { + protected String stringRepresentation; + + public Version() { /* Empty Constructor */ } + + protected Version(String stringRepresentation) { + this.stringRepresentation = stringRepresentation; + } + + public abstract Optional parse(String versionString); + + public Optional optional() { + return Optional.of(this); } /** * @return this version as a human-readable string */ - String getStringRepresentation(); + public abstract String getStringRepresentation(); } From c56ec5532c9944d3cf5cc7326d5e1f42dedf56ed Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 19 Nov 2025 18:50:27 -0300 Subject: [PATCH 10/42] Refactor: Convert DataAdapter to abstract class and add nested JSON access This commit refactors the DataAdapter hierarchy to improve its structure and add new functionality. The DataAdapter interface has been converted into an abstract class. This change allows for a backingObject to be managed in the base class, which simplifies the concrete JsonAdapter and TomlAdapter implementations by removing redundant fields and constructor logic. Nested Property Access: The JsonAdapter now supports accessing nested properties using dot notation (e.g., parent.child.key), making it much easier to work with complex JSON objects. --- .../util/adapter/DataAdapter.java | 40 ++++++------ .../util/adapter/JsonAdapter.java | 64 +++++++++++-------- .../util/adapter/TomlAdapter.java | 44 +++---------- 3 files changed, 70 insertions(+), 78 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java index bea7e57..de739ee 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java @@ -29,21 +29,33 @@ import java.util.function.Function; /** - * An adapter interface for standardizing access to data from different sources, + * An abstract class for standardizing access to data from different sources, * such as JSON or TOML. This allows for a unified way of retrieving values, * regardless of the underlying data format. * * @param The type of the underlying data source (e.g., JsonObject, TomlTable). * @param The type of the array-like data structure in the source (e.g., JsonArray, TomlArray). */ -public interface DataAdapter { +public abstract class DataAdapter { + /** + * The underlying data source object. + */ + protected T backingObject; + + /** + * Constructs a new {@code DataAdapter} with the given backing object. + */ + public DataAdapter(T backingObject) { + this.backingObject = backingObject; + } + /** * Retrieves a string value for a given key. * * @param key The key to look up. * @return An {@link Optional} containing the string value, or empty if not found. */ - Optional getString(String key); + public abstract Optional getString(String key); /** * Retrieves a boolean value for a given key. @@ -51,7 +63,7 @@ public interface DataAdapter { * @param key The key to look up. * @return An {@link Optional} containing the boolean value, or empty if not found. */ - Optional getBoolean(String key); + public abstract Optional getBoolean(String key); /** * Retrieves an array-like data structure for a given key. @@ -59,17 +71,7 @@ public interface DataAdapter { * @param key The key to look up. * @return An {@link Optional} containing the array-like data, or empty if not found. */ - Optional getArray(String key); - - /** - * Retrieves a list of objects from an array-like data structure. - * - * @param key The key of the array to retrieve. - * @param mapper A function to map each element of the array to the desired object type. - * @param The type of the objects in the resulting list. - * @return A {@link List} of mapped objects, or an empty list if the array is not found. - */ - List getList(String key, Function, V> mapper); + public abstract Optional getArray(String key); /** * Checks if a value for the given key exists. @@ -77,14 +79,16 @@ public interface DataAdapter { * @param key The key to check. * @return {@code true} if the key exists, otherwise {@code false}. */ - boolean has(String key); + public abstract boolean hasKey(String key); /** * Returns the underlying data source object. * * @return The raw data source object. */ - T getBackingObject(); + public T getBackingObject() { + return backingObject; + } /** * Retrieves a list of strings from a key that can be either a single string or an array of strings. @@ -92,7 +96,7 @@ public interface DataAdapter { * @param key The key to look up. * @return A {@link List} of strings, or an empty list if not found. */ - default List getListOrString(String key) { + public List getListOrString(String key) { return Collections.emptyList(); } } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java index 2097290..19b036c 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java @@ -38,40 +38,35 @@ * provides a standardized way to access data from a JSON structure, allowing * for consistent data retrieval regardless of the underlying format. */ -public class JsonAdapter implements DataAdapter { - private final JsonObject jsonObject; - +public class JsonAdapter extends DataAdapter { /** * Constructs a {@link JsonAdapter} with the given {@link JsonObject}. * * @param jsonObject The JSON object to adapt. */ public JsonAdapter(JsonObject jsonObject) { - this.jsonObject = jsonObject; + super(jsonObject); } @Override public Optional getString(String key) { - if (jsonObject.has(key) && jsonObject.get(key).isJsonPrimitive()) { - return Optional.of(jsonObject.get(key).getAsString()); - } - return Optional.empty(); + return getElement(key) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsString); } @Override public Optional getBoolean(String key) { - if (jsonObject.has(key) && jsonObject.get(key).isJsonPrimitive()) { - return Optional.of(jsonObject.get(key).getAsBoolean()); - } - return Optional.empty(); + return getElement(key) + .filter(JsonElement::isJsonPrimitive) + .map(JsonElement::getAsBoolean); } @Override public Optional getArray(String key) { - if (jsonObject.has(key) && jsonObject.get(key).isJsonArray()) { - return Optional.of(jsonObject.getAsJsonArray(key)); - } - return Optional.empty(); + return getElement(key) + .filter(JsonElement::isJsonArray) + .map(JsonElement::getAsJsonArray); } @Override @@ -88,13 +83,8 @@ public List getList(String key, Function getListOrString(String key) { // The first two ways of representing author names are interchangeable in Fabric, // so an array could have both types at the same time! - if (!has(key)) return Collections.emptyList(); + if (!hasKey(key)) return Collections.emptyList(); - JsonElement element = jsonObject.get(key); + JsonElement element = backingObject.get(key); List result = new ArrayList<>(); if (!element.isJsonArray() && !element.isJsonObject()) return Collections.emptyList(); @@ -151,4 +141,28 @@ public List getListOrString(String key) { return result; } + + /** + * Retrieves a {@link JsonElement} from the backing {@link JsonObject} based on the given key. + * The key can be a dot-separated path to access nested elements. + * + * @param key The key or path to the element. + * @return An {@link Optional} containing the {@link JsonElement} if found, otherwise empty. + */ + private Optional getElement(String key) { + String[] parts = key.split("\\."); + JsonElement current = backingObject; + + for (String part : parts) { + if (current == null || !current.isJsonObject()) { + return Optional.empty(); + } + JsonObject obj = current.getAsJsonObject(); + if (!obj.has(part)) { + return Optional.empty(); + } + current = obj.get(part); + } + return Optional.ofNullable(current); + } } \ No newline at end of file diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java index e95ab68..36fa3ab 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java @@ -38,60 +38,34 @@ * provides a standardized way to access data from a TOML structure, allowing * for consistent data retrieval regardless of the underlying format. */ -public class TomlAdapter implements DataAdapter { - private final TomlTable tomlTable; - - /** - * Constructs a {@link TomlAdapter} with the given {@link TomlTable}. - * - * @param tomlTable The TOML table to adapt. - */ - public TomlAdapter(TomlTable tomlTable) { - this.tomlTable = tomlTable; +public class TomlAdapter extends DataAdapter { + public TomlAdapter(TomlTable backingObject) { + super(backingObject); } @Override public Optional getString(String key) { - return Optional.ofNullable(tomlTable.getString(key)); + return Optional.ofNullable(backingObject.getString(key)); } @Override public Optional getBoolean(String key) { - return Optional.ofNullable(tomlTable.getBoolean(key)); + return Optional.ofNullable(backingObject.getBoolean(key)); } @Override public Optional getArray(String key) { - return Optional.ofNullable(tomlTable.getArray(key)); - } - - @Override - public List getList(String key, Function, V> mapper) { - List list = new ArrayList<>(); - getArray(key).ifPresent(array -> { - for (int i = 0; i < array.size(); i++) { - TomlTable table = array.getTable(i); - if (table != null) { - list.add(mapper.apply(new TomlAdapter(table))); - } - } - }); - return list; - } - - @Override - public boolean has(String key) { - return tomlTable.contains(key); + return Optional.ofNullable(backingObject.getArray(key)); } @Override - public TomlTable getBackingObject() { - return tomlTable; + public boolean hasKey(String key) { + return backingObject.contains(key); } @Override public List getListOrString(String key) { - if (!has(key)) { + if (!hasKey(key)) { return Collections.emptyList(); } From 39ea4abb252677dd91ce9c73889e0274f8006cba Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 19 Nov 2025 22:50:10 -0300 Subject: [PATCH 11/42] fix: made bolder type checking inside the JsonAdapter.get* methods --- .../util/adapter/JsonAdapter.java | 19 ++++--------------- 1 file changed, 4 insertions(+), 15 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java index 19b036c..8d396f6 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java @@ -51,14 +51,16 @@ public JsonAdapter(JsonObject jsonObject) { @Override public Optional getString(String key) { return getElement(key) - .filter(JsonElement::isJsonPrimitive) + .filter(element -> element.isJsonPrimitive() + && element.getAsJsonPrimitive().isString()) .map(JsonElement::getAsString); } @Override public Optional getBoolean(String key) { return getElement(key) - .filter(JsonElement::isJsonPrimitive) + .filter(element -> element.isJsonPrimitive() + && element.getAsJsonPrimitive().isBoolean()) .map(JsonElement::getAsBoolean); } @@ -69,19 +71,6 @@ public Optional getArray(String key) { .map(JsonElement::getAsJsonArray); } - @Override - public List getList(String key, Function, V> mapper) { - List list = new ArrayList<>(); - getArray(key).ifPresent(array -> { - for (JsonElement element : array) { - if (element.isJsonObject()) { - list.add(mapper.apply(new JsonAdapter(element.getAsJsonObject()))); - } - } - }); - return list; - } - @Override public boolean hasKey(String key) { return getElement(key).isPresent(); From 5532a4f2be59706d6123a945fefee6098b679fe3 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:13:52 -0300 Subject: [PATCH 12/42] feat: create individual parsers for mod dependencies This commit creates FabricDependencyParser, `ForgeDependencyParser`, `LegacyForgeDependencyParser`, `QuiltDependencyParser` and their interface, `IDependencyParser`. This separates the dependency parsing from the `Platform` code and makes it easier to implement more complex logic. As of the time of this commit, the parsing logic has not yet been altered to use these classes. --- .../parser/FabricDependencyParser.java | 91 ++++++++ .../parser/ForgeDependencyParser.java | 58 ++++++ .../dependency/parser/IDependencyParser.java | 18 ++ .../parser/LegacyForgeDependencyParser.java | 62 ++++++ .../parser/QuiltDependencyParser.java | 196 ++++++++++++++++++ 5 files changed, 425 insertions(+) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/ForgeDependencyParser.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/IDependencyParser.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/LegacyForgeDependencyParser.java create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/QuiltDependencyParser.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java new file mode 100644 index 0000000..fc8b7e2 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java @@ -0,0 +1,91 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.parser; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.StandardDependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.FabricVersionRange; +import me.andreasmelone.basicmodinfoparser.util.adapter.JsonAdapter; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.StreamSupport; + +public class FabricDependencyParser implements IDependencyParser { + @Override + public List parse(String[] keys, JsonAdapter modAdapter) { + final List result = new ArrayList<>(); + + for (String key : keys) { + if (!modAdapter.hasKey(key) || !modAdapter.getBackingObject().get(key).isJsonObject()) { + continue; + } + + parseSingleDependency(key, modAdapter).ifPresent(result::add); + } + + return result; + } + + protected Optional parseSingleDependency(String key, JsonAdapter modAdapter) { + // As the fabric documentation specifies, the dependency keys are specified in the format + // of a string -> VersionRange dictionary, where the string key matches the desired ID. + // (https://wiki.fabricmc.net/documentation:fabric_mod_json_spec#optional_fields_dependency_resolution) + + if (!modAdapter.hasKey(key) || !modAdapter.getBackingObject().get(key).isJsonObject()) return Optional.empty(); + + final JsonObject dependencyObject = modAdapter.getBackingObject().getAsJsonObject(key); + final boolean required = isRequired(key); + + // Extract all the keys from the found object + for (Map.Entry entry : dependencyObject.entrySet()) { + final String dependencyId = entry.getKey(); + final JsonObject versionRange = entry.getValue().getAsJsonObject(); + + // VersionRange can be a string or an array of strings + + // Case 1: String + if (versionRange.isJsonPrimitive() && versionRange.getAsJsonPrimitive().isString()) { + Optional range = FabricVersionRange.parse(versionRange.getAsString()); + if (range.isPresent()) return Optional.of( + new StandardDependency<>(dependencyId, required, range.get()) + ); + } + + // Case 2: array of Strings + if (versionRange.isJsonArray()) { + final String[] onlyStrings = StreamSupport.stream( + versionRange.getAsJsonArray().spliterator(), + false + ).filter(element -> + element.isJsonPrimitive() && element.getAsJsonPrimitive().isString() + ).map(JsonElement::getAsString) + .toArray(String[]::new); + + Optional fabricVersionRange = FabricVersionRange.parse(onlyStrings); + return fabricVersionRange.map( + range -> new StandardDependency<>( + dependencyId, + required, + range + ) + ); + } + } + + // No parsing was successful, return an empty optional + return Optional.empty(); + } + + protected boolean isRequired(String keyName) { + switch (keyName) { + case "depends": + case "breaks": + return true; + default: + return false; + } + } +} diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/ForgeDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/ForgeDependencyParser.java new file mode 100644 index 0000000..18f79c4 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/ForgeDependencyParser.java @@ -0,0 +1,58 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.parser; + +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.DependencySide; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.ForgeDependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersionRange; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.Ordering; +import me.andreasmelone.basicmodinfoparser.util.adapter.DataAdapter; +import me.andreasmelone.basicmodinfoparser.util.adapter.TomlAdapter; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class ForgeDependencyParser implements IDependencyParser { + @Override + public List parse(String[] keys, TomlAdapter modInfo) { + if (modInfo == null || keys.length == 0) return Collections.emptyList(); + + List dependencies = new ArrayList<>(); + + for (String dependencyKey : keys) { + modInfo.getArray(dependencyKey).ifPresent(dependenciesArray -> { + for (int i = 0; i < dependenciesArray.size(); i++) { + TomlAdapter dependencyAdapter = new TomlAdapter(dependenciesArray.getTable(i)); + dependencies.add(parseForgeDependency(dependencyAdapter)); + } + }); + } + return dependencies; + } + + /** + * Parses a {@link DataAdapter} into a {@link ForgeDependency} object. + *

+ * This method extracts the necessary fields from a DataAdapter and + * returns a corresponding {@link ForgeDependency} object. + *

+ * + * @param dependencyAdapter The DataAdapter containing the dependency's data. + * @return A {@link ForgeDependency} object constructed from the values in the given DataAdapter. + */ + @NotNull + private static ForgeDependency parseForgeDependency(TomlAdapter dependencyAdapter) { + String depModId = dependencyAdapter.getString("modId").orElse(""); + boolean mandatory = dependencyAdapter.getBoolean("mandatory").orElse(true); + String versionRange = dependencyAdapter.getString("versionRange").orElse(null); + String ordering = dependencyAdapter.getString("ordering").orElse("NONE"); + String side = dependencyAdapter.getString("side").orElse("BOTH"); + + Optional range = MavenVersionRange.parse(versionRange); + return new ForgeDependency( + depModId, range.orElse(null), mandatory, + Ordering.getFromString(ordering), DependencySide.getFromString(side) + ); + } +} diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/IDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/IDependencyParser.java new file mode 100644 index 0000000..99a3f69 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/IDependencyParser.java @@ -0,0 +1,18 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.parser; + +import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; +import me.andreasmelone.basicmodinfoparser.util.adapter.DataAdapter; + +import java.util.List; + +/** + * An interface for parsing dependencies from a mod adapter. + * This is a functional interface whose only method is {@link #parse(String[], DataAdapter)}.+ + * + * @param The type of the mod adapter + * @param The type of the dependency + */ +@FunctionalInterface +public interface IDependencyParser, D extends Dependency> { + List parse(String[] keys, T modAdapter); +} \ No newline at end of file diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/LegacyForgeDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/LegacyForgeDependencyParser.java new file mode 100644 index 0000000..6720c3a --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/LegacyForgeDependencyParser.java @@ -0,0 +1,62 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.parser; + +import com.google.gson.JsonElement; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.DependencySide; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.ForgeDependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersionRange; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.Ordering; +import me.andreasmelone.basicmodinfoparser.util.adapter.JsonAdapter; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; + +public class LegacyForgeDependencyParser implements IDependencyParser { + @Override + public List parse(String[] keys, JsonAdapter modAdapter) { + if (modAdapter == null || keys.length == 0) return Collections.emptyList(); + + List dependencies = new ArrayList<>(); + + for (String dependencyKey : keys) { + modAdapter.getArray(dependencyKey).ifPresent(dependenciesArray -> { + for (JsonElement element : dependenciesArray) { + if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) { + parseLegacyForgeDependency(element.getAsString()).ifPresent(dependencies::add); + } + } + }); + } + + return dependencies; + } + + private Optional parseLegacyForgeDependency(String dependencyString) { + String modId; + String version = null; + Ordering ordering = Ordering.NONE; + + String[] splitPrefix = dependencyString.split(":", 2); + if (splitPrefix.length > 1) { + ordering = Ordering.getFromString(splitPrefix[0]); + dependencyString = splitPrefix[1]; + } + + String[] splitVersion = dependencyString.split("@"); + if (splitVersion.length > 1) { + version = splitVersion[1]; + dependencyString = splitVersion[0]; + } + + modId = dependencyString; + + if (modId == null || modId.isEmpty()) { + return Optional.empty(); + } + + Optional range = MavenVersionRange.parse(version); + return Optional.of(new ForgeDependency(modId, range.orElse(null), true, ordering, DependencySide.BOTH)); + + } +} diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/QuiltDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/QuiltDependencyParser.java new file mode 100644 index 0000000..47f85fd --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/QuiltDependencyParser.java @@ -0,0 +1,196 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.parser; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.StandardDependency; +import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.FabricVersionRange; +import me.andreasmelone.basicmodinfoparser.util.adapter.JsonAdapter; + +import java.util.*; + +public class QuiltDependencyParser implements IDependencyParser { + + public List parse(final String[] keys, final JsonAdapter modAdapter) { + final List result = new ArrayList<>(); + + for (final String key : keys) { + if (!modAdapter.hasKey(key)) continue; + + final JsonElement element = modAdapter.getBackingObject().get(key); + + // In Quilt, all the dependency properties MUST be an array + // (https://github.com/QuiltMC/rfcs/blob/main/specification/0002-quilt.mod.json.md#the-depends-field) + // (https://github.com/QuiltMC/rfcs/blob/main/specification/0002-quilt.mod.json.md#the-breaks-field) + if (!element.isJsonArray()) continue; + + final JsonArray asArray = element.getAsJsonArray(); + if (asArray.size() == 0) continue; + + // Get the dependencies present in the current key + for (final JsonElement dependency : asArray) { + result.addAll(parseSingleDependency(dependency)); + } + } + + return result; + } + + /** + * Parses a single dependency json element. + * According to + * + * Quilt's official specification for dependency objects, a single Dependency can be of one of three types: + *
    + *
  • An object containing at least the {@code id} field
  • + *
  • A string mod identifier in the form of either {@code mavenGroup:modId} or {@code modId}
  • + *
  • An array of dependency objects
  • + *
+ * + * @param dependency The dependency JSON element. The type can be any of the aforementioned types. + * @return A list of dependencies, or an empty optional if the element is not a valid dependency + */ + private List parseSingleDependency( + final JsonElement dependency + ) { + final List result = new ArrayList<>(); + + // If the passed dependency's type is not an array, + // add it to an array so the parsing logic is the same. + final JsonArray jsonElements = dependency.isJsonArray() + ? dependency.getAsJsonArray() + : new JsonArray(); + + if (jsonElements.size() == 0) jsonElements.add(dependency); + + + for (final JsonElement dependencyElement : jsonElements) { + // The supported versions for the current dependency + List versions = new ArrayList<>(Collections.singletonList("*")); + + // First case: string + // A string mod identifier in the form of either "mavenGroup:modId" or "modId" + if (dependencyElement.isJsonPrimitive() && dependencyElement.getAsJsonPrimitive().isString()) { + final String asString = dependencyElement.getAsString(); + final String dependencyId = asString.contains(":") ? asString.split(":")[1] : asString; + + result.add(new StandardDependency<>( + dependencyId, + true, + FabricVersionRange.parse(versions.toArray(new String[0])).orElse(null) + )); + continue; + } + + // Second case: object + // An object containing at least the id field + if (dependencyElement.isJsonObject()) { + final JsonObject asObject = dependencyElement.getAsJsonObject(); + + // The `id` field is required + if (!asObject.has("id")) continue; + + final String dependencyId = asObject.get("id").getAsString(); + + // The `Optional` field + boolean mandatory = true; + if (asObject.has("optional")) + mandatory = !asObject.getAsJsonPrimitive("optional").getAsBoolean(); + + // The versions + versions = Arrays.asList(getVersionsFor(asObject)); + + result.add(new StandardDependency<>( + dependencyId, + mandatory, + FabricVersionRange.parse( + versions.toArray(new String[0]) + ).orElse(null) + )); + continue; + } + + // Third case: array + // An array of dependency objects + // Since it is a nested array with the same specifications as the previous + // cases, we can use recursion to parse it. + if (dependencyElement.isJsonArray()) result.addAll(parseSingleDependency(dependencyElement)); + } + + return result; + } + + /** + * Helper method to retrieve the {@code versions} array from a dependency. + * This method takes into account the + * + * Quilt official specification for the versions field. + * on GitHub. + * + * @param dependencyElement the {@link JsonAdapter} containing the dependency data + * @return an array of versions supported by the dependency + */ + private String[] getVersionsFor(JsonElement dependencyElement) { + List result = new ArrayList<>(); + + // The "versions" field can be a string, an array of strings (deprecated) or an object + + // First case: object + // In this case, the object must contain a single field, which must either be `any` or `all` + // The field value must be an array, with more constraints. Each element of the array must + // either be a string version specifier, or an object which is interpreted in the same way as + // the versions field itself. + if (dependencyElement.isJsonObject()) { + final JsonObject asObject = dependencyElement.getAsJsonObject(); + + if (asObject.has("any") || asObject.has("all")) { + final JsonElement any = asObject.get("any"); + final JsonElement all = asObject.get("all"); + final JsonObject notNull; + + // Find which key type the object has (must be either `any` or `all`) + // and use the non-null one to make a recursive call to getVersionsFor + if (any != null) notNull = any.getAsJsonObject(); + else if (all != null) notNull = all.getAsJsonObject(); + else throw new RuntimeException("Dependency element must have 'any' or 'all': " + dependencyElement); + + List embeddedVersions = Arrays.asList(getVersionsFor(notNull)); + // Embedded versions found, add them to the result list and return + result.addAll(embeddedVersions); + return result.toArray(new String[0]); + } + + // The required `any` or `all` fields were not found, return an empty list + return new String[0]; + } + + // Second and third cases: string, array of strings + + // As a string, the content should be a single version specifier defining the versions + // this dependency applies to. + + // As an array of strings, regardless of being deprecated, it should be + // an array of version specifiers defining the versions this dependency applies to. + + // Since there's no fundamental difference in the way a single version without an array + // and a version element inside an array are written, we can treat a single version string + // as a version array with only one element. + + JsonArray versionsArray = dependencyElement.isJsonArray() + ? dependencyElement.getAsJsonArray() + : new JsonArray(); + + if (versionsArray.size() == 0) + versionsArray.add(dependencyElement); // In this case, the version element turned out to be a single string + + // In the array, the only allowed type is string, so we don't have to worry about recursion here. + for (JsonElement versionElement : versionsArray) { + if (!versionElement.isJsonPrimitive() || !versionElement.getAsJsonPrimitive().isString()) continue; + + result.add(versionElement.getAsString()); + } + + return result.toArray(new String[0]); + } +} From 1fab74264c630f0f697c8ed1fe6ea9c4bbdcc1c1 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:20:07 -0300 Subject: [PATCH 13/42] refactor: remove type parameter from `ProvidedMod` --- .../basicmodinfoparser/platform/BasicModInfo.java | 2 +- .../platform/dependency/ProvidedMod.java | 10 +++++----- .../platform/dependency/StandardDependency.java | 6 +++--- .../platform/modinfo/ProvidesList.java | 4 ++-- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java index af86f85..e3f0d1f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java @@ -50,7 +50,7 @@ public interface BasicModInfo { * * @return the version of the mod */ - @Nullable Version getVersion(); + @Nullable Version getVersion(); /** * The mods description diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/ProvidedMod.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/ProvidedMod.java index a36a7bc..2ed6ec8 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/ProvidedMod.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/ProvidedMod.java @@ -27,11 +27,11 @@ import java.util.Objects; -public class ProvidedMod> { +public class ProvidedMod { private final String id; - private final Version version; + private final Version version; - public ProvidedMod(String id, Version version) { + public ProvidedMod(String id, Version version) { this.id = id; this.version = version; } @@ -40,7 +40,7 @@ public String getId() { return id; } - public Version getVersion() { + public Version getVersion() { return version; } @@ -55,7 +55,7 @@ public String toString() { @Override public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; - ProvidedMod that = (ProvidedMod) o; + ProvidedMod that = (ProvidedMod) o; return Objects.equals(id, that.id) && Objects.equals(version, that.version); } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/StandardDependency.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/StandardDependency.java index 0cf2422..4c3fb75 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/StandardDependency.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/StandardDependency.java @@ -31,7 +31,7 @@ import java.util.*; -public class StandardDependency> implements Dependency { +public class StandardDependency implements Dependency { protected final String id; protected final boolean mandatory; protected final VersionRange range; @@ -64,10 +64,10 @@ public boolean isMandatory() { if (mod instanceof ProvidesList) { ProvidesList providesList = (ProvidesList) mod; if (this.range == null || providesList.getType().isAssignableFrom(range.getType())) { - List> innerMods = new ArrayList<>(Optional.ofNullable(((ProvidesList) providesList).getProvidedIds()) + List innerMods = new ArrayList<>(Optional.ofNullable(((ProvidesList) providesList).getProvidedIds()) .orElse(Collections.emptyList())); - for (ProvidedMod innerMod : innerMods) { + for (ProvidedMod innerMod : innerMods) { if (innerMod.getId() == null || !innerMod.getId().equalsIgnoreCase(this.getModId())) continue; if (innerMod.getVersion() == null || this.range == null || !range.getType().isInstance(mod.getVersion())) { diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/ProvidesList.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/ProvidesList.java index 43bc259..b528d98 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/ProvidesList.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/ProvidesList.java @@ -28,12 +28,12 @@ import java.util.List; -public interface ProvidesList> { +public interface ProvidesList { /** * @return the provided mod IDs * @see ProvidedMod */ - List> getProvidedIds(); + List getProvidedIds(); /** * @return the type of the provided {@link Version} objects From 08f68c4eca548340076570aea3034064c833173e Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:27:03 -0300 Subject: [PATCH 14/42] clean: remove method `fabricKeys()` from `ModInfoKeys`. The property keys are not the same for fabric and quilt, so a helper method is not necessary --- .../platform/modinfo/model/ModInfoKeys.java | 23 ------------------- 1 file changed, 23 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java index 28b415c..9712ce7 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/model/ModInfoKeys.java @@ -80,29 +80,6 @@ public static ModInfoKeys forgeKeys() { ); } - /** - * Helper method to create a fabric-and-quilt-compliant {@link ModInfoKeys} - * - * @return a fabric-and-quilt-compliant {@link ModInfoKeys} - */ - public static ModInfoKeys fabricKeys() { - return new ModInfoKeys( - "id", - "name", - "version", - "description", - "icon", - "authors", - new String[]{ - "depends", - "recommends", - "suggests", - "breaks", - "conflicts" - } - ); - } - @Override public boolean equals(Object other) { if (other == null) return false; From 8a7d1a0425de18cb28db713599202b8a827633a7 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:33:51 -0300 Subject: [PATCH 15/42] refactor: remove `BreaksList` interface The interface was only being implemented inside `FabricModInfo`, at such point we're better off not using interfaces at all --- .../platform/modinfo/BreaksList.java | 35 ------------------- .../platform/modinfo/FabricModInfo.java | 13 ++----- 2 files changed, 3 insertions(+), 45 deletions(-) delete mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/BreaksList.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/BreaksList.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/BreaksList.java deleted file mode 100644 index a533a32..0000000 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/BreaksList.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2024-2025 RaydanOMGr - * - * 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 me.andreasmelone.basicmodinfoparser.platform.modinfo; - -import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; - -import java.util.List; - -public interface BreaksList { - /** - * @return a list of {@link Dependency} that this mod is incompatible with - */ - List getBreaks(); -} diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java index 8564344..00a9e05 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java @@ -35,9 +35,8 @@ import java.util.List; import java.util.Objects; -public class FabricModInfo extends StandardBasicModInfo implements BreaksList, ProvidesList { - private final List breaks; - private final List> provides; +public class FabricModInfo extends StandardBasicModInfo implements ProvidesList { + private final List provides; public FabricModInfo(@Nullable String id, @Nullable String name, @Nullable Version version, @Nullable String description, @Nullable List dependencies, @Nullable String iconPath, @NotNull Platform platform, @Nullable List breaks, @Nullable List> provides) { super(id, name, version, description, dependencies, iconPath, platform); @@ -46,13 +45,7 @@ public FabricModInfo(@Nullable String id, @Nullable String name, @Nullable Versi } @Override - public List getBreaks() { - if (breaks == null) return null; - return new ArrayList<>(breaks); - } - - @Override - public List> getProvidedIds() { + public List getProvidedIds() { if (provides == null) return null; return new ArrayList<>(provides); } From 9d2fa91863a97cc3e2da03a66dea2ea36d8c4d7c Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:38:22 -0300 Subject: [PATCH 16/42] fix: change MavenVersionRange to instantiate MavenVersion instead of using an unexistent static method --- .../dependency/forge/MavenVersionRange.java | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java index da68ce7..249785f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java @@ -23,6 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.forge; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.VersionRange; import java.util.*; @@ -93,13 +94,22 @@ public static Optional parse(String range) { String lowerStr = matcher.group(2).trim(); String upperStr = matcher.group(3).trim(); - MavenVersion lower = lowerStr.isEmpty() ? null : MavenVersion.parse(lowerStr).orElse(null); - MavenVersion upper = upperStr.isEmpty() ? null : MavenVersion.parse(upperStr).orElse(null); + MavenVersion lower = lowerStr.isEmpty() + ? null + : (MavenVersion) new MavenVersion().parse(lowerStr).orElse(null); + MavenVersion upper = upperStr.isEmpty() + ? null + : (MavenVersion) new MavenVersion().parse(upperStr).orElse(null); ranges.add(new Range(lower, lowerExclusive, upper, upperExclusive)); } else { - Optional exact = MavenVersion.parse(part.trim()); - exact.ifPresent(v -> ranges.add(new Range(v, false, v, false))); + Optional exact = new MavenVersion().parse(part.trim()); + exact.ifPresent(v -> ranges.add(new Range( + (MavenVersion) v, + false, + (MavenVersion) v, + false + ))); } } From 9ed2176df5790b6e9d786e3b4412f6983b77b715 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 19:55:00 -0300 Subject: [PATCH 17/42] fix: remove type parameter from interface `VersionRange` --- .../platform/dependency/version/VersionRange.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionRange.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionRange.java index a6f9dd2..e9e886f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionRange.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionRange.java @@ -23,7 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.version; -public interface VersionRange> { +public interface VersionRange { /** * @return the string representation of the version range */ From 434b336ea36ede0e24ae8c6c1081a50260f20f1f Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:38:41 -0300 Subject: [PATCH 18/42] fix: adapt more parts of the code to the Version Type parameter removal --- .../basicmodinfoparser/modfile/DependencyChecker.java | 4 ++-- .../platform/dependency/fabric/FabricVersionRange.java | 8 +++++--- .../platform/modinfo/StandardBasicModInfo.java | 4 ++-- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java index 17f1da3..c9fb737 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java @@ -56,7 +56,7 @@ public static Pair> checkDependencies(S javaInfo = new StandardBasicModInfo( "java", "Java", - LooseSemanticVersion.parse(javaVersion).orElse(null), + new LooseSemanticVersion().parse(javaVersion).orElse(null), "Java", new ArrayList<>(), null, @@ -67,7 +67,7 @@ public static Pair> checkDependencies(S BasicModInfo gameInfo = new StandardBasicModInfo( "minecraft", "Minecraft", - (isFabricBased ? LooseSemanticVersion.parse(gameVersion) : MavenVersion.parse(gameVersion)).orElse(null), + (isFabricBased ? new LooseSemanticVersion().parse(gameVersion) : new MavenVersion().parse(gameVersion)).orElse(null), "Minecraft", new ArrayList<>(), null, diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java index 0d743be..46692b7 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java @@ -23,6 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.fabric; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.VersionRange; import java.util.*; @@ -106,11 +107,11 @@ public static Optional parse(String... version) { if (operators == null || versionString == null || versionString.isEmpty()) continue; } - Optional parsedVersion = LooseSemanticVersion.parse(versionString, true); + Optional parsedVersion = new LooseSemanticVersion().parse(versionString, true); if (!parsedVersion.isPresent()) continue; if (operators.isEmpty()) operators.add(Operator.EQUALS); - versionConditions.add(new VersionCondition(operators, parsedVersion.get())); + versionConditions.add(new VersionCondition(operators, (LooseSemanticVersion) parsedVersion.get())); } allConditions.add(versionConditions); @@ -167,7 +168,8 @@ public boolean matches(LooseSemanticVersion version) { return true; } - if (operator == Operator.CARET || (operator == Operator.TILDE && this.version.getWildcardPositions().contains(1))) { + if (operator == Operator.CARET || (operator == Operator.TILDE && this.version.getWildcardPositions() + .contains(1))) { boolean isAboveLower = this.version.compareTo(version) <= 0; LooseSemanticVersion upperBound = this.version.increaseMajor(1); diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java index ee5473a..ef09505 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java @@ -37,7 +37,7 @@ public class StandardBasicModInfo implements BasicModInfo { private final String id; private final String name; - private final Version version; + private final Version version; private final String description; private final List dependencies; private final String iconPath; @@ -90,7 +90,7 @@ public String getName() { */ @Override @Nullable - public Version getVersion() { + public Version getVersion() { return version; } From ceb5a9b7a58c07cc856ba9d148c7b371a9e3b5fd Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:39:48 -0300 Subject: [PATCH 19/42] fix: remove `breaks` references from `FabricModInfo`'s `toString`, `equals` and `hashCode` methods. --- .../basicmodinfoparser/platform/modinfo/FabricModInfo.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java index 00a9e05..e95d5c3 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java @@ -58,8 +58,7 @@ public Class getType() { @Override public String toString() { return "FabricModInfo{" + - "breaks=" + breaks + - ", provides=" + provides + + "provides=" + provides + '}'; } @@ -68,11 +67,11 @@ public boolean equals(Object o) { if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; FabricModInfo that = (FabricModInfo) o; - return Objects.equals(breaks, that.breaks) && Objects.equals(provides, that.provides); + return Objects.equals(provides, that.provides); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), breaks, provides); + return Objects.hash(super.hashCode(), provides); } } From 6a4f4cfccbb38d6c7a93ab8775a6cfae314f3fa9 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:42:13 -0300 Subject: [PATCH 20/42] feat: added `authors` field support to `BasicModInfo`s As of the time of this commit, the field is not yet used --- .../basicmodinfoparser/platform/BasicModInfo.java | 5 +++++ .../platform/modinfo/FabricModInfo.java | 15 ++++++++++++--- .../platform/modinfo/StandardBasicModInfo.java | 12 ++++++++++-- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java index e3f0d1f..589f190 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/BasicModInfo.java @@ -76,4 +76,9 @@ public interface BasicModInfo { * @return the platform that this mod is for */ @NotNull Platform getPlatform(); + + /** + * @return the authors of the mod + */ + @Nullable List getAuthors(); } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java index e95d5c3..eaaf186 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java @@ -38,9 +38,18 @@ public class FabricModInfo extends StandardBasicModInfo implements ProvidesList { private final List provides; - public FabricModInfo(@Nullable String id, @Nullable String name, @Nullable Version version, @Nullable String description, @Nullable List dependencies, @Nullable String iconPath, @NotNull Platform platform, @Nullable List breaks, @Nullable List> provides) { - super(id, name, version, description, dependencies, iconPath, platform); - this.breaks = breaks != null ? new ArrayList<>(breaks) : null; + public FabricModInfo( + @Nullable String id, + @Nullable String name, + @Nullable Version version, + @Nullable String description, + @Nullable List dependencies, + @Nullable String iconPath, + @NotNull Platform platform, + @NotNull List authors, + @Nullable List provides + ) { + super(id, name, version, description, dependencies, iconPath, platform, authors); this.provides = provides != null ? new ArrayList<>(provides) : null; } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java index ef09505..69847e4 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java @@ -42,15 +42,17 @@ public class StandardBasicModInfo implements BasicModInfo { private final List dependencies; private final String iconPath; private final Platform platform; + private final List authors; public StandardBasicModInfo( @Nullable String id, @Nullable String name, - @Nullable Version version, + @Nullable Version version, @Nullable String description, @Nullable List dependencies, @Nullable String iconPath, - @NotNull Platform platform + @NotNull Platform platform, + @Nullable List authors ) { this.id = id; this.name = name; @@ -59,6 +61,7 @@ public StandardBasicModInfo( this.dependencies = dependencies != null ? new ArrayList<>(dependencies) : null; this.iconPath = iconPath; this.platform = platform; + this.authors = authors; } /** @@ -127,6 +130,10 @@ public List getDependencies() { return platform; } + public List getAuthors() { + return authors; + } + @Override public String toString() { return "StandardBasicModInfo{" + @@ -156,4 +163,5 @@ public int hashCode() { public static StandardBasicModInfo[] emptyArray() { return new StandardBasicModInfo[0]; } + } From 6765a0b8df19688675d05f894ed8c6cd084a7003 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:43:55 -0300 Subject: [PATCH 21/42] fix: check for strings before getting versionRange as object inside `FabricDependencyParser.parseSingleDependency` --- .../dependency/parser/FabricDependencyParser.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java index fc8b7e2..c6d777d 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/parser/FabricDependencyParser.java @@ -41,16 +41,20 @@ protected Optional parseSingleDependency(String key, JsonAdapter mod // Extract all the keys from the found object for (Map.Entry entry : dependencyObject.entrySet()) { + // A versionRange can be a string or an array of string final String dependencyId = entry.getKey(); - final JsonObject versionRange = entry.getValue().getAsJsonObject(); - - // VersionRange can be a string or an array of strings + final JsonElement versionRange = entry.getValue(); // Case 1: String if (versionRange.isJsonPrimitive() && versionRange.getAsJsonPrimitive().isString()) { Optional range = FabricVersionRange.parse(versionRange.getAsString()); - if (range.isPresent()) return Optional.of( - new StandardDependency<>(dependencyId, required, range.get()) + + return range.map(fabricVersionRange -> + new StandardDependency<>( + dependencyId, + required, + fabricVersionRange + ) ); } From 58c2ddfaf5368869f5a8272ee1e499ec474f7edc Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:44:57 -0300 Subject: [PATCH 22/42] fix: check for strings before getting object as an array inside `TomlAdapter.getListOrString` --- .../basicmodinfoparser/util/adapter/TomlAdapter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java index 36fa3ab..b86fa81 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/TomlAdapter.java @@ -26,11 +26,9 @@ import org.tomlj.TomlArray; import org.tomlj.TomlTable; -import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -69,6 +67,10 @@ public List getListOrString(String key) { return Collections.emptyList(); } + if (backingObject.isString(key)) { + return Collections.singletonList(backingObject.getString(key)); + } + Optional arrayOptional = getArray(key); return arrayOptional.map(tomlArray -> tomlArray.toList().stream() .map(Object::toString) From c584f61a8dbfeedf1c4410ea56770d0d1b84724a Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Thu, 20 Nov 2025 20:54:41 -0300 Subject: [PATCH 23/42] feat: full support to the new parsing system. This commit adapts the `ParserUtils` and `Platform` code structures to support the new system for parsing mod info. This change provides full implementations for handling all the supported mod loader formats. The core logic is now delegated to dedicated parser classes, making the system cleaner and easier to maintain. Key changes include: Concrete Parser Implementations: ForgeDependencyParser, LegacyForgeDependencyParser, FabricDependencyParser, and QuiltDependencyParser are now fully implemented to handle their respective metadata formats (mods.toml, legacy strings, fabric.mod.json, and quilt.mod.json). Integration with Platforms: The Platform enum now uses these new parsers to correctly extract dependency information for each mod loader, centralizing the parsing process. This new architecture makes the dependency analysis more understandable and provides a solid foundation for supporting additional mod loaders or format changes in the future. --- .../basicmodinfoparser/platform/Platform.java | 310 ++++++++------ .../basicmodinfoparser/util/ParserUtils.java | 401 ++---------------- 2 files changed, 197 insertions(+), 514 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java index 721e093..e853227 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java @@ -29,19 +29,27 @@ import me.andreasmelone.abstractzip.IZipEntry; import me.andreasmelone.abstractzip.IZipFile; import me.andreasmelone.abstractzip.IZipFileFactory; -import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; import me.andreasmelone.basicmodinfoparser.platform.dependency.ProvidedMod; -import me.andreasmelone.basicmodinfoparser.platform.dependency.StandardDependency; -import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.FabricVersionRange; import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.LooseSemanticVersion; import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; +import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.FabricDependencyParser; +import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.ForgeDependencyParser; +import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.LegacyForgeDependencyParser; +import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.QuiltDependencyParser; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import me.andreasmelone.basicmodinfoparser.platform.modinfo.FabricModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; import me.andreasmelone.basicmodinfoparser.util.ModInfoParseException; import me.andreasmelone.basicmodinfoparser.util.ParserUtils; +import me.andreasmelone.basicmodinfoparser.util.adapter.JsonAdapter; +import me.andreasmelone.basicmodinfoparser.util.adapter.TomlAdapter; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.tomlj.Toml; +import org.tomlj.TomlArray; +import org.tomlj.TomlParseResult; +import org.tomlj.TomlTable; import java.io.File; import java.io.IOException; @@ -50,11 +58,10 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.stream.StreamSupport; -import static me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys.fabricKeys; import static me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys.forgeKeys; -import static me.andreasmelone.basicmodinfoparser.util.ParserUtils.*; +import static me.andreasmelone.basicmodinfoparser.util.ParserUtils.GSON; +import static me.andreasmelone.basicmodinfoparser.util.ParserUtils.readEverythingAsString; public enum Platform { /** @@ -67,35 +74,31 @@ public enum Platform { "version", "description", "logoFile", - "authors", + "authorList", new String[]{"dependencies"} ), "mcmod.info" ) { @Override protected BasicModInfo[] parseFileData(String fileData) { + // In Legacy Forge, you can specify multiple mods per file (the root element is an array) JsonArray topArray = GSON.fromJson(fileData, JsonArray.class); + if (topArray == null || topArray.size() == 0) { return StandardBasicModInfo.emptyArray(); } + // Since the mod list is not empty, we iterate over all mods and parse each one List parsedInfos = new ArrayList<>(); for (JsonElement topArrayElement : topArray) { if (!topArrayElement.isJsonObject()) continue; + JsonObject modObject = topArrayElement.getAsJsonObject(); - List dependencyList = new ArrayList<>(); - if (modObject.has("dependencies") && modObject.get("dependencies").isJsonArray()) { - JsonArray dependenciesArray = modObject.getAsJsonArray("dependencies"); - for (JsonElement element : dependenciesArray) { - if (!element.isJsonPrimitive() || !element.getAsJsonPrimitive().isString()) { - continue; - } - parseLegacyForgeDependency(element.getAsString()).ifPresent(dependencyList::add); - } - } - parsedInfos.add(ParserUtils.createForgeModInfoFromJsonObject(modObject, - "modid", "name", "version", "description", "logoFile", - dependencyList, this + parsedInfos.add(ParserUtils.createModInfoFrom( + new JsonAdapter(modObject), + this, + new LegacyForgeDependencyParser(), + new MavenVersion() )); } @@ -113,12 +116,34 @@ protected BasicModInfo[] parseFileData(String fileData) { FORGE(forgeKeys(), "META-INF/mods.toml") { @Override protected BasicModInfo[] parseFileData(String fileData) { - return ParserUtils.parseForgelikeInfo(fileData, this); + final TomlParseResult parseResult = Toml.parse(fileData); + + final TomlArray mods = parseResult.getArray("mods"); + if (mods == null) return StandardBasicModInfo.emptyArray(); + + final List modInfos = new ArrayList<>(); + // I transferred Forge-specific logic to the Platform Enumeration + // (the capacity to provide multiple mods per file) + for (int i = 0; i < mods.size(); i++) { + final TomlTable modInfo = mods.getTable(i); + if (modInfo.isEmpty()) continue; + + final BasicModInfo info = ParserUtils.createModInfoFrom( + new TomlAdapter(modInfo), + this, + new ForgeDependencyParser(), + new MavenVersion() + ); + + modInfos.add(info); + } + + return modInfos.toArray(new BasicModInfo[0]); } @Override protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = MavenVersion.parse(loaderVersion); + Optional version = new MavenVersion().parse(loaderVersion); return new StandardBasicModInfo( "forge", "Forge", @@ -126,7 +151,8 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { "Modifications to the Minecraft base files to assist in compatibility between mods.", new ArrayList<>(), null, - this + this, + new ArrayList<>() ); } }, @@ -136,12 +162,20 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { NEOFORGE(forgeKeys(), "META-INF/neoforge.mods.toml") { @Override protected @NotNull BasicModInfo[] parseFileData(String fileData) { - return ParserUtils.parseForgelikeInfo(fileData, this); + final TomlTable modsTable = Toml.parse(fileData).getTable("mods"); + final BasicModInfo info = ParserUtils.createModInfoFrom( + new TomlAdapter(modsTable), + this, + new ForgeDependencyParser(), + new MavenVersion() + ); + + return new BasicModInfo[]{info}; } @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = MavenVersion.parse(loaderVersion); + Optional version = new MavenVersion().parse(loaderVersion); return new StandardBasicModInfo( "neoforge", "NeoForge", @@ -149,48 +183,84 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { "NeoForge is a free, open-source, community-oriented modding API for Minecraft.", new ArrayList<>(), null, - this + this, + new ArrayList<>() ); } }, + /** * Fabric platform, which uses the {@code fabric.mod.json} file. As the extension suggests, it stores data in JSON format. */ - FABRIC(fabricKeys(), "fabric.mod.json") { + FABRIC( + new ModInfoKeys( + "id", + "name", + "version", + "description", + "icon", + "authors", + new String[]{ + "depends", + "recommends", + "suggests", + "breaks", + "conflicts" + } + ), + "fabric.mod.json" + ) { @Override protected BasicModInfo[] parseFileData(String fileData) { JsonElement root = GSON.fromJson(fileData, JsonElement.class); if (root == null || (!root.isJsonArray() && !root.isJsonObject())) { return StandardBasicModInfo.emptyArray(); } + + // Fabric 0.4.0 or older allowed the root object to be an array + // (https://wiki.fabricmc.net/documentation:fabric_mod_json_spec#fabricmodjson_specification) JsonArray jsonArray = root.isJsonArray() ? root.getAsJsonArray() : new JsonArray(); + // Treat everything as a Json Array so we can iterate over it (Even if there's only one element) if (root.isJsonObject()) { jsonArray.add(root.getAsJsonObject()); } + // Now that we have the mod list, we iterate over all mods and parse each one List parsedInfos = new ArrayList<>(); for (JsonElement jsonArrayElement : jsonArray) { if (!jsonArrayElement.isJsonObject()) continue; - JsonObject jsonObject = jsonArrayElement.getAsJsonObject(); - - Optional version = LooseSemanticVersion.parse(getValidString(jsonObject, "version")); - - List dependencyList = new ArrayList<>(); - ParserUtils.parseFabricDependencies(dependencyList, jsonObject, "depends", true); - ParserUtils.parseFabricDependencies(dependencyList, jsonObject, "recommends", false); - List breaksList = new ArrayList<>(); - ParserUtils.parseFabricDependencies(breaksList, jsonObject, "breaks", true); - List> provided = new ArrayList<>(); - if (jsonObject.has("provides") && jsonObject.get("provides").isJsonArray()) { - for (JsonElement dependency : jsonObject.getAsJsonArray("provides")) { + final JsonObject modObject = jsonArrayElement.getAsJsonObject(); + + final BasicModInfo current = ParserUtils.createModInfoFrom( + new JsonAdapter(modObject), + this, + new FabricDependencyParser(), + new LooseSemanticVersion() + ); + + List providedMods = new ArrayList<>(); + if (modObject.has("provides") && modObject.get("provides").isJsonArray()) { + for (JsonElement dependency : modObject.getAsJsonArray("provides")) { if (!dependency.isJsonPrimitive() || !dependency.getAsJsonPrimitive().isString()) continue; - provided.add(new ProvidedMod<>(dependency.getAsString(), version.orElse(null))); + providedMods.add(new ProvidedMod(dependency.getAsString(), current.getVersion())); } } - parsedInfos.add(ParserUtils.createFabricModInfoFromJsonObject(jsonObject, - "id", "name", version.orElse(null), "description", "icon", - dependencyList, breaksList, provided, this)); + // Create a FabricModInfo with the above parsed information + // and add it to the list of parsed infos + parsedInfos.add( + new FabricModInfo( + current.getId(), + current.getName(), + current.getVersion(), + current.getDescription(), + current.getDependencies(), + current.getIconPath(), + current.getPlatform(), + current.getAuthors(), + providedMods + ) + ); } return parsedInfos.toArray(new BasicModInfo[0]); @@ -198,7 +268,7 @@ protected BasicModInfo[] parseFileData(String fileData) { @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = LooseSemanticVersion.parse(loaderVersion); + Optional version = new LooseSemanticVersion().parse(loaderVersion); return new StandardBasicModInfo( "fabricloader", "Fabric Loader", @@ -206,14 +276,29 @@ protected BasicModInfo[] parseFileData(String fileData) { "A flexible platform-independent mod loader designed for Minecraft and other games and applications.", new ArrayList<>(), null, - this + this, + new ArrayList<>() ); } }, + /** * Quilt platform, which uses the {@code quilt.mod.json} file. As the extensions suggests, it stores data in the JSON format. */ - QUILT(fabricKeys(), "quilt.mod.json") { + QUILT( + new ModInfoKeys( + "id", + "metadata.name", + "version", + "metadata.description", + "metadata.icon", + "metadata.contributors", + new String[]{ + "depends", "breaks" + } + ), + "quilt.mod.json" + ) { @Override protected BasicModInfo[] parseFileData(String fileData) { JsonObject jsonObj = GSON.fromJson(fileData, JsonObject.class); @@ -221,110 +306,48 @@ protected BasicModInfo[] parseFileData(String fileData) { if (!jsonObj.has("quilt_loader") || !jsonObj.get("quilt_loader").isJsonObject()) return StandardBasicModInfo.emptyArray(); - JsonObject quiltLoader = jsonObj.getAsJsonObject("quilt_loader"); - String modId = getValidString(quiltLoader, "id"); - String version = getValidString(quiltLoader, "version"); - - String name = null; - String description = null; - String iconPath = null; - if (quiltLoader.has("metadata") && quiltLoader.get("metadata").isJsonObject()) { - JsonObject metadata = quiltLoader.getAsJsonObject("metadata"); - name = getValidString(metadata, "name"); - description = getValidString(metadata, "description"); - iconPath = getValidString(metadata, "icon"); - } - List dependencies = new ArrayList<>(); - if (quiltLoader.has("depends") && quiltLoader.get("depends").isJsonArray()) { - for (JsonElement dependency : quiltLoader.getAsJsonArray("depends")) { - if (!dependency.isJsonObject()) continue; - JsonObject dependencyObject = dependency.getAsJsonObject(); - String dependencyId = getValidString(dependencyObject, "id"); - boolean isMandatory = true; - if (dependencyObject.has("optional") - && dependencyObject.get("optional").isJsonPrimitive() - && dependencyObject.get("optional").getAsJsonPrimitive().isString()) { - isMandatory = !dependencyObject.get("optional").getAsBoolean(); - } - String[] versions = new String[]{"*"}; - - if (dependencyObject.has("versions")) { - JsonElement dependencyVersion = dependencyObject.get("versions"); - if (dependencyVersion.isJsonPrimitive() && dependencyVersion.getAsJsonPrimitive().isString()) { - versions[0] = dependencyVersion.getAsString(); - } else if (dependencyVersion.isJsonArray()) { - versions = StreamSupport.stream(dependencyVersion.getAsJsonArray().spliterator(), false) - .filter((el) -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) - .map(JsonElement::getAsString) - .toArray(String[]::new); - } - } - - Optional fabricVersionRange = FabricVersionRange.parse(versions); - dependencies.add(new StandardDependency<>(dependencyId, isMandatory, fabricVersionRange.orElse(null))); - } - } - - List breaks = new ArrayList<>(); - if (quiltLoader.has("breaks") && quiltLoader.get("breaks").isJsonArray()) { - for (JsonElement dependency : quiltLoader.getAsJsonArray("breaks")) { - if (!dependency.isJsonObject()) continue; - JsonObject dependencyObject = dependency.getAsJsonObject(); - String dependencyId = getValidString(dependencyObject, "id"); - boolean isMandatory = true; - String[] versions = new String[]{"*"}; - - if (dependencyObject.has("versions")) { - JsonElement dependencyVersion = dependencyObject.get("versions"); - if (dependencyVersion.isJsonPrimitive() && dependencyVersion.getAsJsonPrimitive().isString()) { - versions[0] = dependencyVersion.getAsString(); - } else if (dependencyVersion.isJsonArray()) { - versions = StreamSupport.stream(dependencyVersion.getAsJsonArray().spliterator(), false) - .filter((el) -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) - .map(JsonElement::getAsString) - .toArray(String[]::new); - } - } - - Optional fabricVersionRange = FabricVersionRange.parse(versions); - breaks.add(new StandardDependency<>(dependencyId, isMandatory, fabricVersionRange.orElse(null))); - } - } - - List> provides = new ArrayList<>(); - if (quiltLoader.has("provides") && quiltLoader.get("provides").isJsonArray()) { - for (JsonElement dependency : quiltLoader.getAsJsonArray("provides")) { - if (!dependency.isJsonObject()) continue; - JsonObject dependencyObject = dependency.getAsJsonObject(); - String dependencyId = getValidString(dependencyObject, "id"); - String providedVersion = version; - - if (dependencyObject.has("versions")) { - JsonElement dependencyVersion = dependencyObject.get("versions"); - if (dependencyVersion.isJsonPrimitive() && dependencyVersion.getAsJsonPrimitive().isString()) { - providedVersion = dependencyVersion.getAsString(); - } - } + // Get the main quilt loader object + JsonAdapter quiltLoader = new JsonAdapter( + jsonObj.getAsJsonObject("quilt_loader") + ); - Optional semVer = LooseSemanticVersion.parse(providedVersion); - provides.add(new ProvidedMod<>(dependencyId, semVer.orElse(null))); - } - } + // NOTE: The "provides" logic had no effect ultimately. The provides list was being updated, but was not + // being used anywhere else. Should we make it a property inside BasicModInfo so it's usable? + + // List> provides = new ArrayList<>(); + // if (quiltLoader.has("provides") && quiltLoader.get("provides").isJsonArray()) { + // for (JsonElement dependency : quiltLoader.getAsJsonArray("provides")) { + // if (!dependency.isJsonObject()) continue; + // JsonObject dependencyObject = dependency.getAsJsonObject(); + // String dependencyId = getValidString(dependencyObject, "id"); + // String providedVersion = version; + // + // if (dependencyObject.has("versions")) { + // JsonElement dependencyVersion = dependencyObject.get("versions"); + // if (dependencyVersion.isJsonPrimitive() && dependencyVersion.getAsJsonPrimitive().isString()) { + // providedVersion = dependencyVersion.getAsString(); + // } + // } + // + // Optional semVer = LooseSemanticVersion.parse(providedVersion); + // provides.add(new ProvidedMod<>(dependencyId, semVer.orElse(null))); + // } + // } - Optional semanticVersion = LooseSemanticVersion.parse(version, false); return new BasicModInfo[]{ - new FabricModInfo(modId, name, - semanticVersion.orElse(null), - description, dependencies, iconPath, this, - breaks, provides + ParserUtils.createModInfoFrom( + quiltLoader, + this, + new QuiltDependencyParser(), + new LooseSemanticVersion() ) }; } @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = LooseSemanticVersion.parse(loaderVersion); + Optional version = new LooseSemanticVersion().parse(loaderVersion); return new StandardBasicModInfo( "quilt_loader", "Quilt Loader", @@ -332,7 +355,8 @@ protected BasicModInfo[] parseFileData(String fileData) { "The loader for mods under Quilt. It provides mod loading facilities and useful abstractions for other mods to use.", new ArrayList<>(), null, - this + this, + new ArrayList<>() ); } }; @@ -463,4 +487,8 @@ public static Platform[] findModPlatform(IZipFile zip) throws IOException { } return platforms.toArray(new Platform[0]); } + + public ModInfoKeys getModInfoKeys() { + return modInfoKeys; + } } diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java index 49158b2..47f3f9e 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java @@ -24,58 +24,27 @@ package me.andreasmelone.basicmodinfoparser.util; import com.google.gson.Gson; -import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; import me.andreasmelone.basicmodinfoparser.platform.BasicModInfo; import me.andreasmelone.basicmodinfoparser.platform.Platform; import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; -import me.andreasmelone.basicmodinfoparser.platform.dependency.ProvidedMod; -import me.andreasmelone.basicmodinfoparser.platform.dependency.StandardDependency; -import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.FabricVersionRange; -import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.LooseSemanticVersion; -import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.*; +import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.IDependencyParser; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; -import me.andreasmelone.basicmodinfoparser.platform.modinfo.FabricModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; +import me.andreasmelone.basicmodinfoparser.util.adapter.DataAdapter; import org.jetbrains.annotations.NotNull; -import org.tomlj.Toml; -import org.tomlj.TomlArray; -import org.tomlj.TomlParseResult; -import org.tomlj.TomlTable; import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.*; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.stream.StreamSupport; public class ParserUtils { public static final Gson GSON = new Gson(); - /** - * Compares an array of paths to a single path to check if any match after normalisation.

- * This ensures that two paths are considered equal, even if their string representations differ - * (e.g., {@code run/embeddium.jar} and {@code .\run\embeddium.jar}). - * - * @param paths An array of paths to compare. - * @param path2 The path to compare against. - * @return {@code true} if any of the provided paths are equal to {@code path2} after normalisation, otherwise {@code false}. - */ - public static boolean comparePaths(String[] paths, String path2) { - for (String path : paths) { - Path normalizedPath1 = Paths.get(path).normalize(); - Path normalizedPath2 = Paths.get(path2).normalize(); - if (normalizedPath1.equals(normalizedPath2)) return true; - } - return false; - } - /** * Reads the entire content of an {@link InputStream} and returns it as a string. * @@ -94,105 +63,49 @@ public static String readEverythingAsString(InputStream in) throws IOException { } /** - * Finds a value by key in a {@link JsonObject} and checks it against a predicate. - * - * @param jsonObject The {@link JsonObject} in which to search for the value. - * @param key The key for which the value needs to be found. - * @param predicate The predicate that the value of the key must satisfy. - * @return An {@link Optional} containing the {@link JsonElement} if it exists and matches the predicate, - * or a {@link Optional#empty()} if the value was not found or did not match. - */ - public static Optional findValidValue(JsonObject jsonObject, String key, Predicate predicate) { - if (!jsonObject.has(key)) { - return Optional.empty(); - } - - JsonElement element = jsonObject.get(key); - return predicate.test(element) ? Optional.of(element) : Optional.empty(); - } - - /** - * Helper method to fetch a valid string value from a {@link JsonObject} - * by key, ensuring it matches the specified predicate. - * - * @param obj The {@link JsonObject} from which to retrieve the value. - * @param key The key for the value to be retrieved. - * @param predicate The predicate that the value must satisfy. - * @return The string value if found and valid, or {@code null} if not found - * or invalid. - */ - public static String getValidString(JsonObject obj, String key, Predicate predicate) { - return findValidValue(obj, key, predicate) - .map(JsonElement::getAsString) - .orElse(null); - } - - /** - * Helper method to fetch a valid string value from a {@link JsonObject} - * by key. - * - * @param obj The {@link JsonObject} from which to retrieve the value. - * @param key The key for the value to be retrieved. - * @return The string value if found and valid, or {@code null} if not found - * or invalid. - */ - public static String getValidString(JsonObject obj, String key) { - return findValidValue(obj, key, (element) -> true) - .map(JsonElement::getAsString) - .orElse(null); - } - - /** - * Creates a {@link BasicModInfo} object from a {@link JsonObject}. + * Creates a {@link BasicModInfo} object from a {@link DataAdapter}. *

- * This method parses a JSON object, extracts the required fields (modId, displayName, version, - * and description), and creates a new {@link BasicModInfo} object along with any given dependencies. + * This method parses a data adapter, extracts the required fields, and creates a new {@link BasicModInfo} object along with any given dependencies. *

* - * @param modObject The {@link JsonObject} containing the mod information. + * @param modAdapter The {@link DataAdapter} containing the mod information. * @param platform The {@link Platform} this mod info belongs to. * @param dependenciesParser A {@link BiFunction} that takes an array of dependency keys and the - * {@link JsonObject} to parse dependencies from, returning a list of {@link Dependency}. + * {@link DataAdapter} to parse dependencies from, returning a list of {@link Dependency}. + * @param versionParser A function to parse the version string. * @return A {@link BasicModInfo} object containing the mod information and its dependencies. */ - public static BasicModInfo createModInfoFrom( - @NotNull JsonObject modObject, + public static , D extends Dependency> + @NotNull BasicModInfo createModInfoFrom( + @NotNull T modAdapter, @NotNull Platform platform, - @NotNull BiFunction> dependenciesParser + @NotNull IDependencyParser dependenciesParser, + @NotNull Version versionParser ) { // Get miscellaneous information final ModInfoKeys modInfoKeys = platform.getModInfoKeys(); - final Predicate isStringPredicate = element -> element.isJsonPrimitive() && element.getAsJsonPrimitive() - .isString(); - String modId = getValidString(modObject, modInfoKeys.modIdKey); - String name = getValidString(modObject, modInfoKeys.displayNameKey, isStringPredicate); - String description = getValidString(modObject, modInfoKeys.descriptionKey, isStringPredicate); - String version = getValidString(modObject, modInfoKeys.versionKey, isStringPredicate); - String logo = getValidString(modObject, modInfoKeys.logoFileKey, isStringPredicate); - Optional mavenVersion = MavenVersion.parse(version); + String modId = modAdapter.getString(modInfoKeys.modIdKey).orElse(null); + String name = modAdapter.getString(modInfoKeys.displayNameKey).orElse(null); + String description = modAdapter.getString(modInfoKeys.descriptionKey).orElse(null); + String version = modAdapter.getString(modInfoKeys.versionKey).orElse(null); + String logo = modAdapter.getString(modInfoKeys.logoFileKey).orElse(null); - // Get authors - ArrayList authorsList = new ArrayList<>(); - if (modObject.has(modInfoKeys.authorsKey)) { - JsonArray authorsJson = modObject.getAsJsonArray(modInfoKeys.authorsKey); - authorsList.ensureCapacity(authorsJson.size()); + // Parse Version + Optional parsedVersion = versionParser.parse(version); - for (JsonElement element : authorsJson) { - if (element.isJsonPrimitive() && element.getAsJsonPrimitive().isString()) - authorsList.add(element.getAsString()); - } - } + // Get authors + List authorsList = modAdapter.getListOrString(modInfoKeys.authorsKey); // Get dependencies - List dependencyList = dependenciesParser.apply( + List dependencyList = new ArrayList<>(dependenciesParser.parse( modInfoKeys.dependencyKeys, - modObject - ); + modAdapter + )); return new StandardBasicModInfo( modId, name, - mavenVersion.orElse(null), + parsedVersion.orElse(null), description, dependencyList, logo, @@ -201,264 +114,6 @@ public static BasicModInfo createModInfoFrom( ); } - /** - * Parses all the dependencies that are present in {@code modObject} and that are under the - * given {@code dependencyKeys}. - * - * @param dependencyKeys The keys under which the dependencies are located in the {@code modObject}. - * @param modObject The {@link JsonObject} containing the mod's information. - * @return A list of {@link Dependency} objects representing the parsed dependencies. - */ - public static List parseLegacyForgeDependencies(String[] dependencyKeys, JsonObject modObject) { - List dependencies = new ArrayList<>(); - for (String dependencyKey : dependencyKeys) { - // If no key is found, we continue - if (!modObject.has(dependencyKey) || !modObject.get(dependencyKey).isJsonArray()) { - continue; - } - - JsonArray dependenciesArray = modObject.getAsJsonArray(); - for (JsonElement element : dependenciesArray) { - // Not a string = continue - if (!element.isJsonPrimitive() || !element.getAsJsonPrimitive().isString()) continue; - Optional dependency = parseLegacyForgeDependency(element.getAsString()); - dependency.ifPresent(dependencies::add); - } - } - - return dependencies; - } - - /** - * Parses a dependency string into a {@link ForgeDependency} object. - *

- * This method parses a dependency string following the legacy Forge dependency format and returns - * a {@link ForgeDependency} object. The dependency string may include an ordering prefix and a - * version suffix, and these are parsed accordingly. - *

- * - * @param dependencyString The string representation of the dependency. The format is usually - * "ordering:modId@version". - * @return The parsed {@link ForgeDependency} object wrapped inside a {@link Optional}, can be {@link Optional#empty()} if invalid. Returns an {@link Optional#empty()} if the modId - * is empty or invalid. - */ - @NotNull - public static Optional parseLegacyForgeDependency(String dependencyString) { - String modId; - String version = null; - Ordering ordering = Ordering.NONE; - - String[] splitPrefix = dependencyString.split(":", 2); - if (splitPrefix.length > 1) { - ordering = Ordering.getFromString(splitPrefix[0]); - dependencyString = splitPrefix[1]; - } - - String[] splitVersion = dependencyString.split("@"); - if (splitVersion.length > 1) { - version = splitVersion[1]; - dependencyString = splitVersion[0]; - } - - modId = dependencyString; - - if (modId == null || modId.isEmpty()) { - return Optional.empty(); - } - - Optional range = MavenVersionRange.parse(version); - return Optional.of(new ForgeDependency(modId, range.orElse(null), true, ordering, DependencySide.BOTH)); - } - - /** - * Parses a {@link TomlTable} into a {@link ForgeDependency} object. - *

- * This method extracts the necessary fields from a TomlTable (a parsed TOML configuration) and - * returns a corresponding {@link ForgeDependency} object. - *

- * - * @param dependencyTable The TomlTable containing the dependency's data. - * @return A {@link ForgeDependency} object constructed from the values in the given TomlTable. - */ - @NotNull - public static Dependency parseForgeDependency(TomlTable dependencyTable) { - String depModId = dependencyTable.getString("modId"); - boolean mandatory = dependencyTable.getBoolean("mandatory", () -> true); - String versionRange = dependencyTable.getString("versionRange"); - String ordering = dependencyTable.getString("ordering"); - String side = dependencyTable.getString("side"); - - if (ordering == null) ordering = "NONE"; - if (side == null) side = "BOTH"; - - Optional range = MavenVersionRange.parse(versionRange); - return new ForgeDependency( - depModId, range.orElse(null), mandatory, - Ordering.getFromString(ordering), DependencySide.getFromString(side) - ); - } - - - /** - * Parses Fabric dependencies from a {@link JsonObject} and adds them to a given list of dependencies. - *

- * This method processes Fabric dependency entries within the given JSON object and adds them to the - * provided list. The dependencies may be either a single string or an array of strings. - *

- * - * @param dependencyList The list where parsed dependencies will be added. - * @param jsonObject The {@link JsonObject} containing the dependency data. - * @param key The key within the JSON object to retrieve the dependencies. - * @param mandatory Whether the dependency is mandatory or optional. - */ - public static void parseFabricDependencies(List dependencyList, JsonObject jsonObject, String key, boolean mandatory) { - if (jsonObject.has(key) && jsonObject.get(key).isJsonObject()) { - JsonObject depends = jsonObject.getAsJsonObject(key); - - depends.entrySet().forEach(entry -> { - String dependencyKey = entry.getKey(); - JsonElement dependency = entry.getValue(); - - if (dependency.isJsonPrimitive() && dependency.getAsJsonPrimitive().isString()) { - Optional range = FabricVersionRange.parse(dependency.getAsString()); - if (!range.isPresent()) return; - dependencyList.add(new StandardDependency<>(dependencyKey, mandatory, range.get())); - } else if (dependency.isJsonArray()) { - String[] resultingVersion = StreamSupport.stream(dependency.getAsJsonArray().spliterator(), false) - .filter((el) -> el.isJsonPrimitive() && el.getAsJsonPrimitive().isString()) - .map(JsonElement::getAsString) - .toArray(String[]::new); - Optional range = FabricVersionRange.parse(resultingVersion); - if (!range.isPresent()) return; - dependencyList.add(new StandardDependency<>(dependencyKey, mandatory, range.get())); - } - }); - } - } - - /** - * Creates a {@link BasicModInfo} object from a {@link JsonObject}. - *

- * This method parses a JSON object, extracts the required fields (modId, displayName, version, - * and description), and creates a new {@link BasicModInfo} object along with any given dependencies. - *

- * - * @param jsonObject The {@link JsonObject} containing the mod information. - * @param modIdKey The key used to retrieve the mod ID from the JSON object. - * @param displayNameKey The key used to retrieve the mod display name from the JSON object. - * @param versionKey The key used to retrieve the mod version from the JSON object. - * @param descriptionKey The key used to retrieve the mod description from the JSON object. - * @param logoFileKey The key used to retrieve the mod icon from the JSON object. - * @param dependencies The list of {@link Dependency} objects that the current mod depends on. - * @param platform The platform the mod is on. - * @return A {@link BasicModInfo} object containing the mod information and its dependencies. - */ - public static BasicModInfo createForgeModInfoFromJsonObject( - JsonObject jsonObject, - String modIdKey, - String displayNameKey, - String versionKey, - String descriptionKey, - String logoFileKey, - List dependencies, - Platform platform - ) { - Predicate isStringPredicate = element -> - element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); - - String modId = getValidString(jsonObject, modIdKey, isStringPredicate); - String name = getValidString(jsonObject, displayNameKey, isStringPredicate); - String description = getValidString(jsonObject, descriptionKey, isStringPredicate); - String version = getValidString(jsonObject, versionKey, isStringPredicate); - String logo = getValidString(jsonObject, logoFileKey, isStringPredicate); - - Optional mavenVersion = MavenVersion.parse(version); - return new StandardBasicModInfo(modId, name, mavenVersion.orElse(null), description, dependencies, logo, platform); - } - - /** - * Creates a {@link BasicModInfo} object from a {@link JsonObject}. - *

- * This method parses a JSON object, extracts the required fields (modId, displayName, version, - * and description), and creates a new {@link BasicModInfo} object along with any given dependencies. - * - * @param jsonObject The {@link JsonObject} containing the mod information. - * @param modIdKey The key used to retrieve the mod ID from the JSON object. - * @param displayNameKey The key used to retrieve the mod display name from the JSON object. - * @param version A parsed {@link Version} object - * @param descriptionKey The key used to retrieve the mod description from the JSON object. - * @param logoFileKey The key used to retrieve the mod icon from the JSON object. - * @param dependencies The list of {@link Dependency} objects that the current mod depends on. - * @param breaks The list of {@link Dependency} objects that the current mod is incompatible with. - * @return A {@link BasicModInfo} object containing the mod information and its dependencies. - */ - public static > BasicModInfo createFabricModInfoFromJsonObject( - JsonObject jsonObject, - String modIdKey, - String displayNameKey, - Version version, - String descriptionKey, - String logoFileKey, - List dependencies, - List breaks, - List> providedMods, - Platform platform - ) { - Predicate isStringPredicate = element -> - element.isJsonPrimitive() && element.getAsJsonPrimitive().isString(); - - String modId = getValidString(jsonObject, modIdKey, isStringPredicate); - String name = getValidString(jsonObject, displayNameKey, isStringPredicate); - String description = getValidString(jsonObject, descriptionKey, isStringPredicate); - String logo = getValidString(jsonObject, logoFileKey, isStringPredicate); - - return new FabricModInfo(modId, name, version, description, dependencies, logo, platform, breaks, providedMods); - } - - /** - * Parses info in a forge-like way - * - * @param fileData the toml file contents - * @param platform the platform under which to parse (usually {@link Platform#FORGE} or {@link Platform#NEOFORGE}) - * @return the parsed info - */ - public static BasicModInfo[] parseForgelikeInfo(String fileData, Platform platform) { - TomlParseResult result = Toml.parse(fileData); - TomlArray modsArray = result.getArray("mods"); - if (modsArray == null || modsArray.isEmpty()) return StandardBasicModInfo.emptyArray(); - - List parsedInfos = new ArrayList<>(); - for (int index = 0; index < modsArray.size(); index++) { - TomlTable modInfo = modsArray.getTable(index); - if (modInfo.isEmpty()) continue; - - String modId = modInfo.getString("modId"); - String name = modInfo.getString("displayName"); - String description = modInfo.getString("description"); - String version = modInfo.getString("version"); - String logoFile = modInfo.getString("logoFile"); - - List dependencies = new ArrayList<>(); - TomlArray dependenciesArray = result.getArray("dependencies." + modId); - - if (dependenciesArray != null && !dependenciesArray.isEmpty()) { - for (int i = 0; i < dependenciesArray.size(); i++) { - TomlTable dependencyTable = dependenciesArray.getTable(i); - if (dependencyTable != null && !dependencyTable.isEmpty()) { - dependencies.add(ParserUtils.parseForgeDependency(dependencyTable)); - } - } - } - - Optional mavenVersion = MavenVersion.parse(version); - parsedInfos.add(new StandardBasicModInfo( - modId, name, mavenVersion.orElse(null), description, - dependencies, logoFile, platform - )); - } - return parsedInfos.toArray(new BasicModInfo[0]); - } - public static String getTempDir() { return System.getProperty("java.io.tmpdir"); } From 21846c944f511f299424b453dac5df77d297f8b4 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:02:47 -0300 Subject: [PATCH 24/42] feat: create `VersionFactory` --- .../platform/dependency/version/VersionFactory.java | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionFactory.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionFactory.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionFactory.java new file mode 100644 index 0000000..71b617f --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/VersionFactory.java @@ -0,0 +1,8 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.version; + +import java.util.Optional; + +@FunctionalInterface +public interface VersionFactory { + Optional parseVersion(final String versionString); +} From 7d4d8605b00487793a33ffa965d3d2e52901c219 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:07:18 -0300 Subject: [PATCH 25/42] clean: remove `Version`'s default public constructor and its abstract `parse` method --- .../platform/dependency/version/Version.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java index 99afb0f..1dbe8c8 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/Version.java @@ -26,16 +26,12 @@ import java.util.Optional; public abstract class Version implements Comparable { - protected String stringRepresentation; - - public Version() { /* Empty Constructor */ } + protected final String stringRepresentation; protected Version(String stringRepresentation) { this.stringRepresentation = stringRepresentation; } - public abstract Optional parse(String versionString); - public Optional optional() { return Optional.of(this); } From 9435fc5198ca82c492d4626d16bf110442722286 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:10:31 -0300 Subject: [PATCH 26/42] refactor: move `MavenVersion`'s initializing logic to a proper `MavenVersionFactory` class --- .../dependency/forge/MavenVersion.java | 62 +++------------- .../version/MavenVersionFactory.java | 70 +++++++++++++++++++ 2 files changed, 78 insertions(+), 54 deletions(-) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/MavenVersionFactory.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java index 76b09dd..e2df5bd 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java @@ -37,59 +37,14 @@ * The format is quite complex, so this is not a fully compliant implementation. */ public class MavenVersion extends Version { - private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.]+"); - private static final Pattern STRING_PARSER = Pattern.compile("^(\\d*)?(\\D+)(\\d*)?$"); - private VersionSegment[] versionSegments; - - @Override - public Optional parse(String versionString) { - if (versionString == null || versionString.isEmpty() || !ALPHANUMERIC.matcher(versionString).matches()) return Optional.empty(); - - List segments = new ArrayList<>(); - - String noHyphens = versionString.replace("-", "."); - String[] splitByDot = noHyphens.split("\\."); - for (String segment : splitByDot) { - Matcher matcher = STRING_PARSER.matcher(segment); - if (!matcher.matches()) { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(segment))); - } catch (NumberFormatException ignored) { - // - } - - continue; - } - - String firstNumber = matcher.group(1); - String string = matcher.group(2); - String secondNumber = matcher.group(3); - - if (firstNumber != null && !firstNumber.isEmpty()) { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(firstNumber))); - } catch (NumberFormatException ignored) { - } - } - if (string != null && !string.isEmpty()) { - VersionSegment.QualifierVersionSegment.Qualifier qualifier = VersionSegment.QualifierVersionSegment.Qualifier.getByName(string); - if (qualifier == null) { - segments.add(new VersionSegment.StringVersionSegment(string)); - } else { - segments.add(new VersionSegment.QualifierVersionSegment(qualifier)); - } - } - if (secondNumber != null && !secondNumber.isEmpty()) { - try { - segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(secondNumber))); - } catch (NumberFormatException ignored) { - } - } - } - - this.stringRepresentation = versionString; - this.versionSegments = segments.toArray(new VersionSegment[0]); - return Optional.of(this); + private final VersionSegment[] versionSegments; + + public MavenVersion( + String versionString, + VersionSegment[] versionSegments + ) { + super(versionString); + this.versionSegments = versionSegments; } @Override @@ -121,7 +76,6 @@ public int compareTo(@NotNull Version other) { return 0; } - @Override public String toString() { return getStringRepresentation(); diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/MavenVersionFactory.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/MavenVersionFactory.java new file mode 100644 index 0000000..d430703 --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/MavenVersionFactory.java @@ -0,0 +1,70 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.version; + +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; +import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion.VersionSegment; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class MavenVersionFactory implements VersionFactory { + private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.]+"); + private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d*)?(\\D+)(\\d*)?$"); + + @Override + public Optional parseVersion(String versionString) { + if (versionString == null || versionString.isEmpty() || !ALPHANUMERIC.matcher(versionString).matches()) + return Optional.empty(); + + List segments = new ArrayList<>(); + + String noHyphens = versionString.replace("-", "."); + String[] splitByDot = noHyphens.split("\\."); + for (String segment : splitByDot) { + Matcher matcher = VERSION_PATTERN.matcher(segment); + if (!matcher.matches()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(segment))); + } catch (NumberFormatException ignored) { + // + } + + continue; + } + + String firstNumber = matcher.group(1); + String string = matcher.group(2); + String secondNumber = matcher.group(3); + + if (firstNumber != null && !firstNumber.isEmpty()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(firstNumber))); + } catch (NumberFormatException ignored) { + } + } + if (string != null && !string.isEmpty()) { + VersionSegment.QualifierVersionSegment.Qualifier qualifier = VersionSegment.QualifierVersionSegment.Qualifier.getByName(string); + if (qualifier == null) { + segments.add(new VersionSegment.StringVersionSegment(string)); + } else { + segments.add(new VersionSegment.QualifierVersionSegment(qualifier)); + } + } + if (secondNumber != null && !secondNumber.isEmpty()) { + try { + segments.add(new VersionSegment.NumberVersionSegment(Integer.parseUnsignedInt(secondNumber))); + } catch (NumberFormatException ignored) { + } + } + } + + return Optional.of( + new MavenVersion( + versionString, + segments.toArray(new VersionSegment[0]) + ) + ); + } +} From f2d6828c96a82aa25748b545c7e27db4788d6ebb Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:39:44 -0300 Subject: [PATCH 27/42] refactor: move `LooseSemanticVersion`'s initializing logic to a proper `LooseSemanticVersionFactory` class --- .../fabric/LooseSemanticVersion.java | 92 ++++--------------- .../version/LooseSemanticVersionFactory.java | 82 +++++++++++++++++ 2 files changed, 98 insertions(+), 76 deletions(-) create mode 100644 src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/LooseSemanticVersionFactory.java diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java index 745d340..df26c88 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java @@ -35,19 +35,22 @@ * Represents a looser version of the SemVer 2.0, which is accepted by fabric */ public class LooseSemanticVersion extends Version { - private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.+*]+"); - private static final Pattern REGEX = Pattern.compile("^([0-9xX*]+(?:\\.[0-9xX*]+)*)(-.*?)?(\\+.+)?$", Pattern.MULTILINE); - - private int[] versionParts; - private List wildcardPositions; - private String preReleaseSuffix; - private Integer preReleaseNumber; - private String buildMetadata; - private boolean usesWildcards; - - public LooseSemanticVersion() { /* Empty constructor */ } - - private LooseSemanticVersion(String stringRepresentation, int[] versionParts, List wildcardPositions, String preReleaseSuffix, Integer preReleaseNumber, String buildMetadata, boolean usesWildcards) { + private final int[] versionParts; + private final List wildcardPositions; + private final String preReleaseSuffix; + private final Integer preReleaseNumber; + private final String buildMetadata; + private final boolean usesWildcards; + + public LooseSemanticVersion( + String stringRepresentation, + int[] versionParts, + List wildcardPositions, + String preReleaseSuffix, + Integer preReleaseNumber, + String buildMetadata, + boolean usesWildcards + ) { super(stringRepresentation); this.versionParts = versionParts; this.wildcardPositions = wildcardPositions; @@ -204,69 +207,6 @@ public int hashCode() { return Objects.hash(Arrays.hashCode(versionParts), wildcardPositions, preReleaseSuffix, preReleaseNumber, buildMetadata, usesWildcards); } - @Override - public Optional parse(String versionString) { - return parse(versionString, false); - } - - public Optional parse(String ver, boolean wildcards) { - if (ver == null || ver.isEmpty() || !ALPHANUMERIC.matcher(ver).matches()) return Optional.empty(); - - Matcher matcher = REGEX.matcher(ver); - if (!matcher.matches()) return Optional.empty(); - String numbers = matcher.group(1); - String prerelease = matcher.group(2); - String metadata = matcher.group(3); - - if (prerelease != null && !prerelease.isEmpty()) { - prerelease = prerelease.substring(1); - } - - if (metadata != null && !metadata.isEmpty()) { - metadata = metadata.substring(1); - } - - String[] splitNumbers = numbers.split("\\."); - int[] versionInts = new int[splitNumbers.length]; - List wildcardPositions = new ArrayList<>(); - - for (int i = 0; i < splitNumbers.length; i++) { - String num = splitNumbers[i]; - if (num.equalsIgnoreCase("x") || num.equals("*")) { - if (!wildcards) return Optional.empty(); - versionInts[i] = 0; - wildcardPositions.add(i); - continue; - } - try { - versionInts[i] = Integer.parseUnsignedInt(num); - } catch (NumberFormatException ignored) { - return Optional.empty(); - } - } - - Integer prereleaseNumber = null; - if (prerelease != null) { - String[] prereleaseSplit = prerelease.split("\\.", 2); - if (prereleaseSplit.length > 1) { - try { - prereleaseNumber = Integer.parseInt(prereleaseSplit[1]); - prerelease = prereleaseSplit[0]; - } catch (NumberFormatException ignored) { - } - } - } - - this.wildcardPositions = wildcardPositions; - this.preReleaseSuffix = prerelease; - this.preReleaseNumber = prereleaseNumber; - this.buildMetadata = metadata; - this.versionParts = versionInts; - this.usesWildcards = wildcards; - - return Optional.of(this); - } - @Override public String getStringRepresentation() { return this.stringRepresentation; diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/LooseSemanticVersionFactory.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/LooseSemanticVersionFactory.java new file mode 100644 index 0000000..361b78f --- /dev/null +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/version/LooseSemanticVersionFactory.java @@ -0,0 +1,82 @@ +package me.andreasmelone.basicmodinfoparser.platform.dependency.version; + +import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.LooseSemanticVersion; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LooseSemanticVersionFactory implements VersionFactory { + private static final Pattern ALPHANUMERIC = Pattern.compile("[a-zA-Z0-9_\\-.+*]+"); + private static final Pattern VERSION_PATTERN = Pattern.compile("^([0-9xX*]+(?:\\.[0-9xX*]+)*)(-.*?)?(\\+.+)?$", Pattern.MULTILINE); + + @Override + public Optional parseVersion(String versionString) { + return parseVersion(versionString, false); + } + + public Optional parseVersion(String versionString, boolean wildcards) { + if (versionString == null || versionString.isEmpty() || !ALPHANUMERIC.matcher(versionString).matches()) + return Optional.empty(); + + Matcher matcher = VERSION_PATTERN.matcher(versionString); + if (!matcher.matches()) return Optional.empty(); + + String prerelease = matcher.group(2); + if (prerelease != null && !prerelease.isEmpty()) { + prerelease = prerelease.substring(1); + } + + String metadata = matcher.group(3); + if (metadata != null && !metadata.isEmpty()) { + metadata = metadata.substring(1); + } + + String numbers = matcher.group(1); + String[] splitNumbers = numbers.split("\\."); + int[] versionInts = new int[splitNumbers.length]; + List wildcardPositions = new ArrayList<>(); + + for (int i = 0; i < splitNumbers.length; i++) { + String num = splitNumbers[i]; + if (num.equalsIgnoreCase("x") || num.equals("*")) { + if (!wildcards) return Optional.empty(); + versionInts[i] = 0; + wildcardPositions.add(i); + continue; + } + try { + versionInts[i] = Integer.parseUnsignedInt(num); + } catch (NumberFormatException ignored) { + return Optional.empty(); + } + } + + Integer prereleaseNumber = null; + String[] prereleaseSplit = null; + if (prerelease != null) { + prereleaseSplit = prerelease.split("\\.", 2); + if (prereleaseSplit.length > 1) { + try { + prereleaseNumber = Integer.parseInt(prereleaseSplit[1]); + prerelease = prereleaseSplit[0]; + } catch (NumberFormatException ignored) { + } + } + } + + return Optional.of( + new LooseSemanticVersion( + versionString, + versionInts, + wildcardPositions, + prerelease, + prereleaseNumber, + metadata, + wildcards + ) + ); + } +} From 13b86fab9b3104c93d406ef962451e39655b5cd1 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:40:17 -0300 Subject: [PATCH 28/42] clean: remove unnecessary imports --- .../platform/dependency/forge/MavenVersion.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java index e2df5bd..fafd68c 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersion.java @@ -27,9 +27,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.Arrays; +import java.util.Objects; /** * This class partially implements mavens version format: ComparableVersion From df6240f0d6af4346186ee2a0aabadfe1081e3c53 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:45:33 -0300 Subject: [PATCH 29/42] refactor: make the platform enumerations pass their respective `VersionFactories` instead of the raw `Version` constructors --- .../basicmodinfoparser/platform/Platform.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java index e853227..cee475f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java @@ -36,7 +36,8 @@ import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.ForgeDependencyParser; import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.LegacyForgeDependencyParser; import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.QuiltDependencyParser; -import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.LooseSemanticVersionFactory; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.MavenVersionFactory; import me.andreasmelone.basicmodinfoparser.platform.modinfo.FabricModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; @@ -98,7 +99,7 @@ protected BasicModInfo[] parseFileData(String fileData) { new JsonAdapter(modObject), this, new LegacyForgeDependencyParser(), - new MavenVersion() + new MavenVersionFactory() )); } @@ -132,7 +133,7 @@ protected BasicModInfo[] parseFileData(String fileData) { new TomlAdapter(modInfo), this, new ForgeDependencyParser(), - new MavenVersion() + new MavenVersionFactory() ); modInfos.add(info); @@ -143,7 +144,7 @@ protected BasicModInfo[] parseFileData(String fileData) { @Override protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = new MavenVersion().parse(loaderVersion); + Optional version = new MavenVersionFactory().parseVersion(loaderVersion); return new StandardBasicModInfo( "forge", "Forge", @@ -167,7 +168,7 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { new TomlAdapter(modsTable), this, new ForgeDependencyParser(), - new MavenVersion() + new MavenVersionFactory() ); return new BasicModInfo[]{info}; @@ -175,7 +176,7 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = new MavenVersion().parse(loaderVersion); + Optional version = new MavenVersionFactory().parseVersion(loaderVersion); return new StandardBasicModInfo( "neoforge", "NeoForge", @@ -235,7 +236,7 @@ protected BasicModInfo[] parseFileData(String fileData) { new JsonAdapter(modObject), this, new FabricDependencyParser(), - new LooseSemanticVersion() + new LooseSemanticVersionFactory() ); List providedMods = new ArrayList<>(); @@ -268,7 +269,7 @@ protected BasicModInfo[] parseFileData(String fileData) { @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = new LooseSemanticVersion().parse(loaderVersion); + Optional version = new LooseSemanticVersionFactory().parseVersion(loaderVersion); return new StandardBasicModInfo( "fabricloader", "Fabric Loader", @@ -340,14 +341,14 @@ protected BasicModInfo[] parseFileData(String fileData) { quiltLoader, this, new QuiltDependencyParser(), - new LooseSemanticVersion() + new LooseSemanticVersionFactory() ) }; } @Override protected @NotNull BasicModInfo createNullableLoaderInfo(String loaderVersion) { - Optional version = new LooseSemanticVersion().parse(loaderVersion); + Optional version = new LooseSemanticVersionFactory().parseVersion(loaderVersion); return new StandardBasicModInfo( "quilt_loader", "Quilt Loader", From 0631e114ada0a561f8f7dd7ad1274f0121509b41 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:46:46 -0300 Subject: [PATCH 30/42] refactor: use `VersionFactory` instead of `Version` inside `ParserUtils` --- .../basicmodinfoparser/util/ParserUtils.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java index 47f3f9e..e01cbab 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/ParserUtils.java @@ -29,6 +29,7 @@ import me.andreasmelone.basicmodinfoparser.platform.dependency.Dependency; import me.andreasmelone.basicmodinfoparser.platform.dependency.parser.IDependencyParser; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.VersionFactory; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; import me.andreasmelone.basicmodinfoparser.platform.modinfo.model.ModInfoKeys; import me.andreasmelone.basicmodinfoparser.util.adapter.DataAdapter; @@ -40,7 +41,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; -import java.util.function.BiFunction; public class ParserUtils { public static final Gson GSON = new Gson(); @@ -70,9 +70,9 @@ public static String readEverythingAsString(InputStream in) throws IOException { * * @param modAdapter The {@link DataAdapter} containing the mod information. * @param platform The {@link Platform} this mod info belongs to. - * @param dependenciesParser A {@link BiFunction} that takes an array of dependency keys and the + * @param dependenciesParser A {@link IDependencyParser} that takes an array of dependency keys and the + * @param versionFactory A {@link VersionFactory} to parse the mod's version string into a {@link Version} object. * {@link DataAdapter} to parse dependencies from, returning a list of {@link Dependency}. - * @param versionParser A function to parse the version string. * @return A {@link BasicModInfo} object containing the mod information and its dependencies. */ public static , D extends Dependency> @@ -80,7 +80,7 @@ public static String readEverythingAsString(InputStream in) throws IOException { @NotNull T modAdapter, @NotNull Platform platform, @NotNull IDependencyParser dependenciesParser, - @NotNull Version versionParser + @NotNull VersionFactory versionFactory ) { // Get miscellaneous information final ModInfoKeys modInfoKeys = platform.getModInfoKeys(); @@ -91,7 +91,7 @@ public static String readEverythingAsString(InputStream in) throws IOException { String logo = modAdapter.getString(modInfoKeys.logoFileKey).orElse(null); // Parse Version - Optional parsedVersion = versionParser.parse(version); + Optional parsedVersion = versionFactory.parseVersion(version); // Get authors List authorsList = modAdapter.getListOrString(modInfoKeys.authorsKey); From c54147c220d63a036c2a743a4b8d00425c87457c Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 15:53:41 -0300 Subject: [PATCH 31/42] chore: annotated `BasicModInfo#getAuthors` with @Nullable --- .../platform/modinfo/StandardBasicModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java index 69847e4..62321df 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/StandardBasicModInfo.java @@ -130,7 +130,7 @@ public List getDependencies() { return platform; } - public List getAuthors() { + public @Nullable List getAuthors() { return authors; } From ace1ef6b3d3af1e60609d513d2ad55d61a1e42c2 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:28:00 -0300 Subject: [PATCH 32/42] chore: remove redundant `private` modifier from the `JarInJarPlatform` constructor --- .../basicmodinfoparser/jarinjar/JarInJarPlatform.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/jarinjar/JarInJarPlatform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/jarinjar/JarInJarPlatform.java index c51c2c7..e00f22b 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/jarinjar/JarInJarPlatform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/jarinjar/JarInJarPlatform.java @@ -123,7 +123,7 @@ public enum JarInJarPlatform { private final String[] metadataFiles; - private JarInJarPlatform(String... metadataFiles) { + JarInJarPlatform(String... metadataFiles) { this.metadataFiles = metadataFiles; } From b7d5d9452896d1f8ab2ae63bd2e3ca555c34680c Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:28:44 -0300 Subject: [PATCH 33/42] chore: added final modifier to `DataAdapter`'s backingObject field --- .../basicmodinfoparser/util/adapter/DataAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java index de739ee..b6d36a8 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/DataAdapter.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.function.Function; /** * An abstract class for standardizing access to data from different sources, @@ -40,7 +39,7 @@ public abstract class DataAdapter { /** * The underlying data source object. */ - protected T backingObject; + protected final T backingObject; /** * Constructs a new {@code DataAdapter} with the given backing object. From 7de49ff0ff08fc910801b188a277ebc3408a6dcf Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:29:16 -0300 Subject: [PATCH 34/42] chore: imports inside `LooseSemanticVersion` --- .../platform/dependency/fabric/LooseSemanticVersion.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java index df26c88..2173388 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/LooseSemanticVersion.java @@ -23,13 +23,13 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.fabric; -import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; import org.jetbrains.annotations.NotNull; -import java.util.*; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; /** * Represents a looser version of the SemVer 2.0, which is accepted by fabric From 4df8ae9e68ca8eb5731d207554667f533adc944b Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:30:12 -0300 Subject: [PATCH 35/42] refactor: make `DependencyChecker` use `VersionFactories` instead of `Version` constructors --- .../basicmodinfoparser/modfile/DependencyChecker.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java index c9fb737..65de783 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/modfile/DependencyChecker.java @@ -29,6 +29,8 @@ import me.andreasmelone.basicmodinfoparser.platform.dependency.PresenceStatus; import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.LooseSemanticVersion; import me.andreasmelone.basicmodinfoparser.platform.dependency.forge.MavenVersion; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.LooseSemanticVersionFactory; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.MavenVersionFactory; import me.andreasmelone.basicmodinfoparser.platform.modinfo.StandardBasicModInfo; import me.andreasmelone.basicmodinfoparser.util.Pair; @@ -56,7 +58,7 @@ public static Pair> checkDependencies(S javaInfo = new StandardBasicModInfo( "java", "Java", - new LooseSemanticVersion().parse(javaVersion).orElse(null), + new LooseSemanticVersionFactory().parseVersion(javaVersion).orElse(null), "Java", new ArrayList<>(), null, @@ -67,7 +69,10 @@ public static Pair> checkDependencies(S BasicModInfo gameInfo = new StandardBasicModInfo( "minecraft", "Minecraft", - (isFabricBased ? new LooseSemanticVersion().parse(gameVersion) : new MavenVersion().parse(gameVersion)).orElse(null), + (isFabricBased + ? new LooseSemanticVersionFactory().parseVersion(gameVersion) + : new MavenVersionFactory().parseVersion(gameVersion)) + .orElse(null), "Minecraft", new ArrayList<>(), null, From 2c5b693c90ec74f64f959180d3313d531c7c6d50 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:31:03 -0300 Subject: [PATCH 36/42] refactor: make `authors` constructor parameter @Nullable inside `FabricModInfo` --- .../basicmodinfoparser/platform/modinfo/FabricModInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java index eaaf186..d76cd0d 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/modinfo/FabricModInfo.java @@ -46,7 +46,7 @@ public FabricModInfo( @Nullable List dependencies, @Nullable String iconPath, @NotNull Platform platform, - @NotNull List authors, + @Nullable List authors, @Nullable List provides ) { super(id, name, version, description, dependencies, iconPath, platform, authors); From 611f8312df8c57b329d1a56d6c60c03faeace454 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:32:05 -0300 Subject: [PATCH 37/42] refactor: make `FabricVersionRange` use `VersionFactories` instead of `Version` constructors --- .../platform/dependency/fabric/FabricVersionRange.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java index 46692b7..367a8ac 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/fabric/FabricVersionRange.java @@ -23,7 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.fabric; -import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.LooseSemanticVersionFactory; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.VersionRange; import java.util.*; @@ -107,11 +107,11 @@ public static Optional parse(String... version) { if (operators == null || versionString == null || versionString.isEmpty()) continue; } - Optional parsedVersion = new LooseSemanticVersion().parse(versionString, true); + Optional parsedVersion = new LooseSemanticVersionFactory().parseVersion(versionString, true); if (!parsedVersion.isPresent()) continue; if (operators.isEmpty()) operators.add(Operator.EQUALS); - versionConditions.add(new VersionCondition(operators, (LooseSemanticVersion) parsedVersion.get())); + versionConditions.add(new VersionCondition(operators, parsedVersion.get())); } allConditions.add(versionConditions); From 550e42333e4d4a31923b907c5e76e37da63dea04 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:32:25 -0300 Subject: [PATCH 38/42] chore: remove unnecessary import --- .../basicmodinfoparser/util/adapter/JsonAdapter.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java index 8d396f6..019594b 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java @@ -31,7 +31,6 @@ import java.util.Collections; import java.util.List; import java.util.Optional; -import java.util.function.Function; /** * A {@link DataAdapter} implementation for {@link JsonObject}. This class From aca7e2c07028e7204f9c5da1dfae3f5519d3655e Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:33:13 -0300 Subject: [PATCH 39/42] refactor: make `MavenVersionRange` use `VersionFactories` instead of `Version` constructors --- .../platform/dependency/forge/MavenVersionRange.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java index 249785f..4d48979 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/dependency/forge/MavenVersionRange.java @@ -23,7 +23,7 @@ */ package me.andreasmelone.basicmodinfoparser.platform.dependency.forge; -import me.andreasmelone.basicmodinfoparser.platform.dependency.version.Version; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.MavenVersionFactory; import me.andreasmelone.basicmodinfoparser.platform.dependency.version.VersionRange; import java.util.*; @@ -96,18 +96,18 @@ public static Optional parse(String range) { MavenVersion lower = lowerStr.isEmpty() ? null - : (MavenVersion) new MavenVersion().parse(lowerStr).orElse(null); + : new MavenVersionFactory().parseVersion(lowerStr).orElse(null); MavenVersion upper = upperStr.isEmpty() ? null - : (MavenVersion) new MavenVersion().parse(upperStr).orElse(null); + : new MavenVersionFactory().parseVersion(upperStr).orElse(null); ranges.add(new Range(lower, lowerExclusive, upper, upperExclusive)); } else { - Optional exact = new MavenVersion().parse(part.trim()); + Optional exact = new MavenVersionFactory().parseVersion(part.trim()); exact.ifPresent(v -> ranges.add(new Range( - (MavenVersion) v, + v, false, - (MavenVersion) v, + v, false ))); } From 6a5017ebfdbf5a764a23fa7012a6d95e0b13018e Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:36:49 -0300 Subject: [PATCH 40/42] fix: parsing Neoforge mods was throwing an exception --- .../basicmodinfoparser/platform/Platform.java | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java index cee475f..cb7961f 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/platform/Platform.java @@ -163,15 +163,28 @@ protected BasicModInfo createNullableLoaderInfo(String loaderVersion) { NEOFORGE(forgeKeys(), "META-INF/neoforge.mods.toml") { @Override protected @NotNull BasicModInfo[] parseFileData(String fileData) { - final TomlTable modsTable = Toml.parse(fileData).getTable("mods"); - final BasicModInfo info = ParserUtils.createModInfoFrom( - new TomlAdapter(modsTable), - this, - new ForgeDependencyParser(), - new MavenVersionFactory() - ); + final TomlParseResult parsedFileData = Toml.parse(fileData); + + final TomlArray mods = parsedFileData.getArray("mods"); + if (mods == null) return StandardBasicModInfo.emptyArray(); + + final BasicModInfo[] result = new BasicModInfo[mods.size()]; + + for (int i = 0; i < mods.size(); i++) { + final TomlTable currentTable = mods.getTable(i); + if (currentTable.isEmpty()) continue; + + final BasicModInfo info = ParserUtils.createModInfoFrom( + new TomlAdapter(currentTable), + this, + new ForgeDependencyParser(), + new MavenVersionFactory() + ); + + result[i] = info; + } - return new BasicModInfo[]{info}; + return result; } @Override From e486f2660e01c307aa9e21c26dd15ac71c71c453 Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 16:57:01 -0300 Subject: [PATCH 41/42] refactor: make `LooseSemanticVersionParserTests.java` use `LooseSemanticVersionFactory` instead of the `LooseSemanticVersion` constructor --- .../test/LooseSemanticVersionParserTests.java | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/src/test/java/me/andreasmelone/basicmodinfoparser/test/LooseSemanticVersionParserTests.java b/src/test/java/me/andreasmelone/basicmodinfoparser/test/LooseSemanticVersionParserTests.java index 6cc9725..b726b8e 100644 --- a/src/test/java/me/andreasmelone/basicmodinfoparser/test/LooseSemanticVersionParserTests.java +++ b/src/test/java/me/andreasmelone/basicmodinfoparser/test/LooseSemanticVersionParserTests.java @@ -1,6 +1,7 @@ package me.andreasmelone.basicmodinfoparser.test; import me.andreasmelone.basicmodinfoparser.platform.dependency.fabric.LooseSemanticVersion; +import me.andreasmelone.basicmodinfoparser.platform.dependency.version.LooseSemanticVersionFactory; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -13,7 +14,7 @@ public class LooseSemanticVersionParserTests { class Basic { @Test void parsesSimpleVersion() { - Optional ver = LooseSemanticVersion.parse("1.0.0"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -23,7 +24,7 @@ void parsesSimpleVersion() { @Test void parsesWithMetadata() { - Optional ver = LooseSemanticVersion.parse("1.0.0+metadata"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0+metadata"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -36,7 +37,7 @@ void parsesWithMetadata() { class PreRelease { @Test void parsesWithoutNumber() { - Optional ver = LooseSemanticVersion.parse("1.0.0-alpha"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0-alpha"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -46,7 +47,7 @@ void parsesWithoutNumber() { @Test void parsesWithNumber() { - Optional ver = LooseSemanticVersion.parse("1.0.0-alpha.1"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0-alpha.1"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -56,7 +57,7 @@ void parsesWithNumber() { @Test void parsesWithEmptyPreRelease() { - Optional ver = LooseSemanticVersion.parse("1.2-"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.2-"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 2 }, ver.get().getVersionParts()); assertEquals("", ver.get().getPreReleaseSuffix()); @@ -69,7 +70,7 @@ void parsesWithEmptyPreRelease() { class Wildcard { @Test void parsesLowercaseXWildcard() { - Optional ver = LooseSemanticVersion.parse("1.2.x", true); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.2.x", true); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 2, 0 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -80,7 +81,7 @@ void parsesLowercaseXWildcard() { @Test void parsesUppercaseXWildcard() { - Optional ver = LooseSemanticVersion.parse("1.2.X", true); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.2.X", true); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 2, 0 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -91,7 +92,7 @@ void parsesUppercaseXWildcard() { @Test void parsesAsterixWildcard() { - Optional ver = LooseSemanticVersion.parse("1.2.*", true); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.2.*", true); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 2, 0 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -102,7 +103,7 @@ void parsesAsterixWildcard() { @Test void parsesWithMiddleWildcard() { - Optional ver = LooseSemanticVersion.parse("1.x.4", true); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.x.4", true); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 4 }, ver.get().getVersionParts()); assertNull(ver.get().getPreReleaseSuffix()); @@ -116,7 +117,7 @@ void parsesWithMiddleWildcard() { class General { @Test void parsesOrdinarySemVer() { - Optional ver = LooseSemanticVersion.parse("1.0.0-alpha.1+metadata"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0-alpha.1+metadata"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -126,7 +127,7 @@ void parsesOrdinarySemVer() { @Test void parsesWithLessComponents() { - Optional ver = LooseSemanticVersion.parse("1.0-alpha.1+metadata"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0-alpha.1+metadata"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -136,7 +137,7 @@ void parsesWithLessComponents() { @Test void parsesWithMoreComponents() { - Optional ver = LooseSemanticVersion.parse("1.0.0.0-alpha.1+metadata"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0.0-alpha.1+metadata"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 1, 0, 0, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -146,7 +147,7 @@ void parsesWithMoreComponents() { @Test void parsesRealVersion() { - Optional ver = LooseSemanticVersion.parse("11.0.0-alpha.3+0.102.0-1.21"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("11.0.0-alpha.3+0.102.0-1.21"); assertTrue(ver.isPresent()); assertArrayEquals(new int[] { 11, 0, 0 }, ver.get().getVersionParts()); assertEquals("alpha", ver.get().getPreReleaseSuffix()); @@ -159,19 +160,19 @@ void parsesRealVersion() { class Invalid { @Test void rejectsGarbage() { - Optional ver = LooseSemanticVersion.parse("potato"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("potato"); assertFalse(ver.isPresent()); } @Test void rejectsEmptyString() { - Optional ver = LooseSemanticVersion.parse(""); + Optional ver = new LooseSemanticVersionFactory().parseVersion(""); assertFalse(ver.isPresent()); } @Test void rejectsNonAlphanumeric() { - Optional ver = LooseSemanticVersion.parse("1.0.0-абрикос.2+不甜瓜"); + Optional ver = new LooseSemanticVersionFactory().parseVersion("1.0.0-абрикос.2+不甜瓜"); assertFalse(ver.isPresent()); } } From 6a7237e61b989d75347cf628da2e1c7c4124d3ad Mon Sep 17 00:00:00 2001 From: apenasolinco <120327456+ApenasOLinco@users.noreply.github.com> Date: Wed, 26 Nov 2025 17:35:44 -0300 Subject: [PATCH 42/42] fix: add `!isJsonNull()` check before any operations with the element in JsonAdapter --- .../basicmodinfoparser/util/adapter/JsonAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java index 019594b..f9af9dc 100644 --- a/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java +++ b/src/main/java/me/andreasmelone/basicmodinfoparser/util/adapter/JsonAdapter.java @@ -96,7 +96,7 @@ public List getListOrString(String key) { JsonElement element = backingObject.get(key); List result = new ArrayList<>(); - if (!element.isJsonArray() && !element.isJsonObject()) return Collections.emptyList(); + if (element.isJsonNull() || !element.isJsonArray() && !element.isJsonObject()) return Collections.emptyList(); // Since both on Forge Legacy (https://docs.minecraftforge.net/en/1.13.x/gettingstarted/structuring/) // and Fabric (https://wiki.fabricmc.net/documentation:fabric_mod_json#metadata) the