From 52500f9853f869e6847cf22bfe0ac0f26991a928 Mon Sep 17 00:00:00 2001 From: Eli Orona Date: Wed, 7 Aug 2024 23:25:25 -0700 Subject: [PATCH 1/4] Begin QMJ Writer --- .../qmj/InternalModMetadataJsonWriter.java | 208 ++++++++++++++++++ .../metadata/qmj/V1ModMetadataReader.java | 2 +- .../InternalModMetadataJsonWriterTest.java | 33 +++ 3 files changed, 242 insertions(+), 1 deletion(-) create mode 100644 src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java create mode 100644 src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java new file mode 100644 index 000000000..d32db0054 --- /dev/null +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java @@ -0,0 +1,208 @@ +/* + * Copyright 2022, 2023 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.quiltmc.loader.impl.metadata.qmj; + +import java.io.IOException; +import java.io.Writer; +import java.nio.file.Path; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; +import org.quiltmc.json5.JsonWriter; +import org.quiltmc.loader.api.LoaderValue; +import org.quiltmc.loader.api.ModDependency; +import org.quiltmc.loader.api.VersionConstraint; +import org.quiltmc.loader.api.VersionRange; +import org.quiltmc.loader.impl.util.QuiltLoaderInternal; +import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; + +@QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) +public class InternalModMetadataJsonWriter { + + public static void write(InternalModMetadata mod, Writer writer) throws IOException { + JsonWriter json = JsonWriter.json(writer); + ((JsonLoaderValue) toJson(mod)).write(json); + } + + public static void write(InternalModMetadata mod, Path path) throws IOException { + JsonWriter json = JsonWriter.json(path); + ((JsonLoaderValue) toJson(mod)).write(json); + } + + public static LoaderValue toJson(InternalModMetadata mod) { + // Put all the custom values (+more if QMJ impl). If this is empty then we cannot get the custom values anyway. + Map values = new TreeMap<>((o1, o2) -> { + // Force schema then quilt_loader + if (o1.equals("schemaVersion")) { + return -1; + } else if (o2.equals("schemaVersion")) { + return 1; + } + + if (o1.equals("quilt_loader")) { + return -1; + } else if (o2.equals("quilt_loader")) { + return 1; + } + + return o1.compareTo(o2); + }); + values.putAll(mod.values()); + + if (!values.containsKey("schemaVersion")) { + values.put("schemaVersion", new JsonLoaderValue.NumberImpl("", 1)); + } + + // Implementation does not have quilt_loader tag (likely FMJ wrapper) + if (!values.containsKey("quilt_loader")) { + Map quilt_loader = new LinkedHashMap<>(); + quilt_loader.put("id", string(mod.id())); + if (!mod.group().equals("loader.fabric")) { + quilt_loader.put("group", string(mod.group())); + } + quilt_loader.put("version", string(mod.version().raw())); + quilt_loader.put("entrypoints", + object( + mod + .getEntrypoints() + .entrySet() + .stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> array(entry.getValue().stream().map(entrypoint -> { + if (entrypoint instanceof AdapterLoadableClassEntry) { + AdapterLoadableClassEntry adapted = (AdapterLoadableClassEntry) entrypoint; + if (adapted.getAdapter().equals("default")) { + return string(adapted.getValue()); + } else { + Map value = new LinkedHashMap<>(); + value.put("adapter", string(adapted.getAdapter())); + value.put("value", string(adapted.getValue())); + return object(value); + } + } + return new JsonLoaderValue.NullImpl(""); + }))) + ) + ) + ); + quilt_loader.put("jars", array(mod.jars().stream().map(InternalModMetadataJsonWriter::string))); + quilt_loader.put("language_adapters", object(mod.languageAdapters().entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + adapter -> string(adapter.getValue()) + )))); + quilt_loader.put("depends", array(mod.depends().stream().map(InternalModMetadataJsonWriter::modDependency))); + quilt_loader.put("breaks", array(mod.breaks().stream().map(InternalModMetadataJsonWriter::modDependency))); + quilt_loader.put("repositories", array(mod.repositories().stream().map(InternalModMetadataJsonWriter::string))); + quilt_loader.put("load_type", string(mod.loadType().name().toLowerCase())); + quilt_loader.put("provides", array(mod.provides().stream().map(provided -> { + if (provided.version().equals(mod.version()) && !provided.id().contains(":")) { + return string(provided.id()); + } + + Map value = new LinkedHashMap<>(); + value.put("id", string(provided.id().substring(provided.id().indexOf(":" + 1)))); + if (provided.id().contains(":")) { + value.put("group", string(provided.id().substring(0, provided.id().indexOf(":")))); + } + value.put("version", string(provided.version().raw())); + + return object(value); + }))); + quilt_loader.put("intermediate_mappings", string(mod.intermediateMappings())); + // TODO: metadata + + values.put("quilt_loader", object(quilt_loader)); + } + + // TODO: Add Mixins + // TODO: Add AW + + // TODO: Add Environment? + + return object(values); + } + + @NotNull + private static JsonLoaderValue array(Stream values) { + return new JsonLoaderValue.ArrayImpl("", values.collect(Collectors.toList())); + } + + @NotNull + private static JsonLoaderValue array(List values) { + return new JsonLoaderValue.ArrayImpl("", values); + } + + @NotNull + private static JsonLoaderValue object(Map quilt_loader) { + return new JsonLoaderValue.ObjectImpl("", quilt_loader); + } + + private static JsonLoaderValue modDependency(ModDependency dep) { + if (dep instanceof ModDependency.Only) { + ModDependency.Only only = (ModDependency.Only) dep; + Map value = new LinkedHashMap<>(); + value.put("id", string(only.id().toString())); + + + if (only.optional()) { + value.put("optional", new JsonLoaderValue.BooleanImpl("", only.optional())); + } + if (!only.versionRange().equals(VersionRange.ANY)) { + value.put("version", version(only.versionRange())); + } + if (!only.reason().isEmpty()) { + value.put("reason", string(only.reason())); + } + if (only.unless() != null) { + value.put("unless", modDependency(only.unless())); + } + + if (value.size() == 1) { + return (JsonLoaderValue) value.get("id"); + } + + return object(value); + } else if (dep instanceof ModDependency.Any || dep instanceof ModDependency.All) { + Collection deps = (Collection) dep; + return array(deps.stream().map(InternalModMetadataJsonWriter::modDependency)); + } + + throw new IllegalStateException("Unknown Mod Dependency type! " + dep.getClass().getName()); + } + + @NotNull + private static JsonLoaderValue string(String only) { + return new JsonLoaderValue.StringImpl("", only); + } + + // TODO: test this + private static JsonLoaderValue version(VersionRange range) { + if (range.equals(VersionRange.ANY)) { + return string("*"); + } + + return array(range.convertToConstraints().stream().map(VersionConstraint::toString).map(InternalModMetadataJsonWriter::string)); + } +} diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java index 52c88f574..b4a544c63 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataReader.java @@ -227,7 +227,7 @@ private V1ModMetadataImpl readFields(JsonLoaderValue.ObjectImpl root) { throw parseException(repositoriesValue, "repositories must be an array"); } - readStringList((JsonLoaderValue.ArrayImpl) repositoriesValue, QLKeys.LOAD_TYPE, builder.repositories); + readStringList((JsonLoaderValue.ArrayImpl) repositoriesValue, QLKeys.REPOSITORIES, builder.repositories); } @Nullable diff --git a/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java new file mode 100644 index 000000000..d5afa90fe --- /dev/null +++ b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java @@ -0,0 +1,33 @@ +package org.quiltmc.loader.impl.metadata.qmj; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.IOException; +import java.io.StringWriter; + +import org.junit.jupiter.api.Test; +import org.quiltmc.loader.impl.fabric.metadata.FabricModMetadataReader; +import org.quiltmc.loader.impl.fabric.metadata.ParseMetadataException; +import org.quiltmc.loader.impl.metadata.FabricLoaderModMetadata; + +class InternalModMetadataJsonWriterTest { + + @Test + void toJson() throws IOException, ParseMetadataException { + FabricLoaderModMetadata read = FabricModMetadataReader.parseMetadata(new File(System.getProperty("user.dir")).toPath() + .resolve("src") + .resolve("test") + .resolve("resources") + .resolve("testing") + .resolve("parsing") + .resolve("fabric") + .resolve("spec") + .resolve("long.json")); + + + StringWriter writer = new StringWriter(); + InternalModMetadataJsonWriter.write(((InternalModMetadata) read.asQuiltModMetadata()), writer); + System.out.println(writer); + } +} \ No newline at end of file From ee9942b830423ec91924e86826827e24260637eb Mon Sep 17 00:00:00 2001 From: Eli Orona Date: Fri, 9 Aug 2024 23:09:45 -0700 Subject: [PATCH 2/4] Finished QMJ Writer --- .../qmj/InternalModMetadataJsonWriter.java | 451 +++++++++++++----- .../InternalModMetadataJsonWriterTest.java | 61 ++- 2 files changed, 384 insertions(+), 128 deletions(-) diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java index d32db0054..e313f3f19 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java @@ -1,5 +1,5 @@ /* - * Copyright 2022, 2023 QuiltMC + * Copyright 2024 QuiltMC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,26 +18,41 @@ import java.io.IOException; import java.io.Writer; +import java.lang.reflect.Field; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.jetbrains.annotations.NotNull; import org.quiltmc.json5.JsonWriter; import org.quiltmc.loader.api.LoaderValue; +import org.quiltmc.loader.api.ModContributor; import org.quiltmc.loader.api.ModDependency; +import org.quiltmc.loader.api.ModLicense; +import org.quiltmc.loader.api.ModMetadata; import org.quiltmc.loader.api.VersionConstraint; import org.quiltmc.loader.api.VersionRange; +import org.quiltmc.loader.api.plugin.ModMetadataExt; +import org.quiltmc.loader.impl.metadata.FabricLoaderModMetadata; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; +import net.fabricmc.api.EnvType; +import net.fabricmc.loader.api.metadata.ModEnvironment; + @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) public class InternalModMetadataJsonWriter { + private static final List SPECIFIC_KEY_ORDER = Stream.of("schema_version", "quilt_loader", "mixin", "access_widener", "minecraft").collect(Collectors.toList()); public static void write(InternalModMetadata mod, Writer writer) throws IOException { JsonWriter json = JsonWriter.json(writer); @@ -50,159 +65,371 @@ public static void write(InternalModMetadata mod, Path path) throws IOException } public static LoaderValue toJson(InternalModMetadata mod) { - // Put all the custom values (+more if QMJ impl). If this is empty then we cannot get the custom values anyway. Map values = new TreeMap<>((o1, o2) -> { - // Force schema then quilt_loader - if (o1.equals("schemaVersion")) { - return -1; - } else if (o2.equals("schemaVersion")) { - return 1; + if (o1.equals(o2)) { + return 0; } - if (o1.equals("quilt_loader")) { - return -1; - } else if (o2.equals("quilt_loader")) { - return 1; + for (String key : SPECIFIC_KEY_ORDER) { + if (o1.equals(key)) { + return -1; + } else if (o2.equals(key)) { + return 1; + } } return o1.compareTo(o2); }); - values.putAll(mod.values()); - if (!values.containsKey("schemaVersion")) { - values.put("schemaVersion", new JsonLoaderValue.NumberImpl("", 1)); + values.put("schema_version", new JsonLoaderValue.NumberImpl("", 1)); + values.put("quilt_loader", createQuiltLoader(mod)); + + LoaderValue mixin = createMixins(mod); + if (mixin != null) { + values.put("mixin", mixin); } - // Implementation does not have quilt_loader tag (likely FMJ wrapper) - if (!values.containsKey("quilt_loader")) { - Map quilt_loader = new LinkedHashMap<>(); - quilt_loader.put("id", string(mod.id())); - if (!mod.group().equals("loader.fabric")) { - quilt_loader.put("group", string(mod.group())); - } - quilt_loader.put("version", string(mod.version().raw())); - quilt_loader.put("entrypoints", - object( - mod - .getEntrypoints() - .entrySet() - .stream() - .collect( - Collectors.toMap( - Map.Entry::getKey, - entry -> array(entry.getValue().stream().map(entrypoint -> { - if (entrypoint instanceof AdapterLoadableClassEntry) { - AdapterLoadableClassEntry adapted = (AdapterLoadableClassEntry) entrypoint; - if (adapted.getAdapter().equals("default")) { - return string(adapted.getValue()); - } else { - Map value = new LinkedHashMap<>(); - value.put("adapter", string(adapted.getAdapter())); - value.put("value", string(adapted.getValue())); - return object(value); - } - } - return new JsonLoaderValue.NullImpl(""); - }))) - ) - ) - ); - quilt_loader.put("jars", array(mod.jars().stream().map(InternalModMetadataJsonWriter::string))); - quilt_loader.put("language_adapters", object(mod.languageAdapters().entrySet().stream().collect(Collectors.toMap( - Map.Entry::getKey, - adapter -> string(adapter.getValue()) - )))); - quilt_loader.put("depends", array(mod.depends().stream().map(InternalModMetadataJsonWriter::modDependency))); - quilt_loader.put("breaks", array(mod.breaks().stream().map(InternalModMetadataJsonWriter::modDependency))); - quilt_loader.put("repositories", array(mod.repositories().stream().map(InternalModMetadataJsonWriter::string))); - quilt_loader.put("load_type", string(mod.loadType().name().toLowerCase())); - quilt_loader.put("provides", array(mod.provides().stream().map(provided -> { - if (provided.version().equals(mod.version()) && !provided.id().contains(":")) { - return string(provided.id()); + LoaderValue accessWidener = createAccessWidener(mod); + if (accessWidener != null) { + values.put("access_widener", accessWidener); + } + + LoaderValue minecraft = createMinecraft(mod); + if (minecraft != null) { + values.put("minecraft", minecraft); + } + + // Put all the custom values (+more if QMJ impl). If this is empty then we cannot get the custom values anyway. + mod.values().forEach((s, loaderValue) -> { + if (!values.containsKey(s)) { + values.put(s, loaderValue); + } + }); + + return object(values); + } + + private static LoaderValue createQuiltLoader(InternalModMetadata mod) { + return object(quiltLoader -> { + + quiltLoader.put("id", string(mod.id())); + // This is required but there isn't a good way to set it without knowing + quiltLoader.put("group", string(mod.group())); + quiltLoader.put("version", string(mod.version().raw())); + + quiltLoader.put("metadata", createMetadata(mod)); + + putEmptyMap("entrypoints", mod.getEntrypoints(), k -> k, InternalModMetadataJsonWriter::entrypoints, quiltLoader); + + putEmptyCollection("depends", mod.depends(), InternalModMetadataJsonWriter::modDependency, quiltLoader); + putEmptyCollection("breaks", mod.breaks(), InternalModMetadataJsonWriter::modDependency, quiltLoader); + + putEmptyCollection("provides", mod.provides(), InternalModMetadataJsonWriter.provided(mod), quiltLoader); + + if (mod.loadType() != ModMetadataExt.ModLoadType.IF_REQUIRED) { + quiltLoader.put("load_type", string(mod.loadType().name().toLowerCase())); + } + + if (!mod.intermediateMappings().equals("org.quiltmc:hashed")) { + quiltLoader.put("intermediate_mappings", string(mod.intermediateMappings())); + } + + putEmptyCollection("jars", mod.jars(), InternalModMetadataJsonWriter::string, quiltLoader); + putEmptyCollection("repositories", mod.repositories(), InternalModMetadataJsonWriter::string, quiltLoader); + + putEmptyMap("language_adapters", mod.languageAdapters(), k -> k, InternalModMetadataJsonWriter::string, quiltLoader); + }); + } + + private static JsonLoaderValue entrypoints(Collection entrypoints) { + return array(entrypoints.stream().map(entrypoint -> { + if (entrypoint instanceof AdapterLoadableClassEntry) { + AdapterLoadableClassEntry adapted = (AdapterLoadableClassEntry) entrypoint; + if (adapted.getAdapter().equals("default")) { + return string(adapted.getValue()); } - Map value = new LinkedHashMap<>(); - value.put("id", string(provided.id().substring(provided.id().indexOf(":" + 1)))); - if (provided.id().contains(":")) { - value.put("group", string(provided.id().substring(0, provided.id().indexOf(":")))); + return object(value -> { + value.put("adapter", string(adapted.getAdapter())); + value.put("value", string(adapted.getValue())); + }); + } + + throw new IllegalStateException("Unknown Mod Dependency type! " + entrypoint.getClass().getName()); + })); + } + + private static JsonLoaderValue modDependency(ModDependency dep) { + if (dep instanceof ModDependency.Only) { + ModDependency.Only only = (ModDependency.Only) dep; + JsonLoaderValue.ObjectImpl obj = object(value -> { + value.put("id", string(only.id().toString())); + + if (only.optional()) { + value.put("optional", new JsonLoaderValue.BooleanImpl("", only.optional())); } - value.put("version", string(provided.version().raw())); + if (!only.versionRange().equals(VersionRange.ANY)) { + value.put("version", version(only.versionRange())); + } + if (!only.reason().isEmpty()) { + value.put("reason", string(only.reason())); + } + if (only.unless() != null) { + value.put("unless", modDependency(only.unless())); + } + }); - return object(value); - }))); - quilt_loader.put("intermediate_mappings", string(mod.intermediateMappings())); - // TODO: metadata + if (obj.size() == 1) { + return obj.get("id"); + } - values.put("quilt_loader", object(quilt_loader)); + return obj; + } else if (dep instanceof ModDependency.Any || dep instanceof ModDependency.All) { + Collection deps = (Collection) dep; + return array(deps.stream().map(InternalModMetadataJsonWriter::modDependency)); } - // TODO: Add Mixins - // TODO: Add AW + throw new IllegalStateException("Unknown Mod Dependency type! " + dep.getClass().getName()); + } - // TODO: Add Environment? + private static JsonLoaderValue version(VersionRange range) { + // TODO: test this + if (range.equals(VersionRange.ANY)) { + return string("*"); + } - return object(values); + return array(range.convertToConstraints().stream().map(VersionConstraint::toString).map(InternalModMetadataJsonWriter::string)); } - @NotNull - private static JsonLoaderValue array(Stream values) { - return new JsonLoaderValue.ArrayImpl("", values.collect(Collectors.toList())); + private static

Function provided(InternalModMetadata mod) { + return provided -> { + if (provided.version().equals(mod.version()) && !provided.id().contains(":")) { + return string(provided.id()); + } + + Map value = new LinkedHashMap<>(); + value.put("id", string(provided.id().substring(provided.id().indexOf(":" + 1)))); + if (provided.id().contains(":")) { + value.put("group", string(provided.id().substring(0, provided.id().indexOf(":")))); + } + + value.put("version", string(provided.version().raw())); + + return object(value); + }; } - @NotNull - private static JsonLoaderValue array(List values) { - return new JsonLoaderValue.ArrayImpl("", values); + private static JsonLoaderValue createMetadata(InternalModMetadata mod) { + return object(metadata -> { + metadata.put("name", string(mod.name())); + if (!mod.description().isEmpty()) { + metadata.put("description", string(mod.description())); + } + + putEmptyMap("contributors", mod.contributors(), ModContributor::name, contributor -> { + if (contributor.roles().size() == 1) { + return string(contributor.roles().iterator().next()); + } + return array(contributor.roles().stream().map(InternalModMetadataJsonWriter::string)); + }, metadata); + + putEmptyMap("contact", mod.contactInfo(), Function.identity(), InternalModMetadataJsonWriter::string, metadata); + + // Special case a single license + if (mod.licenses().size() == 1) { + ModLicense license = mod.licenses().iterator().next(); + metadata.put("licence", createLicense(license)); + } else { + putEmptyCollection("license", mod.licenses(), InternalModMetadataJsonWriter::createLicense, metadata); + } + + + LoaderValue iconJson = getIcon(mod); + if (iconJson != null) { + metadata.put("icon", iconJson); + } + }); } - @NotNull - private static JsonLoaderValue object(Map quilt_loader) { - return new JsonLoaderValue.ObjectImpl("", quilt_loader); + private static JsonLoaderValue createLicense(ModLicense license) { + // Object equality here is fine because it comes from a map + if (ModLicense.fromIdentifier(license.id()) == license) { + return string(license.id()); + } + + return object(licenseObj -> { + licenseObj.put("name", string(license.name())); + licenseObj.put("id", string(license.id())); + licenseObj.put("url", string(license.url())); + licenseObj.put("description", string(license.description())); + }); } - private static JsonLoaderValue modDependency(ModDependency dep) { - if (dep instanceof ModDependency.Only) { - ModDependency.Only only = (ModDependency.Only) dep; - Map value = new LinkedHashMap<>(); - value.put("id", string(only.id().toString())); + private static LoaderValue getIcon(InternalModMetadata mod) { + // Cursed time! + if (mod instanceof V1ModMetadataImpl) { + try { + Field iconsField = mod.getClass().getDeclaredField("icons"); + iconsField.setAccessible(true); + Icons icons = (Icons) iconsField.get(mod); + if (icons instanceof Icons.Single) { + String icon = icons.getIcon(0); + if (icon == null) { + return null; + } + return string(icon); + } else if (icons instanceof Icons.Multiple) { + Field mapField = Icons.Multiple.class.getDeclaredField("icons"); + mapField.setAccessible(true); + SortedMap map = (SortedMap) mapField.get(icons); + return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } else if (mod instanceof FabricModMetadataWrapper) { + FabricLoaderModMetadata fabric = mod.asFabricModMetadata(); + if (fabric.getClass().getSimpleName().equals("V1ModMetadataFabric")) { + try { + // V1ModMetadataFabric.IconEntry + Field iconField = fabric.getClass().getDeclaredField("icon"); + iconField.setAccessible(true); + Object icons = iconField.get(fabric); - if (only.optional()) { - value.put("optional", new JsonLoaderValue.BooleanImpl("", only.optional())); + if (icons.getClass().getName().contains("Single")) { + Field stringField = icons.getClass().getDeclaredField("icon"); + stringField.setAccessible(true); + return string((String) stringField.get(icons)); + } else if (icons.getClass().getName().contains("MapEntry")) { + Field iconsField = icons.getClass().getDeclaredField("icons"); + iconsField.setAccessible(true); + SortedMap map = (SortedMap) iconsField.get(icons); + return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); + } + } catch (Exception e) { + throw new RuntimeException(e); + } } - if (!only.versionRange().equals(VersionRange.ANY)) { - value.put("version", version(only.versionRange())); + } + + return null; + } + + private static LoaderValue createMixins(InternalModMetadata mod) { + Set client = new HashSet<>(mod.mixins(EnvType.CLIENT)); + Set server = new HashSet<>(mod.mixins(EnvType.SERVER)); + + if (client.isEmpty() && server.isEmpty()) { + return null; + } + + Set shared = client.stream().filter(server::contains).collect(Collectors.toSet()); + + LoaderValue mixin; + if (client.size() == 1 && server.size() == 1 && shared.size() == 1) { // Client and server both have the same entry + mixin = string(shared.iterator().next()); + } else if (client.size() == 1 && server.isEmpty()) { // Client only mixin + mixin = mixinObject(client.iterator().next(), "client"); + } else if (server.size() == 1 && client.isEmpty()) { // Server only mixin + mixin = mixinObject(server.iterator().next(), "dedicated_server"); + } else { // We know we have an array + List mixins = new ArrayList<>(); + for (String config : shared) { + mixins.add(string(config)); } - if (!only.reason().isEmpty()) { - value.put("reason", string(only.reason())); + + for (String config : client) { + if (!shared.contains(config)) { + mixins.add(mixinObject(config, "client")); + } } - if (only.unless() != null) { - value.put("unless", modDependency(only.unless())); + + for (String config : server) { + if (!shared.contains(config)) { + mixins.add(mixinObject(config, "dedicated_server")); + } } - if (value.size() == 1) { - return (JsonLoaderValue) value.get("id"); + mixin = array(mixins); + } + + return mixin; + } + + private static JsonLoaderValue mixinObject(String config, String env) { + return object(objectValues -> { + objectValues.put("config", string(config)); + objectValues.put("environment", string(env)); + }); + } + + private static LoaderValue createAccessWidener(InternalModMetadata mod) { + if (mod.accessWideners().isEmpty()) { + return null; + } + + if (mod.accessWideners().size() == 1) { + return string(mod.accessWideners().iterator().next()); + } else { + return array(mod.accessWideners().stream().map(InternalModMetadataJsonWriter::string)); + } + } + + private static LoaderValue createMinecraft(InternalModMetadata mod) { + if (mod.environment().equals(ModEnvironment.UNIVERSAL)) { + return null; + } + + return object(minecraft -> { + switch (mod.environment()) { + case CLIENT: + minecraft.put("environment", string("client")); + break; + case SERVER: + minecraft.put("environment", string("dedicated_server")); + break; } + }); + } - return object(value); - } else if (dep instanceof ModDependency.Any || dep instanceof ModDependency.All) { - Collection deps = (Collection) dep; - return array(deps.stream().map(InternalModMetadataJsonWriter::modDependency)); + private static void putEmptyCollection(String field, Collection collection, Function converter, Map object) { + if (!collection.isEmpty()) { + object.put(field, array(collection.stream().map(converter))); } + } - throw new IllegalStateException("Unknown Mod Dependency type! " + dep.getClass().getName()); + private static void putEmptyMap(String field, Map map, Function key, Function value, Map object) { + if (!map.isEmpty()) { + object.put(field, object(obj -> map.forEach((k, v) -> obj.put(key.apply(k), value.apply(v))))); + } + } + + private static void putEmptyMap(String field, Collection map, Function key, Function value, Map object) { + if (!map.isEmpty()) { + object.put(field, object(obj -> map.forEach((t) -> obj.put(key.apply(t), value.apply(t))))); + } } - @NotNull - private static JsonLoaderValue string(String only) { + private static JsonLoaderValue.StringImpl string(String only) { return new JsonLoaderValue.StringImpl("", only); } - // TODO: test this - private static JsonLoaderValue version(VersionRange range) { - if (range.equals(VersionRange.ANY)) { - return string("*"); - } + private static JsonLoaderValue.ArrayImpl array(List values) { + return new JsonLoaderValue.ArrayImpl("", values); + } - return array(range.convertToConstraints().stream().map(VersionConstraint::toString).map(InternalModMetadataJsonWriter::string)); + private static JsonLoaderValue.ArrayImpl array(Stream values) { + return array(values.collect(Collectors.toList())); + } + + private static JsonLoaderValue.ObjectImpl object(Map values) { + return new JsonLoaderValue.ObjectImpl("", values); + } + + private static JsonLoaderValue.ObjectImpl object(Consumer> consumer) { + Map values = new LinkedHashMap<>(); + consumer.accept(values); + return object(values); } } diff --git a/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java index d5afa90fe..117cfa80b 100644 --- a/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java +++ b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java @@ -1,33 +1,62 @@ package org.quiltmc.loader.impl.metadata.qmj; -import static org.junit.jupiter.api.Assertions.*; - +import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.StringWriter; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; import org.quiltmc.loader.impl.fabric.metadata.FabricModMetadataReader; import org.quiltmc.loader.impl.fabric.metadata.ParseMetadataException; -import org.quiltmc.loader.impl.metadata.FabricLoaderModMetadata; class InternalModMetadataJsonWriterTest { - @Test - void toJson() throws IOException, ParseMetadataException { - FabricLoaderModMetadata read = FabricModMetadataReader.parseMetadata(new File(System.getProperty("user.dir")).toPath() - .resolve("src") - .resolve("test") - .resolve("resources") - .resolve("testing") - .resolve("parsing") - .resolve("fabric") - .resolve("spec") - .resolve("long.json")); + static Stream fabricJsons() throws IOException { + Path specJsons = new File(System.getProperty("user.dir")).toPath() + .resolve("src/test/resources/testing/parsing/fabric/spec"); + + return StreamSupport.stream(Files.newDirectoryStream(specJsons).spliterator(), false); + } + + @ParameterizedTest() + @MethodSource("org.quiltmc.loader.impl.metadata.qmj.InternalModMetadataJsonWriterTest#fabricJsons") + void fabricToJson(Path path) throws IOException, ParseMetadataException { + InternalModMetadata read = (InternalModMetadata) FabricModMetadataReader.parseMetadata(path).asQuiltModMetadata(); + + StringWriter writer = new StringWriter(); + InternalModMetadataJsonWriter.write(read, writer); + String output = writer.toString(); + System.out.println(output); + + JsonLoaderValue.ObjectImpl json = (JsonLoaderValue.ObjectImpl) JsonLoaderFactoryImpl.INSTANCE.read(new ByteArrayInputStream(output.getBytes())); + V1ModMetadataImpl readQuilt = V1ModMetadataReader.read(json); + } + + static Stream quiltJsons() throws IOException { + Path specJsons = new File(System.getProperty("user.dir")).toPath() + .resolve("src/test/resources/testing/parsing/quilt/v1/auto/spec"); + return StreamSupport.stream(Files.newDirectoryStream(specJsons).spliterator(), false); + } + + @ParameterizedTest() + @MethodSource("org.quiltmc.loader.impl.metadata.qmj.InternalModMetadataJsonWriterTest#quiltJsons") + void quiltToJson(Path path) throws IOException { + JsonLoaderValue.ObjectImpl json = (JsonLoaderValue.ObjectImpl) JsonLoaderFactoryImpl.INSTANCE.read(Files.newInputStream(path)); + InternalModMetadata read = V1ModMetadataReader.read(json).asQuiltModMetadata(); StringWriter writer = new StringWriter(); - InternalModMetadataJsonWriter.write(((InternalModMetadata) read.asQuiltModMetadata()), writer); - System.out.println(writer); + InternalModMetadataJsonWriter.write(read, writer); + String output = writer.toString(); + System.out.println(output); + + json = (JsonLoaderValue.ObjectImpl) JsonLoaderFactoryImpl.INSTANCE.read(new ByteArrayInputStream(output.getBytes())); + V1ModMetadataImpl readQuilt = V1ModMetadataReader.read(json); } } \ No newline at end of file From 15f37bebf547a7606535b68a892e829bec95ff69 Mon Sep 17 00:00:00 2001 From: Eli Orona Date: Fri, 9 Aug 2024 23:19:40 -0700 Subject: [PATCH 3/4] Fix licenses --- .../qmj/InternalModMetadataJsonWriterTest.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java index 117cfa80b..9b95768f6 100644 --- a/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java +++ b/src/test/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriterTest.java @@ -1,3 +1,19 @@ +/* + * Copyright 2024 QuiltMC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package org.quiltmc.loader.impl.metadata.qmj; import java.io.ByteArrayInputStream; @@ -9,7 +25,6 @@ import java.util.stream.Stream; import java.util.stream.StreamSupport; -import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; import org.quiltmc.loader.impl.fabric.metadata.FabricModMetadataReader; From 28588989933848193fe89e6994c5f9cf350ff2e0 Mon Sep 17 00:00:00 2001 From: Eli Orona Date: Sat, 10 Aug 2024 21:15:20 -0700 Subject: [PATCH 4/4] Make some improvements --- .../fabric/metadata/V1ModMetadataFabric.java | 14 +- .../loader/impl/metadata/qmj/Icons.java | 4 +- .../qmj/InternalModMetadataJsonWriter.java | 142 ++++++++---------- .../metadata/qmj/JsonLoaderFactoryImpl.java | 5 +- .../impl/metadata/qmj/V1ModMetadataImpl.java | 2 +- 5 files changed, 80 insertions(+), 87 deletions(-) diff --git a/src/main/java/org/quiltmc/loader/impl/fabric/metadata/V1ModMetadataFabric.java b/src/main/java/org/quiltmc/loader/impl/fabric/metadata/V1ModMetadataFabric.java index 170fc4d7b..1fc94e39a 100644 --- a/src/main/java/org/quiltmc/loader/impl/fabric/metadata/V1ModMetadataFabric.java +++ b/src/main/java/org/quiltmc/loader/impl/fabric/metadata/V1ModMetadataFabric.java @@ -45,7 +45,7 @@ import org.quiltmc.loader.impl.util.log.LogCategory; @QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED) -final class V1ModMetadataFabric extends AbstractModMetadata implements FabricLoaderModMetadata { +public final class V1ModMetadataFabric extends AbstractModMetadata implements FabricLoaderModMetadata { static final IconEntry NO_ICON = size -> Optional.empty(); // Required values @@ -76,7 +76,7 @@ final class V1ModMetadataFabric extends AbstractModMetadata implements FabricLoa private final Collection contributors; private final ContactInformation contact; private final Collection license; - private final IconEntry icon; + public final IconEntry icon; // Optional (language adapter providers) private final Map languageAdapters; @@ -346,12 +346,12 @@ static final class MixinEntry { } } - interface IconEntry { + public interface IconEntry { Optional getIconPath(int size); } - static final class Single implements IconEntry { - private final String icon; + public static final class Single implements IconEntry { + public final String icon; Single(String icon) { this.icon = icon; @@ -363,8 +363,8 @@ public Optional getIconPath(int size) { } } - static final class MapEntry implements IconEntry { - private final SortedMap icons; + public static final class MapEntry implements IconEntry { + public final SortedMap icons; MapEntry(SortedMap icons) { this.icons = icons; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/Icons.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/Icons.java index d21d6de72..2801cd941 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/Icons.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/Icons.java @@ -39,7 +39,7 @@ public interface Icons { @QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED) public final class Single implements Icons { @Nullable - private final String icon; + final String icon; public Single(@Nullable String icon) { this.icon = icon; @@ -57,7 +57,7 @@ public String getIcon(int size) { */ @QuiltLoaderInternal(QuiltLoaderInternalType.LEGACY_EXPOSED) public final class Multiple implements Icons { - private final SortedMap icons; + final SortedMap icons; public Multiple(SortedMap icons) { this.icons = icons; diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java index e313f3f19..3231da0d0 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/InternalModMetadataJsonWriter.java @@ -18,7 +18,6 @@ import java.io.IOException; import java.io.Writer; -import java.lang.reflect.Field; import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; @@ -28,7 +27,6 @@ import java.util.Map; import java.util.Set; import java.util.SortedMap; -import java.util.TreeMap; import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -41,8 +39,10 @@ import org.quiltmc.loader.api.ModLicense; import org.quiltmc.loader.api.ModMetadata; import org.quiltmc.loader.api.VersionConstraint; +import org.quiltmc.loader.api.VersionInterval; import org.quiltmc.loader.api.VersionRange; import org.quiltmc.loader.api.plugin.ModMetadataExt; +import org.quiltmc.loader.impl.fabric.metadata.V1ModMetadataFabric; import org.quiltmc.loader.impl.metadata.FabricLoaderModMetadata; import org.quiltmc.loader.impl.util.QuiltLoaderInternal; import org.quiltmc.loader.impl.util.QuiltLoaderInternalType; @@ -52,8 +52,6 @@ @QuiltLoaderInternal(QuiltLoaderInternalType.NEW_INTERNAL) public class InternalModMetadataJsonWriter { - private static final List SPECIFIC_KEY_ORDER = Stream.of("schema_version", "quilt_loader", "mixin", "access_widener", "minecraft").collect(Collectors.toList()); - public static void write(InternalModMetadata mod, Writer writer) throws IOException { JsonWriter json = JsonWriter.json(writer); ((JsonLoaderValue) toJson(mod)).write(json); @@ -65,21 +63,7 @@ public static void write(InternalModMetadata mod, Path path) throws IOException } public static LoaderValue toJson(InternalModMetadata mod) { - Map values = new TreeMap<>((o1, o2) -> { - if (o1.equals(o2)) { - return 0; - } - - for (String key : SPECIFIC_KEY_ORDER) { - if (o1.equals(key)) { - return -1; - } else if (o2.equals(key)) { - return 1; - } - } - - return o1.compareTo(o2); - }); + Map values = new LinkedHashMap<>(); values.put("schema_version", new JsonLoaderValue.NumberImpl("", 1)); values.put("quilt_loader", createQuiltLoader(mod)); @@ -141,7 +125,7 @@ private static LoaderValue createQuiltLoader(InternalModMetadata mod) { }); } - private static JsonLoaderValue entrypoints(Collection entrypoints) { + private static LoaderValue entrypoints(Collection entrypoints) { return array(entrypoints.stream().map(entrypoint -> { if (entrypoint instanceof AdapterLoadableClassEntry) { AdapterLoadableClassEntry adapted = (AdapterLoadableClassEntry) entrypoint; @@ -159,10 +143,10 @@ private static JsonLoaderValue entrypoints(Collection { + JsonLoaderValue.ObjectImpl obj = ((JsonLoaderValue.ObjectImpl) object(value -> { value.put("id", string(only.id().toString())); if (only.optional()) { @@ -177,7 +161,7 @@ private static JsonLoaderValue modDependency(ModDependency dep) { if (only.unless() != null) { value.put("unless", modDependency(only.unless())); } - }); + })); if (obj.size() == 1) { return obj.get("id"); @@ -192,13 +176,40 @@ private static JsonLoaderValue modDependency(ModDependency dep) { throw new IllegalStateException("Unknown Mod Dependency type! " + dep.getClass().getName()); } - private static JsonLoaderValue version(VersionRange range) { - // TODO: test this - if (range.equals(VersionRange.ANY)) { - return string("*"); + private static LoaderValue version(VersionRange range) { + if (range.size() == 1) { + VersionInterval interval = range.iterator().next(); + if (interval.equals(VersionInterval.ALL)) { + return string("*"); + } + return writeInterval(interval); + } + + List sub = new ArrayList<>(); + for (VersionInterval interval : range) { + sub.add(writeInterval(interval)); + } + + Map map = new LinkedHashMap<>(); + map.put("any", array(sub)); + return object(map); + } + + private static LoaderValue writeInterval(VersionInterval interval) { + VersionRange createdRange = VersionRange.ofInterval(interval); + // Convert to constraints will be accurate if there's only a single interval + List out = new ArrayList<>(); + for (VersionConstraint constraint : createdRange.convertToConstraints()) { + out.add(string(constraint.toString())); } - return array(range.convertToConstraints().stream().map(VersionConstraint::toString).map(InternalModMetadataJsonWriter::string)); + if (out.size() == 1) { + return out.iterator().next(); + } else { + Map sub = new LinkedHashMap<>(); + sub.put("all", array(out)); + return object(sub); + } } private static

Function provided(InternalModMetadata mod) { @@ -219,7 +230,7 @@ private static

Function prov }; } - private static JsonLoaderValue createMetadata(InternalModMetadata mod) { + private static LoaderValue createMetadata(InternalModMetadata mod) { return object(metadata -> { metadata.put("name", string(mod.name())); if (!mod.description().isEmpty()) { @@ -251,7 +262,7 @@ private static JsonLoaderValue createMetadata(InternalModMetadata mod) { }); } - private static JsonLoaderValue createLicense(ModLicense license) { + private static LoaderValue createLicense(ModLicense license) { // Object equality here is fine because it comes from a map if (ModLicense.fromIdentifier(license.id()) == license) { return string(license.id()); @@ -266,49 +277,28 @@ private static JsonLoaderValue createLicense(ModLicense license) { } private static LoaderValue getIcon(InternalModMetadata mod) { - // Cursed time! if (mod instanceof V1ModMetadataImpl) { - try { - Field iconsField = mod.getClass().getDeclaredField("icons"); - iconsField.setAccessible(true); - Icons icons = (Icons) iconsField.get(mod); - if (icons instanceof Icons.Single) { - String icon = icons.getIcon(0); - if (icon == null) { - return null; - } - - return string(icon); - } else if (icons instanceof Icons.Multiple) { - Field mapField = Icons.Multiple.class.getDeclaredField("icons"); - mapField.setAccessible(true); - SortedMap map = (SortedMap) mapField.get(icons); - return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); + Icons icons = ((V1ModMetadataImpl) mod).icons; + if (icons instanceof Icons.Single) { + String icon = ((Icons.Single) icons).icon; + if (icon == null) { + return null; } - } catch (Exception e) { - throw new RuntimeException(e); + + return string(icon); + } else if (icons instanceof Icons.Multiple) { + SortedMap map = ((Icons.Multiple) icons).icons; + return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); } } else if (mod instanceof FabricModMetadataWrapper) { FabricLoaderModMetadata fabric = mod.asFabricModMetadata(); - if (fabric.getClass().getSimpleName().equals("V1ModMetadataFabric")) { - try { - // V1ModMetadataFabric.IconEntry - Field iconField = fabric.getClass().getDeclaredField("icon"); - iconField.setAccessible(true); - Object icons = iconField.get(fabric); - - if (icons.getClass().getName().contains("Single")) { - Field stringField = icons.getClass().getDeclaredField("icon"); - stringField.setAccessible(true); - return string((String) stringField.get(icons)); - } else if (icons.getClass().getName().contains("MapEntry")) { - Field iconsField = icons.getClass().getDeclaredField("icons"); - iconsField.setAccessible(true); - SortedMap map = (SortedMap) iconsField.get(icons); - return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); - } - } catch (Exception e) { - throw new RuntimeException(e); + if (fabric instanceof V1ModMetadataFabric) { + V1ModMetadataFabric.IconEntry icons = ((V1ModMetadataFabric) fabric).icon; + if (icons instanceof V1ModMetadataFabric.Single) { + return string(((V1ModMetadataFabric.Single) icons).icon); + } else if (icons instanceof V1ModMetadataFabric.MapEntry) { + SortedMap map = ((V1ModMetadataFabric.MapEntry) icons).icons; + return object(obj -> map.forEach((size, icon) -> obj.put(Integer.toString(size), string(icon)))); } } } @@ -357,7 +347,7 @@ private static LoaderValue createMixins(InternalModMetadata mod) { return mixin; } - private static JsonLoaderValue mixinObject(String config, String env) { + private static LoaderValue mixinObject(String config, String env) { return object(objectValues -> { objectValues.put("config", string(config)); objectValues.put("environment", string(env)); @@ -411,23 +401,23 @@ private static void putEmptyMap(String field, Collection map, Function values) { + private static LoaderValue array(List values) { return new JsonLoaderValue.ArrayImpl("", values); } - private static JsonLoaderValue.ArrayImpl array(Stream values) { + private static LoaderValue array(Stream values) { return array(values.collect(Collectors.toList())); } - private static JsonLoaderValue.ObjectImpl object(Map values) { - return new JsonLoaderValue.ObjectImpl("", values); + private static LoaderValue object(Map values) { + return JsonLoaderFactoryImpl.INSTANCE.object(values); } - private static JsonLoaderValue.ObjectImpl object(Consumer> consumer) { + private static LoaderValue object(Consumer> consumer) { Map values = new LinkedHashMap<>(); consumer.accept(values); return object(values); diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java index 677b8645c..d72aaba11 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/JsonLoaderFactoryImpl.java @@ -25,8 +25,11 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import org.quiltmc.json5.JsonReader; @@ -90,6 +93,6 @@ public LoaderValue.LArray array(LoaderValue[] values) { @Override public LoaderValue.LObject object(Map map) { - return new JsonLoaderValue.ObjectImpl(LOCATION, new HashMap<>(map)); + return new JsonLoaderValue.ObjectImpl(LOCATION, new LinkedHashMap<>(map)); } } diff --git a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java index 11f7fc0df..50314064a 100644 --- a/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java +++ b/src/main/java/org/quiltmc/loader/impl/metadata/qmj/V1ModMetadataImpl.java @@ -59,7 +59,7 @@ final class V1ModMetadataImpl implements InternalModMetadata { private final Collection depends; private final Collection breaks; private final String intermediateMappings; - private final Icons icons; + final Icons icons; /* Internal fields */ private final ModLoadType loadType; private final Collection provides;