aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLinnea Gräf <nea@nea.moe>2024-03-05 20:46:47 +0100
committerLinnea Gräf <nea@nea.moe>2024-03-05 20:46:47 +0100
commit4f704c99ab3f4a294aba9d1f2b0ae7929e2b4a4b (patch)
treea9fa3f57a7efeea0a937aac7fc90fb4df23e5a7b
parentb8c7e6c934ae9e0059ac1ac1455d77319dbc20c0 (diff)
downloadzwirn-4f704c99ab3f4a294aba9d1f2b0ae7929e2b4a4b.tar.gz
zwirn-4f704c99ab3f4a294aba9d1f2b0ae7929e2b4a4b.tar.bz2
zwirn-4f704c99ab3f4a294aba9d1f2b0ae7929e2b4a4b.zip
Add diffing
-rw-r--r--.gitignore2
-rw-r--r--out.tiny7
-rw-r--r--src/main/java/moe/nea/zwirn/TinyBaseMerger.java206
-rw-r--r--src/main/java/moe/nea/zwirn/TinyDiffer.java264
-rw-r--r--src/main/java/moe/nea/zwirn/Zwirn.java16
-rw-r--r--src/main/java/moe/nea/zwirn/ZwirnUtil.java2
-rw-r--r--src/test/java/moe/nea/zwirn/ZwirnTest.java59
7 files changed, 538 insertions, 18 deletions
diff --git a/.gitignore b/.gitignore
index b63da45..dccfa0e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,5 +38,7 @@ bin/
### VS Code ###
.vscode/
+/*.tiny
+
### Mac OS ###
.DS_Store \ No newline at end of file
diff --git a/out.tiny b/out.tiny
deleted file mode 100644
index 718ff9b..0000000
--- a/out.tiny
+++ /dev/null
@@ -1,7 +0,0 @@
-tiny 2 0 official intermediary named
-c A ClassA SomeClassButNamedBetter
- c some comment
- f a a field_a myFieldButNamedCool
- c Field comment
- c Better comment
-c B B OtherClass
diff --git a/src/main/java/moe/nea/zwirn/TinyBaseMerger.java b/src/main/java/moe/nea/zwirn/TinyBaseMerger.java
new file mode 100644
index 0000000..96ddf2a
--- /dev/null
+++ b/src/main/java/moe/nea/zwirn/TinyBaseMerger.java
@@ -0,0 +1,206 @@
+package moe.nea.zwirn;
+
+import net.fabricmc.stitch.commands.tinyv2.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public abstract class TinyBaseMerger {
+ protected final @NotNull TinyFile base;
+ protected final @NotNull TinyFile overlay;
+ protected final @NotNull String sharedNamespace;
+ protected final int baseSharedIndex;
+ protected final int overlaySharedIndex;
+ protected final @NotNull Map<@NotNull String, @NotNull TinyClass> overlayLUT;
+ protected final @NotNull Map<@NotNull String, @NotNull TinyClass> baseLUT;
+ protected final Integer[] baseToOverlayIndexMap;
+
+ protected TinyBaseMerger(TinyFile base, TinyFile overlay, String sharedNamespace) {
+ this.base = base;
+ this.overlay = overlay;
+ this.sharedNamespace = sharedNamespace;
+ this.baseSharedIndex = base.getHeader().getNamespaces().indexOf(sharedNamespace);
+ this.overlaySharedIndex = overlay.getHeader().getNamespaces().indexOf(sharedNamespace);
+ this.overlayLUT = overlay.getClassEntries().stream()
+ .collect(Collectors.toMap(it -> it.getClassNames().get(this.overlaySharedIndex), Function.identity()));
+ this.baseLUT = base.getClassEntries().stream()
+ .collect(Collectors.toMap(it -> it.getClassNames().get(this.baseSharedIndex), Function.identity()));
+ this.baseToOverlayIndexMap = base.getHeader().getNamespaces()
+ .stream().map(it -> overlay.getHeader().getNamespaces().indexOf(it))
+ .map(it -> it < 0 ? null : it)
+ .toArray(Integer[]::new);
+ }
+
+ public @NotNull TinyFile merge() {
+ var baseHeader = base.getHeader();
+ return new TinyFile(
+ new TinyHeader(baseHeader.getNamespaces(), baseHeader.getMajorVersion(), baseHeader.getMinorVersion(), baseHeader.getProperties()),
+ mergeClasses()
+ );
+ }
+
+
+ protected abstract @NotNull List<@NotNull String> mergeNames(
+ @NotNull List<@NotNull String> baseNames,
+ @NotNull List<@NotNull String> overlayNames);
+
+ protected <T> List<String> mergeNames(@Nullable T baseObject, @Nullable T overlayObject, @NotNull Function<@NotNull T, @NotNull List<@NotNull String>> nameExtractor) {
+ if (baseObject == null && overlayObject == null)
+ throw new IllegalArgumentException("Either baseObject or overlayObject must not be null");
+ if (overlayObject == null)
+ return nameExtractor.apply(baseObject);
+ if (baseObject == null) {
+ if (base.getHeader().getNamespaces().size() != overlay.getHeader().getNamespaces().size())
+ throw new IllegalStateException("Can only merge unnamed base objects if both files have the same namespaces");
+ List<String> names = new ArrayList<>();
+ for (int i = 0; i < base.getHeader().getNamespaces().size(); i++) {
+ names.add("");
+ }
+ List<@NotNull String> mergedNames = mergeNames(names, nameExtractor.apply(overlayObject));
+ for (var name : mergedNames) {
+ if (name.isEmpty())
+ throw new RuntimeException("Invariant violated: unnamed overlay name");
+ }
+ return mergedNames;
+ }
+ return mergeNames(nameExtractor.apply(baseObject), nameExtractor.apply(overlayObject));
+ }
+
+ protected abstract <T> @NotNull List<@NotNull String> mergeComments(@Nullable T baseObject, @Nullable T overlayObject, @NotNull Function<@NotNull T, ? extends @NotNull Collection<@NotNull String>> commentExtractor);
+
+ protected TinyClass mergeClass(@Nullable TinyClass baseClass, @Nullable TinyClass overlayClass) {
+ return new TinyClass(
+ mergeNames(baseClass, overlayClass, TinyClass::getClassNames),
+ mergeMethods(baseClass, overlayClass),
+ mergeFields(baseClass, overlayClass),
+ mergeComments(baseClass, overlayClass, TinyClass::getComments)
+ );
+ }
+
+ protected Collection<TinyField> mergeFields(@Nullable TinyClass baseClass, @Nullable TinyClass overlayClass) {
+ return mergeChildren(baseClass, overlayClass,
+ it -> it.getFieldNames().get(baseSharedIndex),
+ it -> it.getFieldNames().get(overlaySharedIndex),
+ TinyClass::getFields,
+ this::mergeField);
+ }
+
+ protected TinyField mergeField(@Nullable TinyField baseField, @Nullable TinyField overlayField) {
+ var mergedNames = mergeNames(baseField, overlayField, TinyField::getFieldNames);
+ return new TinyField(
+ mergedNames.get(0),
+ mergedNames,
+ mergeComments(baseField, overlayField, TinyField::getComments)
+ );
+ }
+
+ protected Collection<TinyMethod> mergeMethods(@Nullable TinyClass baseClass, @Nullable TinyClass overlayClass) {
+ return mergeChildren(baseClass, overlayClass,
+ it -> it.getMethodNames().get(baseSharedIndex),
+ it -> it.getMethodNames().get(overlaySharedIndex),
+ TinyClass::getMethods,
+ this::mergeMethod);
+ }
+
+ protected @NotNull TinyMethod mergeMethod(@Nullable TinyMethod baseMethod, @Nullable TinyMethod overlayMethod) {
+ var mergedNames = mergeNames(baseMethod, overlayMethod, TinyMethod::getMethodNames);
+ return new TinyMethod(
+ mergedNames.get(0),
+ mergedNames,
+ mergeChildren(baseMethod, overlayMethod,
+ TinyMethodParameter::getLvIndex,
+ TinyMethodParameter::getLvIndex,
+ TinyMethod::getParameters,
+ this::mergeMethodParameter),
+ mergeChildren(baseMethod, overlayMethod,
+ TinyLocalVariable::getLvIndex,
+ TinyLocalVariable::getLvIndex,
+ TinyMethod::getLocalVariables,
+ this::mergeLocalVariable),
+ mergeComments(baseMethod, overlayMethod, TinyMethod::getComments)
+ );
+ }
+
+ protected TinyLocalVariable mergeLocalVariable(
+ @Nullable TinyLocalVariable baseVariable,
+ @Nullable TinyLocalVariable overlayVariable) {
+ if (baseVariable == null && overlayVariable == null)
+ throw new NullPointerException();
+ if (overlayVariable != null && baseVariable != null) {
+ if (overlayVariable.getLvIndex() != baseVariable.getLvIndex())
+ throw new IllegalArgumentException("lvIndex must match on local variables with the same shared name");
+ if (overlayVariable.getLvStartOffset() != baseVariable.getLvStartOffset())
+ throw new IllegalArgumentException("lvStartOffset must match on local variables with the same shared name");
+ if (overlayVariable.getLvTableIndex() != baseVariable.getLvTableIndex())
+ throw new IllegalArgumentException("lvTableIndex must match on local variables with the same shared name");
+ }
+ return new TinyLocalVariable(
+ baseVariable == null ? overlayVariable.getLvIndex() : baseVariable.getLvIndex(),
+ baseVariable == null ? overlayVariable.getLvStartOffset() : baseVariable.getLvStartOffset(),
+ baseVariable == null ? overlayVariable.getLvTableIndex() : baseVariable.getLvTableIndex(),
+ mergeNames(baseVariable, overlayVariable, TinyLocalVariable::getLocalVariableNames),
+ mergeComments(baseVariable, overlayVariable, TinyLocalVariable::getComments)
+ );
+ }
+
+ protected TinyMethodParameter mergeMethodParameter(
+ @Nullable TinyMethodParameter baseParam,
+ @Nullable TinyMethodParameter overlayParam) {
+ if (baseParam == null && overlayParam == null)
+ throw new NullPointerException();
+ if (overlayParam != null && baseParam != null && overlayParam.getLvIndex() != baseParam.getLvIndex())
+ throw new IllegalArgumentException("lvIndex must match on method parameters with the same shared name");
+ return new TinyMethodParameter(
+ baseParam != null ? baseParam.getLvIndex() : overlayParam.getLvIndex(),
+ mergeNames(baseParam, overlayParam, TinyMethodParameter::getParameterNames),
+ mergeComments(baseParam, overlayParam, TinyMethodParameter::getComments)
+ );
+ }
+
+ protected <B, C, K> List<C> mergeChildren(
+ @Nullable B base,
+ @Nullable B overlay,
+ @NotNull Function<@NotNull C, @NotNull K> sharedBaseName,
+ @NotNull Function<@NotNull C, @NotNull K> sharedOverlayName,
+ @NotNull Function<@NotNull B, ? extends @NotNull Collection<@NotNull C>> childExtractor,
+ @NotNull BiFunction<@Nullable C, @Nullable C, @NotNull C> childMerger
+ ) {
+ Map<K, C> baseIndex =
+ base == null ? Collections.emptyMap() : childExtractor.apply(base).stream()
+ .collect(Collectors.toMap(sharedBaseName, Function.identity()));
+ Map<K, C> overlayIndex =
+ overlay == null ? Collections.emptyMap() : childExtractor.apply(overlay).stream()
+ .collect(Collectors.toMap(sharedOverlayName, Function.identity()));
+ var results = new ArrayList<C>();
+ if (base != null)
+ for (var baseObject : childExtractor.apply(base)) {
+ var sharedName = sharedBaseName.apply(baseObject);
+ var overlayObject = overlayIndex.get(sharedName);
+ var mergedClass = childMerger.apply(baseObject, overlayObject);
+ results.add(mergedClass);
+ }
+ if (overlay != null)
+ for (var overlayObject : childExtractor.apply(overlay)) {
+ var sharedName = sharedOverlayName.apply(overlayObject);
+ if (baseIndex.containsKey(sharedName))
+ continue;
+ results.add(overlayObject);
+ }
+ return results;
+ }
+
+ protected List<TinyClass> mergeClasses() {
+ return (mergeChildren(
+ base,
+ overlay,
+ it -> it.getClassNames().get(baseSharedIndex),
+ it -> it.getClassNames().get(overlaySharedIndex),
+ TinyFile::getClassEntries,
+ this::mergeClass
+ ));
+ }
+}
diff --git a/src/main/java/moe/nea/zwirn/TinyDiffer.java b/src/main/java/moe/nea/zwirn/TinyDiffer.java
new file mode 100644
index 0000000..f5fc471
--- /dev/null
+++ b/src/main/java/moe/nea/zwirn/TinyDiffer.java
@@ -0,0 +1,264 @@
+package moe.nea.zwirn;
+
+import net.fabricmc.stitch.commands.tinyv2.*;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.*;
+import java.util.function.BiFunction;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+public class TinyDiffer {
+ private final TinyFile base;
+ private final TinyFile overlay;
+ private final List<String> retainedNamespaces;
+ int[] retainedToNormalLookup;
+ protected final int baseSharedIndex;
+ protected final int overlaySharedIndex;
+
+ public TinyDiffer(@NotNull TinyFile base, @NotNull TinyFile overlay, @NotNull List<String> retainedNamespaces) {
+ this.base = base;
+ this.overlay = overlay;
+ this.retainedNamespaces = retainedNamespaces;
+ this.baseSharedIndex = base.getHeader().getNamespaces().indexOf(retainedNamespaces.get(0));// TODO: shared namespace argument
+ this.overlaySharedIndex = overlay.getHeader().getNamespaces().indexOf(retainedNamespaces.get(0));
+
+ retainedToNormalLookup = retainedNamespaces.stream()
+ .mapToInt(it -> base.getHeader().getNamespaces().indexOf(it))
+ .toArray();
+ }
+
+
+ public TinyFile createDiff() {
+ return new TinyFile(new TinyHeader(retainedNamespaces,
+ overlay.getHeader().getMajorVersion(),
+ overlay.getHeader().getMinorVersion(),
+ overlay.getHeader().getProperties()),
+ diffClasses());
+ }
+
+ private <B, C extends Mapping> Retained<List<C>> diffChildrenByMapping(
+ @Nullable B base,
+ @Nullable B overlay,
+ @NotNull Function<@NotNull B, ? extends @NotNull Collection<@NotNull C>> childExtractor,
+ @NotNull BiFunction<@Nullable C, @Nullable C, @NotNull Retained<@NotNull C>> childDiffer
+ ) {
+ return this.diffChildren(base, overlay,
+ it -> it.getMapping().get(baseSharedIndex),
+ it -> it.getMapping().get(overlaySharedIndex),
+ childExtractor,
+ childDiffer
+ );
+ }
+
+ private <B, C, K> Retained<List<C>> diffChildren(
+ @Nullable B base,
+ @Nullable B overlay,
+ @NotNull Function<@NotNull C, @NotNull K> sharedBaseName,
+ @NotNull Function<@NotNull C, @NotNull K> sharedOverlayName,
+ @NotNull Function<@NotNull B, ? extends @NotNull Collection<@NotNull C>> childExtractor,
+ @NotNull BiFunction<@Nullable C, @Nullable C, @NotNull Retained<@NotNull C>> childDiffer
+ ) {
+ Map<K, C> baseIndex =
+ base == null ? Collections.emptyMap() : childExtractor.apply(base).stream()
+ .collect(Collectors.toMap(sharedBaseName, Function.identity()));
+ Map<K, C> overlayIndex =
+ overlay == null ? Collections.emptyMap() : childExtractor.apply(overlay).stream()
+ .collect(Collectors.toMap(sharedOverlayName, Function.identity()));
+ var results = new ArrayList<@NotNull C>();
+ if (base != null)
+ for (var baseObject : childExtractor.apply(base)) {
+ var sharedName = sharedBaseName.apply(baseObject);
+ var overlayObject = overlayIndex.get(sharedName);
+ var mergedClass = childDiffer.apply(baseObject, overlayObject);
+ if (mergedClass.isMeaningfullyDifferent)
+ results.add(mergedClass.diff);
+ }
+ if (overlay != null)
+ for (var overlayObject : childExtractor.apply(overlay)) {
+ var sharedName = sharedOverlayName.apply(overlayObject);
+ if (baseIndex.containsKey(sharedName))
+ continue;
+ results.add(overlayObject);
+ }
+ if (results.isEmpty())
+ return Retained.empty(results);
+ return Retained.strong(results);
+ }
+
+ private Collection<TinyClass> diffClasses() {
+ return diffChildrenByMapping(base, overlay,
+ TinyFile::getClassEntries,
+ this::diffClass).diff;
+ }
+
+ private List<String> retainNames(List<String> names) {
+ List<String> newNames = new ArrayList<>(retainedToNormalLookup.length);
+ for (int j : retainedToNormalLookup) {
+ newNames.add(names.get(j));
+ }
+ return newNames;
+ }
+
+
+ private <T extends Mapping> @NotNull Retained<List<String>> diffNamesWithMappings(@Nullable T base, @Nullable T overlay) {
+ return diffNames(mapNull(base, Mapping::getMapping), mapNull(overlay, Mapping::getMapping));
+ }
+
+ private @NotNull Retained<List<String>> diffNames(@Nullable List<String> base, @Nullable List<String> overlay) {
+ if (overlay == null) return Retained.empty(retainNames(base));
+ if (base == null)
+ return Retained.strong(retainNames(overlay));
+ var b = retainNames(base);
+ var o = retainNames(overlay);
+ if (o.equals(b)) {
+ // TODO: return only the shared name and censor others
+ return Retained.empty(o);
+ }
+ return Retained.strong(o);
+ }
+
+ static class Retained<T> {
+ private final T diff;
+ private final boolean isMeaningfullyDifferent;
+
+ public Retained(T diff, boolean isMeaningfullyDifferent) {
+ this.diff = diff;
+ this.isMeaningfullyDifferent = isMeaningfullyDifferent;
+ }
+
+ public static <T> Retained<T> empty(T base) {
+ return new Retained<>(base, false);
+ }
+
+ public static <T> Retained<T> strong(T base) {
+ return new Retained<>(base, true);
+ }
+
+ public static <T> Retained<T> keep(T base, Retained<?>... dependencies) {
+ return new Retained<>(base, shouldRetainAny(Arrays.asList(dependencies)));
+ }
+
+ public static boolean shouldRetainAny(Iterable<Retained<?>> list) {
+ for (Retained<?> retained : list) {
+ if (retained.isMeaningfullyDifferent)
+ return true;
+ }
+ return false;
+ }
+ }
+
+ private <T, K> @Nullable K mapNull(@Nullable T t, @NotNull Function<@NotNull T, @NotNull K> mapper) {
+ return t == null ? null : mapper.apply(t);
+ }
+
+ private Retained<TinyClass> diffClass(
+ @Nullable TinyClass baseClass,
+ @Nullable TinyClass overlayClass) {
+ var names = diffNamesWithMappings(baseClass, overlayClass);
+ var fields = diffChildrenByMapping(baseClass, overlayClass, TinyClass::getFields, this::diffField);
+ var methods = diffChildrenByMapping(baseClass, overlayClass, TinyClass::getMethods, this::diffMethod);
+ var comments = diffComments(baseClass, overlayClass, TinyClass::getComments);
+ return Retained.keep(
+ new TinyClass(
+ names.diff,
+ methods.diff,
+ fields.diff,
+ comments.diff
+ ),
+ names,
+ methods,
+ fields,
+ comments
+ );
+ }
+
+ private <T> Retained<List<String>> diffComments(T baseObject, T overlayObject, Function<T, ? extends Collection<String>> commentExtractor) {
+ if (baseObject == null)
+ return Retained.strong(new ArrayList<>(commentExtractor.apply(overlayObject)));
+ if (overlayObject == null)
+ return Retained.empty(new ArrayList<>(commentExtractor.apply(baseObject)));
+ var comments = new ArrayList<>(commentExtractor.apply(overlayObject));
+ comments.removeAll(commentExtractor.apply(baseObject));
+ if (comments.isEmpty())
+ return Retained.empty(comments);
+ return Retained.strong(comments);
+ }
+
+ private Retained<TinyMethod> diffMethod(@Nullable TinyMethod baseMethod, @Nullable TinyMethod overlayMethod) {
+ var names = diffNamesWithMappings(baseMethod, overlayMethod);
+ var params = diffChildren(baseMethod, overlayMethod, TinyMethodParameter::getLvIndex, TinyMethodParameter::getLvIndex, TinyMethod::getParameters, this::diffParam);
+ var variables = diffChildren(baseMethod, overlayMethod, TinyLocalVariable::getLvIndex, TinyLocalVariable::getLvIndex, TinyMethod::getLocalVariables, this::diffVariable);
+ var comments = diffComments(baseMethod, overlayMethod, TinyMethod::getComments);
+ return Retained.keep(
+ new TinyMethod(
+ names.diff.get(0),
+ names.diff,
+ params.diff,
+ variables.diff,
+ comments.diff
+ ),
+ names, params, variables, comments
+ );
+ }
+
+ private Retained<TinyLocalVariable> diffVariable(TinyLocalVariable baseVariable, TinyLocalVariable overlayVariable) {
+ if (overlayVariable == null && baseVariable == null)
+ throw new NullPointerException();
+ if (overlayVariable != null && baseVariable != null) {
+ if (overlayVariable.getLvIndex() != baseVariable.getLvIndex())
+ throw new IllegalArgumentException("lvIndex must match on local variables with the same shared name");
+ if (overlayVariable.getLvStartOffset() != baseVariable.getLvStartOffset())
+ throw new IllegalArgumentException("lvStartOffset must match on local variables with the same shared name");
+ if (overlayVariable.getLvTableIndex() != baseVariable.getLvTableIndex())
+ throw new IllegalArgumentException("lvTableIndex must match on local variables with the same shared name");
+ }
+ var names = diffNamesWithMappings(baseVariable, overlayVariable);
+ var comments = diffComments(baseVariable, overlayVariable, TinyLocalVariable::getComments);
+ return Retained.keep(
+ new TinyLocalVariable(
+ overlayVariable == null ? baseVariable.getLvIndex() : overlayVariable.getLvIndex(),
+ overlayVariable == null ? baseVariable.getLvStartOffset() : overlayVariable.getLvStartOffset(),
+ overlayVariable == null ? baseVariable.getLvTableIndex() : overlayVariable.getLvTableIndex(),
+ names.diff,
+ comments.diff
+ ),
+ names,
+ comments
+ );
+ }
+
+ private Retained<TinyMethodParameter> diffParam(TinyMethodParameter baseParam, TinyMethodParameter overlayParam) {
+ if (baseParam == null && overlayParam == null)
+ throw new NullPointerException();
+ if (overlayParam != null && baseParam != null && overlayParam.getLvIndex() != baseParam.getLvIndex())
+ throw new IllegalArgumentException("lvIndex must match on method parameters with the same shared name");
+
+ var names = diffNamesWithMappings(baseParam, overlayParam);
+ var comments = diffComments(baseParam, overlayParam, TinyMethodParameter::getComments);
+ return Retained.keep(
+ new TinyMethodParameter(
+ baseParam != null ? baseParam.getLvIndex() : overlayParam.getLvIndex(),
+ names.diff,
+ comments.diff
+ ),
+ names,
+ comments
+ );
+ }
+
+ private Retained<TinyField> diffField(TinyField baseField, TinyField overlayField) {
+ var names = diffNamesWithMappings(baseField, overlayField);
+ var comments = diffComments(baseField, overlayField, TinyField::getComments);
+ return Retained.keep(
+ new TinyField(
+ names.diff.get(0),
+ names.diff,
+ comments.diff
+ ),
+ names,
+ comments
+ );
+ }
+}
diff --git a/src/main/java/moe/nea/zwirn/Zwirn.java b/src/main/java/moe/nea/zwirn/Zwirn.java
index db616d7..746c993 100644
--- a/src/main/java/moe/nea/zwirn/Zwirn.java
+++ b/src/main/java/moe/nea/zwirn/Zwirn.java
@@ -3,6 +3,8 @@ package moe.nea.zwirn;
import net.fabricmc.stitch.commands.tinyv2.TinyFile;
import org.jetbrains.annotations.NotNull;
+import java.util.List;
+
public class Zwirn {
public static @NotNull TinyFile mergeTinyFile(
@NotNull TinyFile base, @NotNull TinyFile overlay,
@@ -14,4 +16,18 @@ public class Zwirn {
throw new IllegalArgumentException("When merging a tiny file, overlay must contain the shared namespace");
return new TinyMerger(base, overlay, sharedNamespace).merge();
}
+
+ public static @NotNull TinyFile createOverlayTinyFile(
+ @NotNull TinyFile base, @NotNull TinyFile overlay,
+ @NotNull List<@NotNull String> retainedNamespaces,
+ @NotNull String sharedNamespace
+ ) {
+ if (!base.getHeader().getNamespaces().equals(overlay.getHeader().getNamespaces()))
+ throw new IllegalArgumentException("Namespaces in input must be equal");
+ if (!base.getHeader().getNamespaces().containsAll(retainedNamespaces))
+ throw new IllegalArgumentException("Retained namespaces must be present in input files");
+ if (!retainedNamespaces.contains(sharedNamespace))
+ throw new IllegalArgumentException("Shared namespace must be retained");
+ return new TinyDiffer(base, overlay, retainedNamespaces).createDiff();
+ }
}
diff --git a/src/main/java/moe/nea/zwirn/ZwirnUtil.java b/src/main/java/moe/nea/zwirn/ZwirnUtil.java
index f17eae6..805155f 100644
--- a/src/main/java/moe/nea/zwirn/ZwirnUtil.java
+++ b/src/main/java/moe/nea/zwirn/ZwirnUtil.java
@@ -1,7 +1,7 @@
package moe.nea.zwirn;
public class ZwirnUtil {
- private static <T> T TODO(String message) {
+ public static <T> T TODO(String message) {
throw new RuntimeException("Not yet implemented: " + message);
}
}
diff --git a/src/test/java/moe/nea/zwirn/ZwirnTest.java b/src/test/java/moe/nea/zwirn/ZwirnTest.java
index 2f80964..b3ed4f5 100644
--- a/src/test/java/moe/nea/zwirn/ZwirnTest.java
+++ b/src/test/java/moe/nea/zwirn/ZwirnTest.java
@@ -11,8 +11,18 @@ import java.util.HashMap;
class ZwirnTest {
@Test
- void mergeTinyFile() throws IOException {
- var base = new TinyFile(
+ void diffTinyFile() throws Exception {
+ var base = getBaseFile();
+ var overlay = getOverlayFile();
+ var merged = Zwirn.mergeTinyFile(base, overlay, "official");
+ var unmerged = Zwirn.createOverlayTinyFile(base, merged, Arrays.asList("official", "named"), "official");
+ TinyV2Writer.write(overlay, Path.of("overlay.tiny"));
+ TinyV2Writer.write(unmerged, Path.of("unmerged.tiny"));
+ }
+
+
+ TinyFile getBaseFile() {
+ return new TinyFile(
new TinyHeader(
Arrays.asList("official", "intermediary", "named"),
2, 0, new HashMap<>()
@@ -20,32 +30,61 @@ class ZwirnTest {
Arrays.asList(
new TinyClass(
Arrays.asList("A", "ClassA", "SomeClass"),
- Arrays.asList(),
+ Arrays.asList(
+ new TinyMethod(
+ "a",
+ Arrays.asList("a", "method_a", "doSomething"),
+ Arrays.asList(
+ new TinyMethodParameter(
+ 0,
+ Arrays.asList("a", "param_a_a", "somethingToOperateOn"),
+ Arrays.asList()
+ )
+ ),
+ Arrays.asList(),
+ Arrays.asList("method comment")
+ )
+ ),
Arrays.asList(new TinyField("a",
Arrays.asList("a", "field_a", "myField"), Arrays.asList("Field comment"))),
Arrays.asList("some comment")
+ ),
+ new TinyClass(
+ Arrays.asList("C", "ClassC", "SomeOtherClass"),
+ Arrays.asList(),
+ Arrays.asList(),
+ Arrays.asList()
)
)
);
- var overlay = new TinyFile(
+ }
+
+ @Test
+ void mergeTinyFile() throws IOException {
+ var base = getBaseFile();
+ var overlay = getOverlayFile();
+ var merged = Zwirn.mergeTinyFile(base, overlay, "official");
+ TinyV2Writer.write(merged, Path.of("out.tiny"));
+ }
+
+ TinyFile getOverlayFile() {
+ return new TinyFile(
new TinyHeader(
- Arrays.asList("official", "intermediary", "named"),
+ Arrays.asList("official", "named"),
2, 0, new HashMap<>()
),
Arrays.asList(
new TinyClass(
- Arrays.asList("A", "A", "SomeClassButNamedBetter"),
+ Arrays.asList("A", "SomeClassButNamedBetter"),
Arrays.asList(),
Arrays.asList(new TinyField("a",
- Arrays.asList("a", "a", "myFieldButNamedCool"), Arrays.asList("Better comment"))),
+ Arrays.asList("a", "myFieldButNamedCool"), Arrays.asList("Better comment"))),
Arrays.asList()
),
new TinyClass(
- Arrays.asList("B", "B", "OtherClass")
+ Arrays.asList("B", "OtherClass")
)
)
);
- var merged = Zwirn.mergeTinyFile(base, overlay, "official");
- TinyV2Writer.write(merged, Path.of("out.tiny"));
}
} \ No newline at end of file