Create JsonCompare

This commit is contained in:
LLytho 2022-07-12 17:41:10 +02:00
parent f1f8a3121d
commit 004b84290a
5 changed files with 330 additions and 10 deletions

View file

@ -0,0 +1,65 @@
package com.almostreliable.unified.recipe;
import com.google.gson.JsonObject;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.Objects;
public class RawRecipe {
private final ResourceLocation id;
private final ResourceLocation type;
private final JsonObject originalRecipe;
private boolean isDuplicate = false;
@Nullable private JsonObject transformedRecipe;
public RawRecipe(ResourceLocation id, JsonObject originalRecipe) {
this.id = id;
this.originalRecipe = originalRecipe;
try {
this.type = ResourceLocation.tryParse(originalRecipe.get("type").getAsString());
} catch (Exception e) {
throw new IllegalArgumentException("Could not detect recipe type");
}
}
public ResourceLocation getId() {
return id;
}
public ResourceLocation getType() {
return type;
}
public JsonObject getOriginal() {
return originalRecipe;
}
public void markAsDuplicate() {
isDuplicate = true;
}
public boolean isDuplicate() {
return isDuplicate;
}
public void setTransformed(JsonObject transformedRecipe) {
Objects.requireNonNull(transformedRecipe);
if(isTransformed()) {
throw new IllegalStateException("Recipe already transformed");
}
this.transformedRecipe = transformedRecipe;
}
@Nullable
public JsonObject getTransformed() {
return transformedRecipe;
}
public boolean isTransformed() {
return transformedRecipe != null;
}
}

View file

@ -9,7 +9,11 @@ import com.google.gson.JsonPrimitive;
import net.minecraft.resources.ResourceLocation;
import javax.annotation.Nullable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class RecipeTransformer {
@ -29,23 +33,59 @@ public class RecipeTransformer {
}
public RecipeTransformationResult transformRecipes(Map<ResourceLocation, JsonElement> recipes) {
ConcurrentMap<ResourceLocation, List<RawRecipe>> rawRecipesByType = recipes
.entrySet()
.parallelStream()
.filter(entry -> entry.getValue().isJsonObject() && hasValidType(entry.getValue().getAsJsonObject()))
.map(entry -> new RawRecipe(entry.getKey(), entry.getValue().getAsJsonObject()))
.collect(Collectors.groupingByConcurrent(RawRecipe::getType));
RecipeTransformationResult recipeTransformationResult = new RecipeTransformationResult();
for (var entry : recipes.entrySet()) {
if (!hasValidType(entry.getValue().getAsJsonObject())) {
continue;
}
if (entry.getValue() instanceof JsonObject json) {
JsonObject result = transformRecipe(entry.getKey(), json);
recipeTransformationResult.track(entry.getKey(), json, result);
rawRecipesByType.forEach((type, rawRecipes) -> {
for (int curIndex = 0; curIndex < rawRecipes.size(); curIndex++) {
RawRecipe curRecipe = rawRecipes.get(curIndex);
JsonObject result = transformRecipe(curRecipe.getId(), curRecipe.getOriginal());
if (result != null) {
entry.setValue(result);
recipeTransformationResult.track(curRecipe.getId(), curRecipe.getOriginal(), result); // TODO remove
curRecipe.setTransformed(result);
for (int compareIndex = 0; compareIndex < curIndex; compareIndex++) { // TODO extract
RawRecipe toCompare = rawRecipes.get(compareIndex);
if(toCompare.isDuplicate()) continue;
JsonObject json = toCompare.isTransformed() ? toCompare.getTransformed() : toCompare.getOriginal();
if(result.equals(json)) { // TODO replace with cool equal which has additional compare rules
toCompare.markAsDuplicate();
}
}
}
}
}
});
Map<ResourceLocation, List<RawRecipe>> duplicates = new HashMap<>();
rawRecipesByType.forEach((type, rawRecipes) -> {
List<RawRecipe> duplicatesForType = rawRecipes.stream().filter(RawRecipe::isDuplicate).collect(Collectors.toList());
if(!duplicatesForType.isEmpty()) {
duplicates.put(type, duplicatesForType);
}
});
recipeTransformationResult.end();
// for (var entry : recipes.entrySet()) {
// if (!hasValidType(entry.getValue().getAsJsonObject())) {
// continue;
// }
//
// if (entry.getValue() instanceof JsonObject json) {
// JsonObject result = transformRecipe(entry.getKey(), json);
// recipeTransformationResult.track(entry.getKey(), json, result);
// if (result != null) {
// entry.setValue(result);
// }
// }
// }
return recipeTransformationResult;
}

View file

@ -0,0 +1,87 @@
package com.almostreliable.unified.utils;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
public class JsonCompare {
public static int compare(JsonObject first, JsonObject second, LinkedHashMap<String, Rule> rules) {
for (var entry : rules.entrySet()) {
JsonElement fElement = first.get(entry.getKey());
JsonElement sElement = second.get(entry.getKey());
if (fElement == null && sElement == null) {
continue;
}
int compareIndex = entry.getValue().compare(fElement, sElement);
if (compareIndex != 0) {
return compareIndex;
}
}
return 0;
}
public static JsonObject compare(LinkedHashMap<String, Rule> rules, JsonObject... jsonObjects) {
List<JsonObject> unsorted = Arrays.asList(jsonObjects);
unsorted.sort((f, s) -> compare(f, s, rules));
return unsorted.get(0);
}
public static boolean matches(JsonObject first, JsonObject second, Collection<String> ignoredProperties) {
List<String> firstValidKeys = first
.keySet()
.stream()
.filter(key -> !ignoredProperties.contains(key))
.toList();
List<String> secondValidKeys = second
.keySet()
.stream()
.filter(key -> !ignoredProperties.contains(key))
.toList();
if (firstValidKeys.size() != secondValidKeys.size()) return false;
for (String firstKey : firstValidKeys) {
if (!first.get(firstKey).equals(second.get(firstKey))) {
return false;
}
}
return true;
}
@FunctionalInterface
public interface Rule {
/**
* Compare two JsonElements. The caller must ensure that at least one element is not null.
*/
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
int compare(@Nullable JsonElement first, @Nullable JsonElement second);
}
public static class LowerRule implements Rule {
@Override
public int compare(@Nullable JsonElement first, @Nullable JsonElement second) {
double firstValue = first instanceof JsonPrimitive fp ? fp.getAsDouble() : 0;
double secondValue = second instanceof JsonPrimitive sp ? sp.getAsDouble() : 0;
return Double.compare(firstValue, secondValue);
}
}
public static class HigherRule implements Rule {
@Override
public int compare(@Nullable JsonElement first, @Nullable JsonElement second) {
double firstValue = first instanceof JsonPrimitive fp ? fp.getAsDouble() : 0;
double secondValue = second instanceof JsonPrimitive sp ? sp.getAsDouble() : 0;
return Double.compare(secondValue, firstValue);
}
}
}

View file

@ -5,6 +5,8 @@ import com.almostreliable.unified.recipe.handler.RecipeHandlerFactory;
import com.almostreliable.unified.utils.ReplacementMap;
import com.almostreliable.unified.utils.TagMapTests;
import com.almostreliable.unified.utils.UnifyTag;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
@ -71,4 +73,15 @@ public class TestUtils {
consumer.accept(factory);
return new RecipeTransformer(factory, map);
}
public static JsonObject json(String json) {
return new Gson().fromJson(json, JsonObject.class);
}
public static JsonObject json(String json, Consumer<JsonObject> consumer) {
Gson gson = new Gson();
JsonObject obj = gson.fromJson(json, JsonObject.class);
consumer.accept(obj);
return obj;
}
}

View file

@ -0,0 +1,115 @@
package com.almostreliable.unified.util;
import com.almostreliable.unified.TestUtils;
import com.almostreliable.unified.utils.JsonCompare;
import com.google.gson.JsonObject;
import org.junit.jupiter.api.Test;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class JsonCompareTest {
public static String recipe = """
{
"type": "minecraft:smelting",
"group": "coal",
"ingredient": {
"item": "minecraft:coal_ore"
},
"result": "minecraft:coal",
"experience": 0.1,
"cookingtime": 200
}
""";
@Test
public void simpleCompareFirst() {
JsonObject first = TestUtils.json(recipe, j -> j.addProperty("experience", 0.2));
JsonObject second = TestUtils.json(recipe); // 0.1 experience
LinkedHashMap<String, JsonCompare.Rule> rules = new LinkedHashMap<>();
rules.put("experience", new JsonCompare.LowerRule());
JsonObject result = JsonCompare.compare(rules, first, second);
assertEquals(second, result);
}
@Test
public void simpleCompareSecond() {
JsonObject first = TestUtils.json(recipe, j -> j.addProperty("experience", 0.05));
JsonObject second = TestUtils.json(recipe); // 0.1 experience
LinkedHashMap<String, JsonCompare.Rule> rules = new LinkedHashMap<>();
rules.put("experience", new JsonCompare.LowerRule());
JsonObject result = JsonCompare.compare(rules, first, second);
assertEquals(first, result);
}
@Test
public void compareHigherWins() {
JsonObject first = TestUtils.json(recipe, j -> j.addProperty("experience", 0.05));
JsonObject second = TestUtils.json(recipe); // 0.1 experience // 0.1 experience
LinkedHashMap<String, JsonCompare.Rule> rules = new LinkedHashMap<>();
rules.put("experience", new JsonCompare.HigherRule());
JsonObject result = JsonCompare.compare(rules, first, second);
assertEquals(second, result);
}
@Test
public void compareMulti() {
JsonObject a = TestUtils.json(recipe, j -> {
j.addProperty("experience", 0.1);
j.addProperty("cookingtime", 100);
});
JsonObject b = TestUtils.json(recipe, j -> j.addProperty("experience", 0.1));
JsonObject c = TestUtils.json(recipe, j -> {
j.addProperty("experience", 0.1);
j.addProperty("cookingtime", 50);
});
JsonObject d = TestUtils.json(recipe, j -> j.addProperty("experience", 0.2));
JsonObject e = TestUtils.json(recipe, j -> j.addProperty("experience", 0.2));
JsonObject f = TestUtils.json(recipe, j -> j.addProperty("experience", 0.1));
JsonObject g = TestUtils.json(recipe, j -> {
j.addProperty("experience", 0.2);
j.addProperty("cookingtime", 100);
});
LinkedHashMap<String, JsonCompare.Rule> rules = new LinkedHashMap<>();
rules.put("experience", new JsonCompare.HigherRule());
rules.put("cookingtime", new JsonCompare.LowerRule());
List<JsonObject> list = Arrays.asList(a, b, c, d, e, f, g);
list.sort((first, second) -> JsonCompare.compare(first, second, rules));
List<JsonObject> results = Arrays.asList(g, d, e, c, a, b, f);
for (int i = 0; i < list.size(); i++) {
assertEquals(results.get(i), list.get(i), "Failed at index " + i);
}
}
@Test
public void simpleMatch() {
JsonObject first = TestUtils.json(recipe);
JsonObject second = TestUtils.json(recipe);
boolean matches = JsonCompare.matches(first, second, List.of());
assertTrue(matches);
}
@Test
public void noMatch() {
JsonObject first = TestUtils.json(recipe, j -> j.addProperty("experience", 100));
JsonObject second = TestUtils.json(recipe);
boolean matches = JsonCompare.matches(first, second, List.of());
assertFalse(matches);
}
@Test
public void matchBecauseIgnore() {
JsonObject first = TestUtils.json(recipe, j -> j.addProperty("experience", 100));
JsonObject second = TestUtils.json(recipe);
boolean matches = JsonCompare.matches(first, second, List.of("experience"));
assertTrue(matches);
}
}