diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9870a97b..88702c55 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
- Added a simplified `MappingNsCompleter` constructor for completing all destination names with the source names
- Added `MappingTree#propagateOuterClassNames` as a more efficient tree-API alternative to `OuterClassNamePropagator`
+- Added `MappingCollection` interfaces as modifiable views of a tree's internal mapping store
+- Added `getKind` method to `ElementMappingView`
+- Made `MappingTree`'s `add` methods accept read-only mapping views
- Made `OuterClassNamePropagator` configurable
- Made Enigma writer always output destination names if visited explicitly, establishing consistency across all writers
- Adjusted format detection to only return ENIGMA_DIR for non-empty directories with at least one `.mapping` file
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingCollection.java b/src/main/java/net/fabricmc/mappingio/tree/MappingCollection.java
new file mode 100644
index 00000000..e154a5cd
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingCollection.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2025 FabricMC
+ *
+ * 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 net.fabricmc.mappingio.tree;
+
+import java.util.Collection;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import net.fabricmc.mappingio.tree.MappingTreeView.ClassMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.ElementMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.FieldMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodArgMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodVarMappingView;
+
+/**
+ * A {@link Collection}-based modifiable view of element mappings present in a mapping tree.
+ *
+ *
Contrary to what's defined in {@link Collection}'s Javadocs, the {@code add}
+ * methods here do not guarantee adding a passed element into the collection,
+ * instead its data may be merged into a compatible existing entry in case and such that
+ * {@link #containsCompatible(Object)} returns {@code true} for the passed element.
+ *
+ *
The following methods also have alternative versions that operate on
+ * compatible elements rather than equal ones:
+ *
+ * - {@link Collection#contains(Object)},
+ *
- {@link Collection#containsAll(Collection)},
+ *
- {@link Collection#remove(Object)},
+ *
- {@link Collection#removeAll(Collection)} and
+ *
- {@link Collection#retainAll(Collection)}.
+ *
+ * Compatibility is determined via the backing mapping tree's element mapping getters.
+ *
+ * Additionally, the {@link Collection#add(Object)} and {@link Collection#addAll(Collection)}
+ * methods have overloaded variants that accept read-only views of the held element mapping type,
+ * which are converted to the tree's internal representation if necessary and then added to the tree.
+ *
+ * @param The stored Elements' type.
+ * @param The View type correlating to the stored mapping type.
+ */
+@ApiStatus.NonExtendable
+public interface MappingCollection extends MappingCollectionView {
+ boolean add(V e);
+ boolean addAllViews(Collection extends V> c);
+ boolean removeCompatible(V e);
+ boolean removeAllCompatible(Collection extends V> c);
+ boolean retainAllCompatible(Collection extends V> c);
+ MappingCollectionView toUnmodifiableView();
+
+ interface ClassMappingCollection extends ClassMappingCollectionView {
+ @Override
+ ClassMappingCollectionView toUnmodifiableView();
+ }
+
+ interface FieldMappingCollection extends FieldMappingCollectionView {
+ @Override
+ FieldMappingCollectionView toUnmodifiableView();
+ }
+
+ interface MethodMappingCollection extends MethodMappingCollectionView {
+ @Override
+ MethodMappingCollectionView toUnmodifiableView();
+ }
+
+ interface MethodArgMappingCollection extends MethodArgMappingCollectionView {
+ @Override
+ MethodArgMappingCollectionView toUnmodifiableView();
+ }
+
+ interface MethodVarMappingCollection extends MethodVarMappingCollectionView {
+ @Override
+ MethodVarMappingCollectionView toUnmodifiableView();
+ }
+}
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionImpl.java b/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionImpl.java
new file mode 100644
index 00000000..93af3ee4
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionImpl.java
@@ -0,0 +1,485 @@
+/*
+ * Copyright (c) 2025 FabricMC
+ *
+ * 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 net.fabricmc.mappingio.tree;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+import org.jetbrains.annotations.Nullable;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
+import net.fabricmc.mappingio.tree.MappingTree.ElementMapping;
+import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
+import net.fabricmc.mappingio.tree.MappingTreeView.ClassMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.ElementMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.FieldMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodArgMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodVarMappingView;
+
+/**
+ * @param The stored Elements' type.
+ * @param The View type correlating to the stored mapping type.
+ * @param The stored elements' Owner type, or any if this is a root collection.
+ */
+abstract class MappingCollectionImpl implements MappingCollection {
+ private MappingCollectionImpl(MemoryMappingTree tree, @Nullable O owner, Collection backing, MappedElementKind elementKind, boolean readOnly) {
+ this.tree = tree;
+ this.owner = owner;
+ this.elementKind = elementKind;
+ this.readOnly = readOnly;
+
+ if (readOnly) {
+ this.backing = Collections.unmodifiableCollection(backing);
+ } else {
+ this.backing = backing;
+ }
+ }
+
+ @Override
+ public int size() {
+ return backing.size();
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return backing.isEmpty();
+ }
+
+ @Nullable
+ protected abstract E getCompatible(V o);
+
+ @Override
+ public boolean contains(Object o) {
+ if (o instanceof ElementMappingView) {
+ ElementMappingView oElem = (ElementMappingView) o;
+
+ if (oElem.getKind() == elementKind) {
+ return backing.contains(o);
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean containsCompatible(V o) {
+ return getCompatible(o) != null;
+ }
+
+ @Override
+ public boolean containsAll(Collection> c) {
+ for (Object o : c) {
+ if (!contains(o)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public boolean containsAllCompatible(Collection extends V> c) {
+ for (V o : c) {
+ if (!containsCompatible(o)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public Iterator iterator() {
+ Collection backing = readOnly
+ ? this.backing
+ : new ArrayList<>(this.backing);
+ return new IteratorWrapper(backing.iterator(), this);
+ }
+
+ @Override
+ public Object[] toArray() {
+ return backing.toArray();
+ }
+
+ @Override
+ public T[] toArray(T[] a) {
+ return backing.toArray(a);
+ }
+
+ // Mutating methods
+
+ @Override
+ public boolean add(V e) {
+ assertModifiable();
+ addInternal(e);
+ return true;
+ }
+
+ public abstract void addInternal(V e);
+
+ @Override
+ public boolean addAllViews(Collection extends V> c) {
+ assertModifiable();
+ boolean addedAny = false;
+
+ for (V e : c) {
+ addedAny |= add(e);
+ }
+
+ return addedAny;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean addAll(Collection extends E> c) {
+ return addAllViews((Collection extends V>) c);
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ assertModifiable();
+ return backing.remove(o);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public boolean removeCompatible(V o) {
+ assertModifiable();
+
+ if (o instanceof ElementMappingView) {
+ ElementMappingView oElem = (ElementMappingView) o;
+
+ if (oElem.getKind() == elementKind) {
+ return removeCompatibleInternal((V) oElem);
+ }
+ }
+
+ return false;
+ }
+
+ protected abstract boolean removeCompatibleInternal(V e);
+
+ @Override
+ public boolean removeAll(Collection> c) {
+ assertModifiable();
+ boolean removedAny = false;
+
+ for (Object o : c) {
+ removedAny |= remove(o);
+ }
+
+ return removedAny;
+ }
+
+ @Override
+ public boolean removeAllCompatible(Collection extends V> c) {
+ assertModifiable();
+ boolean removedAny = false;
+
+ for (V o : c) {
+ removedAny |= removeCompatible(o);
+ }
+
+ return removedAny;
+ }
+
+ @Override
+ public boolean retainAll(Collection> c) {
+ assertModifiable();
+ boolean removedAny = false;
+
+ for (Iterator it = iterator(); it.hasNext();) {
+ E e = it.next();
+
+ if (!c.contains(e)) {
+ it.remove();
+ removedAny = true;
+ }
+ }
+
+ return removedAny;
+ }
+
+ @Override
+ public boolean retainAllCompatible(Collection extends V> c) {
+ assertModifiable();
+ Set toRemove = new HashSet<>(this);
+ boolean removedAny = false;
+
+ for (V o : c) {
+ E e = getCompatible(o);
+
+ if (e != null) {
+ toRemove.remove(e);
+
+ if (toRemove.isEmpty()) {
+ break;
+ }
+ }
+ }
+
+ for (E e : toRemove) {
+ assert remove(e);
+ removedAny = true;
+ }
+
+ return removedAny;
+ }
+
+ @Override
+ public void clear() {
+ assertModifiable();
+
+ for (Iterator it = iterator(); it.hasNext();) {
+ it.next();
+ it.remove();
+ }
+ }
+
+ protected void assertModifiable() {
+ if (readOnly) {
+ throw new UnsupportedOperationException("Attempted modification of read-only collection");
+ }
+
+ tree.assertNotInVisitPass();
+ }
+
+ private final class IteratorWrapper implements Iterator {
+ IteratorWrapper(Iterator backing, MappingCollectionImpl owner) {
+ this.backing = backing;
+ this.owner = owner;
+ }
+
+ @Override
+ public boolean hasNext() {
+ return backing.hasNext();
+ }
+
+ @Override
+ public E next() {
+ lastReturned = backing.next();
+ return lastReturned;
+ }
+
+ @Override
+ public void remove() {
+ assertModifiable();
+ owner.remove(lastReturned);
+ }
+
+ private final Iterator backing;
+ private final MappingCollectionImpl owner;
+ private E lastReturned;
+ }
+
+ static class ClassMappingCollectionImpl extends MappingCollectionImpl implements ClassMappingCollection {
+ ClassMappingCollectionImpl(MemoryMappingTree tree, Collection backing) {
+ this(tree, backing, false);
+ }
+
+ private ClassMappingCollectionImpl(MemoryMappingTree tree, Collection backing, boolean readOnly) {
+ super(tree, null, backing, MappedElementKind.CLASS, readOnly);
+ }
+
+ @Override
+ public void addInternal(ClassMappingView e) {
+ tree.addClass(e);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected E getCompatible(ClassMappingView o) {
+ return (E) tree.getClass(o.getSrcName());
+ }
+
+ @Override
+ protected boolean removeCompatibleInternal(ClassMappingView o) {
+ return tree.removeClass(o.getSrcName()) != null;
+ }
+
+ @Override
+ public ClassMappingCollectionView toUnmodifiableView() {
+ if (view == null) {
+ view = new ClassMappingCollectionImpl<>(tree, backing, true);
+ }
+
+ return view;
+ }
+
+ protected ClassMappingCollectionView view;
+ }
+
+ static class FieldMappingCollectionImpl extends MappingCollectionImpl implements FieldMappingCollection {
+ FieldMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing) {
+ this(tree, owner, backing, false);
+ }
+
+ private FieldMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing, boolean readOnly) {
+ super(tree, owner, backing, MappedElementKind.FIELD, readOnly);
+ }
+
+ @Override
+ public void addInternal(FieldMappingView e) {
+ owner.addField(e);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected E getCompatible(FieldMappingView o) {
+ return (E) owner.getField(o.getSrcName(), o.getSrcDesc());
+ }
+
+ @Override
+ protected boolean removeCompatibleInternal(FieldMappingView o) {
+ return owner.removeField(o.getSrcName(), o.getSrcDesc()) != null;
+ }
+
+ @Override
+ public FieldMappingCollectionView toUnmodifiableView() {
+ if (view == null) {
+ view = new FieldMappingCollectionImpl<>(tree, owner, backing, true);
+ }
+
+ return view;
+ }
+
+ protected FieldMappingCollectionView view;
+ }
+
+ static class MethodMappingCollectionImpl extends MappingCollectionImpl implements MethodMappingCollection {
+ MethodMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing) {
+ this(tree, owner, backing, false);
+ }
+
+ private MethodMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing, boolean readOnly) {
+ super(tree, owner, backing, MappedElementKind.METHOD, readOnly);
+ }
+
+ @Override
+ public void addInternal(MethodMappingView e) {
+ owner.addMethod(e);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected E getCompatible(MethodMappingView o) {
+ return (E) owner.getMethod(o.getSrcName(), o.getSrcDesc());
+ }
+
+ @Override
+ protected boolean removeCompatibleInternal(MethodMappingView o) {
+ return owner.removeMethod(o.getSrcName(), o.getSrcDesc()) != null;
+ }
+
+ @Override
+ public MethodMappingCollectionView toUnmodifiableView() {
+ if (view == null) {
+ view = new MethodMappingCollectionImpl<>(tree, owner, backing, true);
+ }
+
+ return view;
+ }
+
+ protected MethodMappingCollectionView view;
+ }
+
+ static class MethodArgMappingCollectionImpl extends MappingCollectionImpl implements MethodArgMappingCollection {
+ MethodArgMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing) {
+ this(tree, owner, backing, false);
+ }
+
+ private MethodArgMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing, boolean readOnly) {
+ super(tree, owner, backing, MappedElementKind.METHOD_ARG, readOnly);
+ }
+
+ @Override
+ public void addInternal(MethodArgMappingView e) {
+ owner.addArg(e);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected E getCompatible(MethodArgMappingView o) {
+ return (E) owner.getArg(o.getArgPosition(), o.getLvIndex(), o.getSrcName());
+ }
+
+ @Override
+ protected boolean removeCompatibleInternal(MethodArgMappingView o) {
+ return owner.removeArg(o.getArgPosition(), o.getLvIndex(), o.getSrcName()) != null;
+ }
+
+ @Override
+ public MethodArgMappingCollectionView toUnmodifiableView() {
+ if (view == null) {
+ view = new MethodArgMappingCollectionImpl<>(tree, owner, backing, true);
+ }
+
+ return view;
+ }
+
+ protected MethodArgMappingCollectionView view;
+ }
+
+ static class MethodVarMappingCollectionImpl extends MappingCollectionImpl implements MethodVarMappingCollection {
+ MethodVarMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing) {
+ this(tree, owner, backing, false);
+ }
+
+ private MethodVarMappingCollectionImpl(MemoryMappingTree tree, O owner, Collection backing, boolean readOnly) {
+ super(tree, owner, backing, MappedElementKind.METHOD_VAR, readOnly);
+ }
+
+ @Override
+ public void addInternal(MethodVarMappingView e) {
+ owner.addVar(e);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected E getCompatible(MethodVarMappingView o) {
+ return (E) owner.getVar(o.getLvtRowIndex(), o.getLvIndex(), o.getStartOpIdx(), o.getEndOpIdx(), o.getSrcName());
+ }
+
+ @Override
+ protected boolean removeCompatibleInternal(MethodVarMappingView o) {
+ return owner.removeVar(o.getLvtRowIndex(), o.getLvIndex(), o.getStartOpIdx(), o.getEndOpIdx(), o.getSrcName()) != null;
+ }
+
+ @Override
+ public MethodVarMappingCollectionView toUnmodifiableView() {
+ if (view == null) {
+ view = new MethodVarMappingCollectionImpl<>(tree, owner, Collections.unmodifiableCollection(backing));
+ }
+
+ return view;
+ }
+
+ protected MethodVarMappingCollectionView view;
+ }
+
+ protected final MemoryMappingTree tree;
+ protected final @Nullable O owner;
+ protected final MappedElementKind elementKind;
+ protected final Collection backing;
+ protected final boolean readOnly;
+}
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionView.java b/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionView.java
new file mode 100644
index 00000000..e5bbbfbe
--- /dev/null
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingCollectionView.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025 FabricMC
+ *
+ * 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 net.fabricmc.mappingio.tree;
+
+import java.util.Collection;
+
+import org.jetbrains.annotations.ApiStatus;
+
+import net.fabricmc.mappingio.tree.MappingTreeView.ClassMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.ElementMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.FieldMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodArgMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodMappingView;
+import net.fabricmc.mappingio.tree.MappingTreeView.MethodVarMappingView;
+
+/**
+ * A {@link Collection}-based read-only view of element mappings present in a mapping tree.
+ *
+ * The meaning of "compatibility" as used in {@link #containsCompatible(Object)}
+ * and {@link #containsAllCompatible(Collection)} is determined by the backing
+ * mapping tree's element mapping getters.
+ *
+ * @param The stored Elements' type.
+ * @param The View type correlating to the stored mapping type.
+ */
+@ApiStatus.NonExtendable
+public interface MappingCollectionView extends Collection {
+ boolean containsCompatible(V o);
+ boolean containsAllCompatible(Collection extends V> c);
+
+ interface ClassMappingCollectionView extends MappingCollection { }
+
+ interface FieldMappingCollectionView extends MappingCollection { }
+
+ interface MethodMappingCollectionView extends MappingCollection { }
+
+ interface MethodArgMappingCollectionView extends MappingCollection { }
+
+ interface MethodVarMappingCollectionView extends MappingCollection { }
+}
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
index a24de62d..df25471d 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTree.java
@@ -25,11 +25,17 @@
import net.fabricmc.mappingio.adapter.MappingDstNsReorder;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
+import net.fabricmc.mappingio.tree.MappingCollection.ClassMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.FieldMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodArgMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodVarMappingCollection;
/**
* Mutable mapping tree.
*
* All returned collections are to be assumed unmodifiable, unless explicitly stated otherwise.
+ * {@linkplain MappingCollection}s are an exception.
*/
public interface MappingTree extends MappingTreeView {
/**
@@ -85,7 +91,7 @@ public interface MappingTree extends MappingTreeView {
boolean removeMetadata(String key);
@Override
- Collection extends ClassMapping> getClasses();
+ ClassMappingCollection extends ClassMapping> getClasses();
@Override
@Nullable
ClassMapping getClass(String srcName);
@@ -102,7 +108,7 @@ default ClassMapping getClass(String name, int namespace) {
* @return The {@link ClassMapping} instance present in the tree after the merge has occurred.
* May or may not be the passed instance.
*/
- ClassMapping addClass(ClassMapping cls);
+ ClassMapping addClass(ClassMappingView cls);
/**
* Removes a class mapping from the tree.
@@ -229,7 +235,7 @@ interface ElementMapping extends ElementMappingView {
interface ClassMapping extends ElementMapping, ClassMappingView {
@Override
- Collection extends FieldMapping> getFields();
+ FieldMappingCollection extends FieldMapping> getFields();
@Override
@Nullable
FieldMapping getField(String srcName, @Nullable String srcDesc);
@@ -246,7 +252,7 @@ default FieldMapping getField(String name, @Nullable String desc, int namespace)
* @return The {@link FieldMapping} instance present in the parent {@link ClassMapping} after the merge has occurred.
* May or may not be the passed instance.
*/
- FieldMapping addField(FieldMapping field);
+ FieldMapping addField(FieldMappingView field);
/**
* Removes a field mapping from the class.
@@ -257,7 +263,7 @@ default FieldMapping getField(String name, @Nullable String desc, int namespace)
FieldMapping removeField(String srcName, @Nullable String srcDesc);
@Override
- Collection extends MethodMapping> getMethods();
+ MethodMappingCollection extends MethodMapping> getMethods();
@Override
@Nullable
MethodMapping getMethod(String srcName, @Nullable String srcDesc);
@@ -274,7 +280,7 @@ default MethodMapping getMethod(String name, @Nullable String desc, int namespac
* @return The {@link MethodMapping} instance present in the parent {@link ClassMapping} after the merge has occurred.
* May or may not be the passed instance.
*/
- MethodMapping addMethod(MethodMapping method);
+ MethodMapping addMethod(MethodMappingView method);
/**
* Removes a method mapping from the class.
@@ -295,11 +301,18 @@ interface FieldMapping extends MemberMapping, FieldMappingView { }
interface MethodMapping extends MemberMapping, MethodMappingView {
@Override
- Collection extends MethodArgMapping> getArgs();
+ MethodArgMappingCollection extends MethodArgMapping> getArgs();
@Override
@Nullable
MethodArgMapping getArg(int argPosition, int lvIndex, @Nullable String srcName);
- MethodArgMapping addArg(MethodArgMapping arg);
+
+ /**
+ * Merges an argument mapping into the method.
+ *
+ * @return The {@link MethodArgMapping} instance present in the parent {@link MethodMapping} after the merge has occurred.
+ * May or may not be the passed instance.
+ */
+ MethodArgMapping addArg(MethodArgMappingView arg);
/**
* Removes an argument mapping from the method.
@@ -310,7 +323,7 @@ interface MethodMapping extends MemberMapping, MethodMappingView {
MethodArgMapping removeArg(int argPosition, int lvIndex, @Nullable String srcName);
@Override
- Collection extends MethodVarMapping> getVars();
+ MethodVarMappingCollection extends MethodVarMapping> getVars();
@Override
@Nullable
MethodVarMapping getVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName);
@@ -321,7 +334,7 @@ interface MethodMapping extends MemberMapping, MethodMappingView {
* @return The {@link MethodVarMapping} instance present in the parent {@link MethodMapping} after the merge has occurred.
* May or may not be the passed instance.
*/
- MethodVarMapping addVar(MethodVarMapping var);
+ MethodVarMapping addVar(MethodVarMappingView var);
/**
* Removes a variable mapping from the method.
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
index 3dacb7b1..961d5365 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MappingTreeView.java
@@ -17,12 +17,17 @@
package net.fabricmc.mappingio.tree;
import java.io.IOException;
-import java.util.Collection;
import java.util.List;
import org.jetbrains.annotations.Nullable;
+import net.fabricmc.mappingio.MappedElementKind;
import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.tree.MappingCollectionView.ClassMappingCollectionView;
+import net.fabricmc.mappingio.tree.MappingCollectionView.FieldMappingCollectionView;
+import net.fabricmc.mappingio.tree.MappingCollectionView.MethodArgMappingCollectionView;
+import net.fabricmc.mappingio.tree.MappingCollectionView.MethodMappingCollectionView;
+import net.fabricmc.mappingio.tree.MappingCollectionView.MethodVarMappingCollectionView;
/**
* Read-only mapping tree.
@@ -85,7 +90,7 @@ default String getNamespaceName(int id) {
*/
List extends MetadataEntryView> getMetadata(String key);
- Collection extends ClassMappingView> getClasses();
+ ClassMappingCollectionView extends ClassMappingView> getClasses();
@Nullable
ClassMappingView getClass(String srcName);
@Nullable
@@ -217,6 +222,7 @@ interface MetadataEntryView {
}
interface ElementMappingView {
+ MappedElementKind getKind();
MappingTreeView getTree();
String getSrcName();
@@ -248,7 +254,12 @@ default String getName(String namespace) {
}
interface ClassMappingView extends ElementMappingView {
- Collection extends FieldMappingView> getFields();
+ @Override
+ default MappedElementKind getKind() {
+ return MappedElementKind.CLASS;
+ }
+
+ FieldMappingCollectionView extends FieldMappingView> getFields();
/**
* @see MappingTreeView#getField(String, String, String, int)
@@ -274,7 +285,7 @@ default FieldMappingView getField(String name, @Nullable String desc, int namesp
return null;
}
- Collection extends MethodMappingView> getMethods();
+ MethodMappingCollectionView extends MethodMappingView> getMethods();
/**
* @see MappingTreeView#getMethod(String, String, String, int)
@@ -337,25 +348,45 @@ default String getDesc(String namespace) {
}
}
- interface FieldMappingView extends MemberMappingView { }
+ interface FieldMappingView extends MemberMappingView {
+ @Override
+ default MappedElementKind getKind() {
+ return MappedElementKind.FIELD;
+ }
+ }
interface MethodMappingView extends MemberMappingView {
- Collection extends MethodArgMappingView> getArgs();
+ @Override
+ default MappedElementKind getKind() {
+ return MappedElementKind.METHOD;
+ }
+
+ MethodArgMappingCollectionView extends MethodArgMappingView> getArgs();
@Nullable
MethodArgMappingView getArg(int argPosition, int lvIndex, @Nullable String srcName);
- Collection extends MethodVarMappingView> getVars();
+ MethodVarMappingCollectionView extends MethodVarMappingView> getVars();
@Nullable
MethodVarMappingView getVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName);
}
interface MethodArgMappingView extends ElementMappingView {
+ @Override
+ default MappedElementKind getKind() {
+ return MappedElementKind.METHOD_ARG;
+ }
+
MethodMappingView getMethod();
int getArgPosition();
int getLvIndex();
}
interface MethodVarMappingView extends ElementMappingView {
+ @Override
+ default MappedElementKind getKind() {
+ return MappedElementKind.METHOD_VAR;
+ }
+
MethodMappingView getMethod();
int getLvtRowIndex();
int getLvIndex();
diff --git a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
index 67f5c03a..5df1f997 100644
--- a/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
+++ b/src/main/java/net/fabricmc/mappingio/tree/MemoryMappingTree.java
@@ -40,6 +40,16 @@
import net.fabricmc.mappingio.MappingFlag;
import net.fabricmc.mappingio.MappingVisitor;
import net.fabricmc.mappingio.adapter.MappingSourceNsSwitch;
+import net.fabricmc.mappingio.tree.MappingCollection.ClassMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.FieldMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodArgMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollection.MethodVarMappingCollection;
+import net.fabricmc.mappingio.tree.MappingCollectionImpl.ClassMappingCollectionImpl;
+import net.fabricmc.mappingio.tree.MappingCollectionImpl.FieldMappingCollectionImpl;
+import net.fabricmc.mappingio.tree.MappingCollectionImpl.MethodArgMappingCollectionImpl;
+import net.fabricmc.mappingio.tree.MappingCollectionImpl.MethodMappingCollectionImpl;
+import net.fabricmc.mappingio.tree.MappingCollectionImpl.MethodVarMappingCollectionImpl;
/**
* {@link VisitableMappingTree} implementation that stores all data in memory.
@@ -284,7 +294,7 @@ public boolean removeMetadata(String key) {
}
@Override
- public Collection extends ClassMapping> getClasses() {
+ public ClassMappingCollection extends ClassMapping> getClasses() {
return classesView;
}
@@ -305,7 +315,7 @@ public ClassMapping getClass(String name, int namespace) {
}
@Override
- public ClassMapping addClass(ClassMapping cls) {
+ public ClassMapping addClass(ClassMappingView cls) {
assertNotInVisitPass();
ClassEntry entry = cls instanceof ClassEntry && cls.getTree() == this ? (ClassEntry) cls : new ClassEntry(this, cls, getSrcNsEquivalent(cls));
ClassEntry ret = classesBySrcName.putIfAbsent(cls.getSrcName(), entry);
@@ -325,7 +335,7 @@ public ClassMapping addClass(ClassMapping cls) {
return entry;
}
- private int getSrcNsEquivalent(ElementMapping mapping) {
+ private int getSrcNsEquivalent(ElementMappingView mapping) {
int ret = mapping.getTree().getNamespaceId(srcNamespace);
if (ret == NULL_NAMESPACE_ID) throw new UnsupportedOperationException("can't find source namespace in referenced mapping tree");
@@ -848,7 +858,7 @@ protected Entry(MemoryMappingTree tree, String srcName) {
this.dstNames = new String[tree.dstNamespaces.size()];
}
- protected Entry(MemoryMappingTree tree, ElementMapping src, int srcNsEquivalent) {
+ protected Entry(MemoryMappingTree tree, ElementMappingView src, int srcNsEquivalent) {
this(tree, src.getName(srcNsEquivalent));
for (int i = 0; i < dstNames.length; i++) {
@@ -862,7 +872,10 @@ protected Entry(MemoryMappingTree tree, ElementMapping src, int srcNsEquivalent)
setCommentInternal(src.getComment());
}
- public abstract MappedElementKind getKind();
+ @Override
+ public MemoryMappingTree getTree() {
+ return tree;
+ }
final boolean isSrcNameMissing() {
return srcName == null;
@@ -995,28 +1008,18 @@ static final class ClassEntry extends Entry implements ClassMapping
super(tree, srcName);
}
- ClassEntry(MemoryMappingTree tree, ClassMapping src, int srcNsEquivalent) {
+ ClassEntry(MemoryMappingTree tree, ClassMappingView src, int srcNsEquivalent) {
super(tree, src, srcNsEquivalent);
- for (FieldMapping field : src.getFields()) {
+ for (FieldMappingView field : src.getFields()) {
addFieldInternal(field);
}
- for (MethodMapping method : src.getMethods()) {
+ for (MethodMappingView method : src.getMethods()) {
addMethodInternal(method);
}
}
- @Override
- public MappedElementKind getKind() {
- return MappedElementKind.CLASS;
- }
-
- @Override
- public MemoryMappingTree getTree() {
- return tree;
- }
-
@Override
void setDstNameInternal(String name, int namespace) {
if (tree.indexByDstNames) {
@@ -1038,12 +1041,18 @@ void setDstNameInternal(String name, int namespace) {
}
@Override
- public Collection getFields() {
- if (fields == null) return Collections.emptyList();
-
+ public FieldMappingCollection getFields() {
+ initFieldCollectionsIfRequired();
return fieldsView;
}
+ private void initFieldCollectionsIfRequired() {
+ if (fields == null) {
+ fields = new LinkedHashMap<>();
+ fieldsView = new FieldMappingCollectionImpl<>(tree, this, fields.values());
+ }
+ }
+
@Override
@Nullable
public FieldEntry getField(String srcName, @Nullable String srcDesc) {
@@ -1057,18 +1066,15 @@ public FieldEntry getField(String name, @Nullable String desc, int namespace) {
}
@Override
- public FieldEntry addField(FieldMapping field) {
+ public FieldEntry addField(FieldMappingView field) {
tree.assertNotInVisitPass();
return addFieldInternal(field);
}
- FieldEntry addFieldInternal(FieldMapping field) {
+ FieldEntry addFieldInternal(FieldMappingView field) {
FieldEntry entry = field instanceof FieldEntry && field.getOwner() == this ? (FieldEntry) field : new FieldEntry(this, field, tree.getSrcNsEquivalent(field));
- if (fields == null) {
- fields = new LinkedHashMap<>();
- fieldsView = Collections.unmodifiableCollection(fields.values());
- }
+ initFieldCollectionsIfRequired();
return addMember(entry, fields, FLAG_HAS_ANY_FIELD_DESC, FLAG_MISSES_ANY_FIELD_DESC);
}
@@ -1085,12 +1091,18 @@ public FieldEntry removeField(String srcName, @Nullable String srcDesc) {
}
@Override
- public Collection getMethods() {
- if (methods == null) return Collections.emptyList();
-
+ public MethodMappingCollection getMethods() {
+ initMethodCollectionsIfRequired();
return methodsView;
}
+ private void initMethodCollectionsIfRequired() {
+ if (methods == null) {
+ methods = new LinkedHashMap<>();
+ methodsView = new MethodMappingCollectionImpl<>(tree, this, methods.values());
+ }
+ }
+
@Override
@Nullable
public MethodEntry getMethod(String srcName, @Nullable String srcDesc) {
@@ -1104,18 +1116,15 @@ public MethodEntry getMethod(String name, @Nullable String desc, int namespace)
}
@Override
- public MethodEntry addMethod(MethodMapping method) {
+ public MethodEntry addMethod(MethodMappingView method) {
tree.assertNotInVisitPass();
return addMethodInternal(method);
}
- MethodEntry addMethodInternal(MethodMapping method) {
+ MethodEntry addMethodInternal(MethodMappingView method) {
MethodEntry entry = method instanceof MethodEntry && method.getOwner() == this ? (MethodEntry) method : new MethodEntry(this, method, tree.getSrcNsEquivalent(method));
- if (methods == null) {
- methods = new LinkedHashMap<>();
- methodsView = Collections.unmodifiableCollection(methods.values());
- }
+ initMethodCollectionsIfRequired();
return addMember(entry, methods, FLAG_HAS_ANY_METHOD_DESC, FLAG_MISSES_ANY_METHOD_DESC);
}
@@ -1314,8 +1323,8 @@ public String toString() {
private Map fields = null;
private Map methods = null;
- private Collection fieldsView = null;
- private Collection methodsView = null;
+ private FieldMappingCollection fieldsView = null;
+ private MethodMappingCollection methodsView = null;
private byte flags;
}
@@ -1328,7 +1337,7 @@ protected MemberEntry(ClassEntry owner, String srcName, @Nullable String srcDesc
this.key = new MemberKey(srcName, srcDesc);
}
- protected MemberEntry(ClassEntry owner, MemberMapping src, int srcNsEquivalent) {
+ protected MemberEntry(ClassEntry owner, MemberMappingView src, int srcNsEquivalent) {
super(owner.tree, src, srcNsEquivalent);
this.owner = owner;
@@ -1336,11 +1345,6 @@ protected MemberEntry(ClassEntry owner, MemberMapping src, int srcNsEquivalent)
this.key = new MemberKey(getSrcName(), srcDesc);
}
- @Override
- public MappingTree getTree() {
- return owner.tree;
- }
-
@Override
public final ClassEntry getOwner() {
return owner;
@@ -1403,15 +1407,10 @@ static final class FieldEntry extends MemberEntry implements FieldMa
super(owner, srcName, srcDesc);
}
- FieldEntry(ClassEntry owner, FieldMapping src, int srcNsEquivalent) {
+ FieldEntry(ClassEntry owner, FieldMappingView src, int srcNsEquivalent) {
super(owner, src, srcNsEquivalent);
}
- @Override
- public MappedElementKind getKind() {
- return MappedElementKind.FIELD;
- }
-
@Override
public void setSrcDesc(@Nullable String desc) {
tree.assertNotInVisitPass();
@@ -1460,23 +1459,18 @@ static final class MethodEntry extends MemberEntry implements Metho
super(owner, srcName, srcDesc);
}
- MethodEntry(ClassEntry owner, MethodMapping src, int srcNsEquivalent) {
+ MethodEntry(ClassEntry owner, MethodMappingView src, int srcNsEquivalent) {
super(owner, src, srcNsEquivalent);
- for (MethodArgMapping arg : src.getArgs()) {
+ for (MethodArgMappingView arg : src.getArgs()) {
addArgInternal(arg);
}
- for (MethodVarMapping var : src.getVars()) {
+ for (MethodVarMappingView var : src.getVars()) {
addVarInternal(var);
}
}
- @Override
- public MappedElementKind getKind() {
- return MappedElementKind.METHOD;
- }
-
@Override
public void setSrcDesc(@Nullable String desc) {
tree.assertNotInVisitPass();
@@ -1509,12 +1503,18 @@ void setSrcDescInternal(@Nullable String desc) {
}
@Override
- public Collection getArgs() {
- if (args == null) return Collections.emptyList();
-
+ public MethodArgMappingCollection getArgs() {
+ initArgCollectionsIfRequired();
return argsView;
}
+ private void initArgCollectionsIfRequired() {
+ if (args == null) {
+ args = new ArrayList<>();
+ argsView = new MethodArgMappingCollectionImpl<>(tree, this, args);
+ }
+ }
+
@Override
@Nullable
public MethodArgEntry getArg(int argPosition, int lvIndex, @Nullable String srcName) {
@@ -1544,21 +1544,17 @@ public MethodArgEntry getArg(int argPosition, int lvIndex, @Nullable String srcN
}
@Override
- public MethodArgEntry addArg(MethodArgMapping arg) {
+ public MethodArgEntry addArg(MethodArgMappingView arg) {
tree.assertNotInVisitPass();
return addArgInternal(arg);
}
- MethodArgEntry addArgInternal(MethodArgMapping arg) {
+ MethodArgEntry addArgInternal(MethodArgMappingView arg) {
MethodArgEntry entry = arg instanceof MethodArgEntry && arg.getMethod() == this ? (MethodArgEntry) arg : new MethodArgEntry(this, arg, owner.tree.getSrcNsEquivalent(arg));
MethodArgEntry prev = getArg(arg.getArgPosition(), arg.getLvIndex(), arg.getSrcName());
if (prev == null) {
- if (args == null) {
- args = new ArrayList<>();
- argsView = Collections.unmodifiableList(args);
- }
-
+ initArgCollectionsIfRequired();
args.add(entry);
} else {
prev.copyFrom(entry, true);
@@ -1579,12 +1575,18 @@ public MethodArgEntry removeArg(int argPosition, int lvIndex, @Nullable String s
}
@Override
- public Collection getVars() {
- if (vars == null) return Collections.emptyList();
-
+ public MethodVarMappingCollection getVars() {
+ initVarCollectionsIfRequired();
return varsView;
}
+ private void initVarCollectionsIfRequired() {
+ if (vars == null) {
+ vars = new ArrayList<>();
+ varsView = new MethodVarMappingCollectionImpl<>(tree, this, vars);
+ }
+ }
+
@Override
@Nullable
public MethodVarEntry getVar(int lvtRowIndex, int lvIndex, int startOpIdx, int endOpIdx, @Nullable String srcName) {
@@ -1661,21 +1663,17 @@ public MethodVarEntry getVar(int lvtRowIndex, int lvIndex, int startOpIdx, int e
}
@Override
- public MethodVarEntry addVar(MethodVarMapping var) {
+ public MethodVarEntry addVar(MethodVarMappingView var) {
tree.assertNotInVisitPass();
return addVarInternal(var);
}
- MethodVarEntry addVarInternal(MethodVarMapping var) {
+ MethodVarEntry addVarInternal(MethodVarMappingView var) {
MethodVarEntry entry = var instanceof MethodVarEntry && var.getMethod() == this ? (MethodVarEntry) var : new MethodVarEntry(this, var, owner.tree.getSrcNsEquivalent(var));
MethodVarEntry prev = getVar(var.getLvtRowIndex(), var.getLvIndex(), var.getStartOpIdx(), var.getEndOpIdx(), var.getSrcName());
if (prev == null) {
- if (vars == null) {
- vars = new ArrayList<>();
- varsView = Collections.unmodifiableList(vars);
- }
-
+ initVarCollectionsIfRequired();
vars.add(entry);
} else {
prev.copyFrom(entry, true);
@@ -1755,8 +1753,8 @@ public String toString() {
private List args = null;
private List vars = null;
- private List argsView = null;
- private List varsView = null;
+ private MethodArgMappingCollection argsView = null;
+ private MethodVarMappingCollection varsView = null;
}
static final class MethodArgEntry extends Entry implements MethodArgMapping {
@@ -1768,7 +1766,7 @@ static final class MethodArgEntry extends Entry implements Metho
this.lvIndex = lvIndex;
}
- MethodArgEntry(MethodEntry method, MethodArgMapping src, int srcNsEquivalent) {
+ MethodArgEntry(MethodEntry method, MethodArgMappingView src, int srcNsEquivalent) {
super(method.owner.tree, src, srcNsEquivalent);
this.method = method;
@@ -1776,16 +1774,6 @@ static final class MethodArgEntry extends Entry implements Metho
this.lvIndex = src.getLvIndex();
}
- @Override
- public MappingTree getTree() {
- return method.owner.tree;
- }
-
- @Override
- public MappedElementKind getKind() {
- return MappedElementKind.METHOD_ARG;
- }
-
@Override
public MethodEntry getMethod() {
return method;
@@ -1865,7 +1853,7 @@ static final class MethodVarEntry extends Entry implements Metho
this.endOpIdx = endOpIdx;
}
- MethodVarEntry(MethodEntry method, MethodVarMapping src, int srcNs) {
+ MethodVarEntry(MethodEntry method, MethodVarMappingView src, int srcNs) {
super(method.owner.tree, src, srcNs);
this.method = method;
@@ -1875,16 +1863,6 @@ static final class MethodVarEntry extends Entry implements Metho
this.endOpIdx = src.getEndOpIdx();
}
- @Override
- public MappingTree getTree() {
- return method.owner.tree;
- }
-
- @Override
- public MappedElementKind getKind() {
- return MappedElementKind.METHOD_VAR;
- }
-
@Override
public MethodEntry getMethod() {
return method;
@@ -2104,7 +2082,7 @@ public String toString() {
private List dstNamespaces = Collections.emptyList();
private final List metadata = new ArrayList<>();
private final Map classesBySrcName = new LinkedHashMap<>();
- private final Collection classesView = Collections.unmodifiableCollection(classesBySrcName.values());
+ private final ClassMappingCollection classesView = new ClassMappingCollectionImpl<>(this, classesBySrcName.values());
private Map[] classesByDstNames;
private HierarchyInfoProvider> hierarchyInfo;
diff --git a/src/test/java/net/fabricmc/mappingio/test/tests/tree/MappingCollectionTest.java b/src/test/java/net/fabricmc/mappingio/test/tests/tree/MappingCollectionTest.java
new file mode 100644
index 00000000..d56d9a58
--- /dev/null
+++ b/src/test/java/net/fabricmc/mappingio/test/tests/tree/MappingCollectionTest.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright (c) 2025 FabricMC
+ *
+ * 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 net.fabricmc.mappingio.test.tests.tree;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.util.Collections;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import net.fabricmc.mappingio.MappedElementKind;
+import net.fabricmc.mappingio.MappingVisitor;
+import net.fabricmc.mappingio.test.visitors.VisitOrderVerifyingVisitor;
+import net.fabricmc.mappingio.tree.MappingCollectionView.FieldMappingCollectionView;
+import net.fabricmc.mappingio.tree.MappingTree.ClassMapping;
+import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
+import net.fabricmc.mappingio.tree.MappingTree.MethodArgMapping;
+import net.fabricmc.mappingio.tree.MappingTree.MethodMapping;
+import net.fabricmc.mappingio.tree.MappingTree.MethodVarMapping;
+import net.fabricmc.mappingio.tree.MappingTreeView.FieldMappingView;
+import net.fabricmc.mappingio.tree.MemoryMappingTree;
+import net.fabricmc.mappingio.tree.VisitableMappingTree;
+
+public class MappingCollectionTest {
+ private static final String nsA = "nsA";
+ private static final String nsB = "nsB";
+ private static final String cls1NsAName = "cls1NsAName";
+ private static final String cls1NsBName = "cls1NsBName";
+ private static final String fld1NsAName = "fld1NsAName";
+ private static final String fld1NsBName = "fld1NsBName";
+ private static final String fld2NsAName = "fld2NsAName";
+ private static final String fld1NsADesc = "I";
+ private static final String fld2NsADesc = "L" + cls1NsAName + ";";
+ private static final String fld1Comment = "fld1Comment";
+ private static final String mth1NsAName = "mth1NsAName";
+ private static final String mth1NsBName = "mth1NsBName";
+ private static final String mth2NsAName = "mth2NsAName";
+ private static final String mth1NsADesc = "()I";
+ private static final String mth2NsADesc = "()L" + cls1NsAName + ";";
+ private static final String arg1NsAName = "arg1NsAName";
+ private static final String arg2NsAName = "arg2NsAName";
+ private static final int arg1ArgPos = 0;
+ private static final int arg1LvIdx = 0;
+ private static final int arg2ArgPos = 1;
+ private static final int arg2LvIdx = 1;
+ private static final String var1NsAName = "var1NsAName";
+ private static final String var2NsAName = "var2NsAName";
+ private static final int var1LvtIdx = 0;
+ private static final int var1LvIdx = 0;
+ private static final int var1StartOpIdx = 0;
+ private static final int var1EndOpIdx = 1;
+ private static final int var2LvtIdx = 1;
+ private static final int var2LvIdx = 1;
+ private static final int var2StartOpIdx = 2;
+ private static final int var2EndOpIdx = 3;
+ private MemoryMappingTree tree1, tree2, emptyTree;
+
+ @BeforeEach
+ public void setup() throws Exception {
+ tree1 = new MemoryMappingTree();
+ tree2 = new MemoryMappingTree();
+ emptyTree = new MemoryMappingTree();
+
+ for (int i = 1; i <= 3; i++) {
+ VisitableMappingTree tree = i == 1 ? tree1 : i == 2 ? tree2 : emptyTree;
+ MappingVisitor delegate = new VisitOrderVerifyingVisitor(tree);
+
+ if (delegate.visitHeader()) {
+ delegate.visitNamespaces(nsA, Collections.singletonList(nsB));
+ }
+
+ if (delegate.visitContent()) {
+ if (tree != emptyTree && delegate.visitClass(cls1NsAName)) {
+ delegate.visitDstName(MappedElementKind.CLASS, 0, cls1NsBName);
+
+ if (delegate.visitElementContent(MappedElementKind.CLASS)) {
+ if (delegate.visitField(fld1NsAName, fld1NsADesc)) {
+ if (tree == tree2) {
+ delegate.visitDstName(MappedElementKind.FIELD, 0, fld1NsBName);
+ }
+
+ if (delegate.visitElementContent(MappedElementKind.FIELD) && tree == tree2) {
+ delegate.visitComment(MappedElementKind.FIELD, fld1Comment);
+ }
+ }
+
+ if (tree == tree2 && delegate.visitField(fld2NsAName, fld2NsADesc)) {
+ delegate.visitElementContent(MappedElementKind.FIELD);
+ }
+
+ if (delegate.visitMethod(mth1NsAName, mth1NsADesc)) {
+ if (tree == tree2) {
+ delegate.visitDstName(MappedElementKind.METHOD, 0, mth1NsBName);
+ }
+
+ if (delegate.visitElementContent(MappedElementKind.METHOD)) {
+ if (delegate.visitMethodArg(arg1ArgPos, arg1LvIdx, arg1NsAName)) {
+ delegate.visitElementContent(MappedElementKind.METHOD_ARG);
+ }
+
+ if (tree == tree2 && delegate.visitMethodArg(arg2ArgPos, arg2LvIdx, arg2NsAName)) {
+ delegate.visitElementContent(MappedElementKind.METHOD_ARG);
+ }
+
+ if (delegate.visitMethodVar(var1LvtIdx, var1LvIdx, var1StartOpIdx, var1EndOpIdx, var1NsAName)) {
+ delegate.visitElementContent(MappedElementKind.METHOD_VAR);
+ }
+
+ if (tree == tree2 && delegate.visitMethodVar(var2LvtIdx, var2LvIdx, var2StartOpIdx, var2EndOpIdx, var2NsAName)) {
+ delegate.visitElementContent(MappedElementKind.METHOD_VAR);
+ }
+ }
+ }
+
+ if (tree == tree2 && delegate.visitMethod(mth2NsAName, mth2NsADesc)) {
+ delegate.visitElementContent(MappedElementKind.METHOD);
+ }
+ }
+ }
+ }
+
+ delegate.visitEnd();
+ }
+ }
+
+ @Test
+ public void isEmptyClear() {
+ assertTrue(emptyTree.getClasses().isEmpty());
+ emptyTree.getClasses().clear();
+
+ cls1(tree1).getFields().clear();
+ cls1(tree1).getMethods().clear();
+ assertTrue(emptyTree.getClasses().add(cls1(tree1)));
+ assertTrue(cls1(emptyTree).getFields().isEmpty());
+ assertTrue(cls1(emptyTree).getMethods().isEmpty());
+
+ mth1(cls1(tree2)).getArgs().clear();
+ mth1(cls1(tree2)).getVars().clear();
+ assertTrue(cls1(emptyTree).getMethods().add(mth1(cls1(tree2))));
+ assertTrue(mth1(cls1(emptyTree)).getArgs().isEmpty());
+ assertTrue(mth1(cls1(emptyTree)).getVars().isEmpty());
+ }
+
+ @Test
+ public void size() {
+ // Classes
+ assertEquals(1, tree1.getClasses().size());
+ assertEquals(1, tree2.getClasses().size());
+
+ // Members
+ assertEquals(1, cls1(tree1).getFields().size());
+ assertEquals(2, cls1(tree2).getFields().size());
+ assertEquals(1, cls1(tree1).getMethods().size());
+ assertEquals(2, cls1(tree2).getMethods().size());
+
+ // Locals
+ assertEquals(1, mth1(cls1(tree1)).getArgs().size());
+ assertEquals(2, mth1(cls1(tree2)).getArgs().size());
+ assertEquals(1, mth1(cls1(tree1)).getVars().size());
+ assertEquals(2, mth1(cls1(tree2)).getVars().size());
+ assertEquals(0, mth2(cls1(tree2)).getArgs().size());
+ assertEquals(0, mth2(cls1(tree2)).getVars().size());
+ }
+
+ @Test
+ public void contains() {
+ // Classes
+ assertTrue(tree1.getClasses().contains(cls1(tree1)));
+ assertTrue(tree2.getClasses().contains(cls1(tree2)));
+ assertFalse(tree1.getClasses().contains(cls1(tree2)));
+ assertTrue(tree1.getClasses().containsCompatible(cls1(tree2)));
+ assertFalse(tree2.getClasses().contains(cls1(tree1)));
+ assertTrue(tree2.getClasses().containsCompatible(cls1(tree1)));
+
+ // Fields
+ assertTrue(cls1(tree1).getFields().contains(fld1(cls1(tree1))));
+ assertFalse(cls1(tree1).getFields().contains(fld2(cls1(tree2))));
+ assertFalse(cls1(tree2).getFields().contains(fld1(cls1(tree1))));
+ assertTrue(cls1(tree2).getFields().containsCompatible(fld1(cls1(tree1))));
+ assertTrue(cls1(tree2).getFields().contains(fld2(cls1(tree2))));
+ assertTrue(cls1(tree2).getFields().containsCompatible(fld2(cls1(tree2))));
+
+ // Methods
+ assertTrue(cls1(tree1).getMethods().contains(mth1(cls1(tree1))));
+ assertFalse(cls1(tree1).getMethods().contains(mth2(cls1(tree2))));
+ assertFalse(cls1(tree2).getMethods().contains(mth1(cls1(tree1))));
+ assertTrue(cls1(tree2).getMethods().containsCompatible(mth1(cls1(tree1))));
+ assertTrue(cls1(tree2).getMethods().contains(mth2(cls1(tree2))));
+ assertTrue(cls1(tree2).getMethods().containsCompatible(mth2(cls1(tree2))));
+
+ // Args
+ assertTrue(mth1(cls1(tree1)).getArgs().contains(arg1(mth1(cls1(tree1)))));
+ assertFalse(mth1(cls1(tree1)).getArgs().contains(arg2(mth1(cls1(tree2)))));
+ assertFalse(mth1(cls1(tree2)).getArgs().contains(arg1(mth1(cls1(tree1)))));
+ assertTrue(mth1(cls1(tree2)).getArgs().containsCompatible(arg1(mth1(cls1(tree1)))));
+ assertTrue(mth1(cls1(tree2)).getArgs().contains(arg2(mth1(cls1(tree2)))));
+ assertTrue(mth1(cls1(tree2)).getArgs().containsCompatible(arg2(mth1(cls1(tree2)))));
+
+ // Vars
+ assertTrue(mth1(cls1(tree1)).getVars().contains(var1(mth1(cls1(tree1)))));
+ assertFalse(mth1(cls1(tree1)).getVars().contains(var2(mth1(cls1(tree2)))));
+ assertFalse(mth1(cls1(tree2)).getVars().contains(var1(mth1(cls1(tree1)))));
+ assertTrue(mth1(cls1(tree2)).getVars().containsCompatible(var1(mth1(cls1(tree1)))));
+ assertTrue(mth1(cls1(tree2)).getVars().contains(var2(mth1(cls1(tree2)))));
+ assertTrue(mth1(cls1(tree2)).getVars().containsCompatible(var2(mth1(cls1(tree2)))));
+ }
+
+ @Test
+ public void merge() {
+ assertNull(fld1(cls1(tree1)).getComment());
+ cls1(tree1).getFields().add(fld1(cls1(tree2)));
+ assertEquals(fld1Comment, fld1(cls1(tree1)).getComment());
+ }
+
+ @Test
+ public void addRemoveRetain() {
+ cls1(tree2).getFields().remove(fld1(cls1(tree2)));
+ assertFalse(cls1(tree2).getFields().contains(fld1(cls1(tree2))));
+ assertEquals(1, cls1(tree2).getFields().size());
+
+ for (int i = 0; i <= 4; i++) {
+ switch (i) {
+ case 0:
+ cls1(tree1).getFields().add(fld2(cls1(tree2)));
+ break;
+ case 1:
+ cls1(tree1).getFields().addAllViews(cls1(tree2).getFields());
+ break;
+ case 2:
+ cls1(tree1).getFields().addAllViews((FieldMappingCollectionView extends FieldMappingView>) cls1(tree2).getFields());
+ break;
+ case 3:
+ cls1(tree1).getFields().addAllViews(Collections.singleton((FieldMapping) fld2(cls1(tree2))));
+ break;
+ case 4:
+ cls1(tree1).getFields().addAllViews(Collections.singleton(fld2(cls1(tree2))));
+ break;
+ }
+
+ assertFalse(cls1(tree1).getFields().contains(fld2(cls1(tree2))));
+ assertTrue(cls1(tree1).getFields().containsCompatible(fld2(cls1(tree2))));
+ assertEquals(2, cls1(tree1).getFields().size());
+
+ switch (i) {
+ case 0:
+ assertFalse(cls1(tree1).getFields().remove(fld2(cls1(tree2))));
+ assertTrue(cls1(tree1).getFields().removeCompatible(fld2(cls1(tree2))));
+ break;
+ case 1:
+ assertFalse(cls1(tree1).getFields().removeAll(cls1(tree2).getFields()));
+ assertTrue(cls1(tree1).getFields().removeAllCompatible(cls1(tree2).getFields()));
+ break;
+ case 2:
+ assertFalse(cls1(tree1).getFields().removeAll(Collections.singleton(fld2(cls1(tree2)))));
+ assertTrue(cls1(tree1).getFields().removeAllCompatible(Collections.singleton(fld2(cls1(tree2)))));
+ break;
+ case 3:
+ assertTrue(cls1(tree1).getFields().retainAll(Collections.singleton(fld1(cls1(tree1)))));
+ assertFalse(cls1(tree1).getFields().retainAll(Collections.singleton(fld1(cls1(tree1)))));
+ assertFalse(cls1(tree1).getFields().retainAllCompatible(Collections.singleton(fld1(cls1(tree1)))));
+ break;
+ case 4:
+ assertTrue(cls1(tree1).getFields().retainAllCompatible(Collections.singleton(fld1(cls1(tree1)))));
+ assertFalse(cls1(tree1).getFields().retainAllCompatible(Collections.singleton(fld1(cls1(tree1)))));
+ assertFalse(cls1(tree1).getFields().retainAll(Collections.singleton(fld1(cls1(tree1)))));
+ break;
+ }
+
+ assertFalse(cls1(tree1).getFields().contains(fld2(cls1(tree2))));
+ assertFalse(cls1(tree1).getFields().containsCompatible(fld2(cls1(tree2))));
+ assertEquals(1, cls1(tree1).getFields().size());
+ }
+ }
+
+ private ClassMapping cls1(MemoryMappingTree tree) {
+ return tree.getClass(cls1NsAName);
+ }
+
+ private FieldMapping fld1(ClassMapping cls) {
+ return cls.getField(fld1NsAName, fld1NsADesc);
+ }
+
+ private FieldMapping fld2(ClassMapping cls) {
+ return cls.getField(fld2NsAName, fld2NsADesc);
+ }
+
+ private MethodMapping mth1(ClassMapping cls) {
+ return cls.getMethod(mth1NsAName, mth1NsADesc);
+ }
+
+ private MethodMapping mth2(ClassMapping cls) {
+ return cls.getMethod(mth2NsAName, mth2NsADesc);
+ }
+
+ private MethodArgMapping arg1(MethodMapping mth) {
+ return mth.getArg(arg1ArgPos, arg1LvIdx, arg1NsAName);
+ }
+
+ private MethodArgMapping arg2(MethodMapping mth) {
+ return mth.getArg(arg2ArgPos, arg2LvIdx, arg2NsAName);
+ }
+
+ private MethodVarMapping var1(MethodMapping mth) {
+ return mth.getVar(var1LvtIdx, var1LvIdx, var1StartOpIdx, var1EndOpIdx, var1NsAName);
+ }
+
+ private MethodVarMapping var2(MethodMapping mth) {
+ return mth.getVar(var2LvtIdx, var2LvIdx, var2StartOpIdx, var2EndOpIdx, var2NsAName);
+ }
+}