Skip to content

Commit 1067889

Browse files
authored
Add MappingTree#propagateOuterClassNames (#120)
1 parent 485410e commit 1067889

File tree

4 files changed

+111
-2
lines changed

4 files changed

+111
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
55

66
## [Unreleased]
77
- Added a simplified `MappingNsCompleter` constructor for completing all destination names with the source names
8+
- Added `MappingTree#propagateOuterClassNames` as a more efficient tree-API alternative to `OuterClassNamePropagator`
89
- Made `OuterClassNamePropagator` configurable
910
- Made Enigma writer always output destination names if visited explicitly, establishing consistency across all writers
1011
- Adjusted format detection to only return ENIGMA_DIR for non-empty directories with at least one `.mapping` file

src/main/java/net/fabricmc/mappingio/adapter/OuterClassNamePropagator.java

-2
Original file line numberDiff line numberDiff line change
@@ -259,8 +259,6 @@ public boolean visitEnd() throws IOException {
259259
private final boolean processRemappedDstNames;
260260
private final Map<String, String[]> dstNamesBySrcName = new HashMap<>();
261261
private final Set<String> modifiedClasses = new HashSet<>();
262-
private String srcNamespaceToProcess;
263-
private int srcNsId;
264262
private List<String> dstNamespaces;
265263
private Collection<String> dstNamespacesToProcess;
266264
private Collection<Integer> dstNamespaceIndicesToProcess;

src/main/java/net/fabricmc/mappingio/tree/MappingTree.java

+82
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616

1717
package net.fabricmc.mappingio.tree;
1818

19+
import java.util.Arrays;
1920
import java.util.Collection;
2021
import java.util.List;
22+
import java.util.regex.Pattern;
2123

2224
import org.jetbrains.annotations.Nullable;
2325

@@ -110,6 +112,86 @@ default ClassMapping getClass(String name, int namespace) {
110112
@Nullable
111113
ClassMapping removeClass(String srcName);
112114

115+
/**
116+
* Searches for inner classes whose effective destination name contains outer classes referenced via their source name,
117+
* scans the tree for potential mappings for these enclosing classes, and applies the latters' destination names
118+
* to the formers' fully qualified name.
119+
*
120+
* <p>For example, it takes a class {@code class_1$class_2} that doesn't have a mapping,
121+
* tries to find {@code class_1}, which let's say has the mapping {@code SomeClass},
122+
* and changes the former's destination name to {@code SomeClass$class_2}.
123+
*
124+
* <p>Equivalent of {@link OuterClassNameInheritingVisitor}, but more efficient
125+
* since the tree's existing class map can be reused.
126+
*
127+
* @param processRemappedDstNames Whether already remapped destination names should also get their unmapped outer classes replaced.
128+
*/
129+
default void propagateOuterClassNames(boolean processRemappedDstNames) {
130+
propagateOuterClassNames(getSrcNamespace(), getDstNamespaces(), processRemappedDstNames);
131+
}
132+
133+
/**
134+
* Searches for inner classes whose effective destination name contains outer classes referenced via their source name,
135+
* scans the tree for potential mappings for these enclosing classes, and applies the latters' destination names
136+
* to the formers' fully qualified name.
137+
*
138+
* <p>For example, it takes a class {@code class_1$class_2} that doesn't have a mapping,
139+
* tries to find {@code class_1}, which let's say has the mapping {@code SomeClass},
140+
* and changes the former's destination name to {@code SomeClass$class_2}.
141+
*
142+
* <p>Equivalent of {@link OuterClassNameInheritingVisitor}, but more efficient
143+
* since the tree's existing class map can be reused.
144+
*
145+
* @param srcNamespace The namespace where the original/unmapped outer class names originate from.
146+
* @param dstNamespaces The namespaces where outer class names shall be propagated.
147+
* @param processRemappedDstNames Whether already remapped destination names should also get their unmapped outer classes replaced.
148+
*/
149+
default void propagateOuterClassNames(String srcNamespace, Collection<String> dstNamespaces, boolean processRemappedDstNames) {
150+
int srcNsId = getNamespaceId(srcNamespace);
151+
if (srcNsId == NULL_NAMESPACE_ID) throw new IllegalArgumentException("Namespace " + srcNsId + " does not exist");
152+
153+
for (ClassMapping cls : getClasses()) {
154+
for (String dstNs : dstNamespaces) {
155+
int dstNsId = getNamespaceId(dstNs);
156+
if (dstNsId == SRC_NAMESPACE_ID) throw new UnsupportedOperationException("Cannot change source names");
157+
if (dstNsId == NULL_NAMESPACE_ID) throw new IllegalArgumentException("Destination namespace " + dstNs + " does not exist");
158+
if (dstNsId == srcNsId) continue;
159+
160+
String srcName = cls.getName(srcNsId);
161+
if (srcName == null) continue;
162+
String dstName = cls.getName(dstNsId);
163+
164+
int idx = srcName.lastIndexOf('$');
165+
if (idx == -1) continue;
166+
167+
if (!processRemappedDstNames && dstName != null && !dstName.equals(srcName)) {
168+
continue;
169+
}
170+
171+
String[] srcParts = srcName.split(Pattern.quote("$"));
172+
String[] dstParts = dstName == null ? srcParts : dstName.split(Pattern.quote("$"));
173+
assert dstParts.length == srcParts.length;
174+
175+
for (int pos = srcParts.length - 2; pos >= 0; pos--) {
176+
String outerSrcName = String.join("$", Arrays.copyOfRange(srcParts, 0, pos + 1));
177+
178+
if (dstName != null && !dstParts[pos].equals(srcParts[pos])) {
179+
// That part already has a different mapping
180+
continue;
181+
}
182+
183+
ClassMapping outerCls = getClass(outerSrcName, srcNsId);
184+
String outerDstName;
185+
186+
if (outerCls != null && (outerDstName = outerCls.getName(dstNsId)) != null && !outerDstName.equals(outerSrcName)) {
187+
cls.setDstName(outerDstName + "$" + String.join("$", Arrays.copyOfRange(dstParts, pos + 1, dstParts.length)), dstNsId);
188+
break;
189+
}
190+
}
191+
}
192+
}
193+
}
194+
113195
@Override
114196
@Nullable
115197
default FieldMapping getField(String srcClsName, String srcName, @Nullable String srcDesc) {

src/test/java/net/fabricmc/mappingio/test/tests/OuterClassNamePropagationTest.java

+28
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,34 @@ public void visitorThroughTree() throws IOException {
9898
}
9999
}
100100

101+
@Test
102+
public void tree() throws IOException {
103+
for (int pass = 1; pass <= 2; pass++) {
104+
boolean processRemappedDstNames = pass == 1;
105+
106+
VisitableMappingTree tree = acceptMappings(new MemoryMappingTree());
107+
tree.propagateOuterClassNames(processRemappedDstNames);
108+
tree.accept(new OuterClassNameChecker(true, dstNamespaces, processRemappedDstNames));
109+
110+
checkDiskEquivalence(tree, processRemappedDstNames);
111+
}
112+
113+
VisitableMappingTree tree = acceptMappings(new MemoryMappingTree());
114+
115+
assertThrows(UnsupportedOperationException.class, () -> tree.propagateOuterClassNames(
116+
dstNamespaces.get(0),
117+
Collections.singletonList(srcNamespace),
118+
false));
119+
assertThrows(IllegalArgumentException.class, () -> tree.propagateOuterClassNames(
120+
invalidNs,
121+
tree.getDstNamespaces(),
122+
false));
123+
assertThrows(IllegalArgumentException.class, () -> tree.propagateOuterClassNames(
124+
tree.getSrcNamespace(),
125+
Collections.singletonList(invalidNs),
126+
false));
127+
}
128+
101129
private void checkDiskEquivalence(VisitableMappingTree tree, boolean processRemappedDstNames) throws IOException {
102130
for (MappingFormat format : MappingFormat.values()) {
103131
MappingDir dir = processRemappedDstNames

0 commit comments

Comments
 (0)