summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/translation/mapping
diff options
context:
space:
mode:
authorGravatar Runemoro2020-06-03 13:39:42 -0400
committerGravatar GitHub2020-06-03 18:39:42 +0100
commit0f47403d0220757fed189b76e2071e25b1025cb8 (patch)
tree879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /src/main/java/cuchaz/enigma/translation/mapping
parentFix search dialog hanging for a short time sometimes (#250) (diff)
downloadenigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip
Split GUI code to separate module (#242)
* Split into modules * Post merge compile fixes Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'src/main/java/cuchaz/enigma/translation/mapping')
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java25
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java75
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java105
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java41
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java227
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java10
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java32
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java16
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java76
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java99
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java53
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java6
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java27
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java9
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java319
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java316
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java59
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java51
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java14
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java17
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java134
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java30
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java118
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java115
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java148
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java295
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java169
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java110
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java26
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java40
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java188
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java75
34 files changed, 0 insertions, 3103 deletions
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
deleted file mode 100644
index 5b79b79..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
+++ /dev/null
@@ -1,25 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public enum AccessModifier {
6 UNCHANGED, PUBLIC, PROTECTED, PRIVATE;
7
8 public String getFormattedName() {
9 return "ACC:" + super.toString();
10 }
11
12 public AccessFlags transform(AccessFlags access) {
13 switch (this) {
14 case PUBLIC:
15 return access.setPublic();
16 case PROTECTED:
17 return access.setProtected();
18 case PRIVATE:
19 return access.setPrivate();
20 case UNCHANGED:
21 default:
22 return access;
23 }
24 }
25}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
deleted file mode 100644
index e1a3253..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.stream.Stream;
7
8public interface EntryMap<T> {
9 void insert(Entry<?> entry, T value);
10
11 @Nullable
12 T remove(Entry<?> entry);
13
14 @Nullable
15 T get(Entry<?> entry);
16
17 default boolean contains(Entry<?> entry) {
18 return get(entry) != null;
19 }
20
21 Stream<Entry<?>> getAllEntries();
22
23 boolean isEmpty();
24}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
deleted file mode 100644
index c607817..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
+++ /dev/null
@@ -1,75 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4import javax.annotation.Nullable;
5
6public class EntryMapping {
7 private final String targetName;
8 private final AccessModifier accessModifier;
9 private final @Nullable String javadoc;
10
11 public EntryMapping(@Nonnull String targetName) {
12 this(targetName, AccessModifier.UNCHANGED);
13 }
14
15 public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) {
16 this(targetName, AccessModifier.UNCHANGED, javadoc);
17 }
18
19 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) {
20 this(targetName, accessModifier, null);
21 }
22
23 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) {
24 this.targetName = targetName;
25 this.accessModifier = accessModifier;
26 this.javadoc = javadoc;
27 }
28
29 @Nonnull
30 public String getTargetName() {
31 return targetName;
32 }
33
34 @Nonnull
35 public AccessModifier getAccessModifier() {
36 if (accessModifier == null) {
37 return AccessModifier.UNCHANGED;
38 }
39 return accessModifier;
40 }
41
42 @Nullable
43 public String getJavadoc() {
44 return javadoc;
45 }
46
47 public EntryMapping withName(String newName) {
48 return new EntryMapping(newName, accessModifier, javadoc);
49 }
50
51 public EntryMapping withModifier(AccessModifier newModifier) {
52 return new EntryMapping(targetName, newModifier, javadoc);
53 }
54
55 public EntryMapping withDocs(String newDocs) {
56 return new EntryMapping(targetName, accessModifier, newDocs);
57 }
58
59 @Override
60 public boolean equals(Object obj) {
61 if (obj == this) return true;
62
63 if (obj instanceof EntryMapping) {
64 EntryMapping mapping = (EntryMapping) obj;
65 return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier);
66 }
67
68 return false;
69 }
70
71 @Override
72 public int hashCode() {
73 return targetName.hashCode() + accessModifier.hashCode() * 31;
74 }
75}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
deleted file mode 100644
index ad36c97..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
+++ /dev/null
@@ -1,105 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.JarIndex;
4import cuchaz.enigma.translation.MappingTranslator;
5import cuchaz.enigma.translation.Translatable;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.entry.Entry;
11
12import javax.annotation.Nullable;
13import java.util.Collection;
14import java.util.function.UnaryOperator;
15import java.util.stream.Stream;
16
17public class EntryRemapper {
18 private final DeltaTrackingTree<EntryMapping> obfToDeobf;
19
20 private final EntryResolver obfResolver;
21 private final Translator deobfuscator;
22
23 private final MappingValidator validator;
24
25 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) {
26 this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf);
27
28 this.obfResolver = jarIndex.getEntryResolver();
29
30 this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
31
32 this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex);
33 }
34
35 public static EntryRemapper mapped(JarIndex index, EntryTree<EntryMapping> obfToDeobf) {
36 return new EntryRemapper(index, obfToDeobf);
37 }
38
39 public static EntryRemapper empty(JarIndex index) {
40 return new EntryRemapper(index, new HashEntryTree<>());
41 }
42
43 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
44 mapFromObf(obfuscatedEntry, deobfMapping, true);
45 }
46
47 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) {
48 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST);
49
50 if (renaming && deobfMapping != null) {
51 for (E resolvedEntry : resolvedEntries) {
52 validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
53 }
54 }
55
56 for (E resolvedEntry : resolvedEntries) {
57 obfToDeobf.insert(resolvedEntry, deobfMapping);
58 }
59 }
60
61 public void removeByObf(Entry<?> obfuscatedEntry) {
62 mapFromObf(obfuscatedEntry, null);
63 }
64
65 @Nullable
66 public EntryMapping getDeobfMapping(Entry<?> entry) {
67 return obfToDeobf.get(entry);
68 }
69
70 public boolean hasDeobfMapping(Entry<?> obfEntry) {
71 return obfToDeobf.contains(obfEntry);
72 }
73
74 public <T extends Translatable> T deobfuscate(T translatable) {
75 return deobfuscator.translate(translatable);
76 }
77
78 public Translator getDeobfuscator() {
79 return deobfuscator;
80 }
81
82 public Stream<Entry<?>> getObfEntries() {
83 return obfToDeobf.getAllEntries();
84 }
85
86 public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) {
87 return obfToDeobf.getChildren(obfuscatedEntry);
88 }
89
90 public DeltaTrackingTree<EntryMapping> getObfToDeobf() {
91 return obfToDeobf;
92 }
93
94 public MappingDelta<EntryMapping> takeMappingDelta() {
95 return obfToDeobf.takeDelta();
96 }
97
98 public boolean isDirty() {
99 return obfToDeobf.isDirty();
100 }
101
102 public EntryResolver getObfResolver() {
103 return obfResolver;
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
deleted file mode 100644
index 521f72d..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
+++ /dev/null
@@ -1,41 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Streams;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.translation.representation.entry.MethodEntry;
7
8import java.util.Collection;
9import java.util.Set;
10import java.util.stream.Collectors;
11
12public interface EntryResolver {
13 <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy);
14
15 default <E extends Entry<?>> E resolveFirstEntry(E entry, ResolutionStrategy strategy) {
16 return resolveEntry(entry, strategy).stream().findFirst().orElse(entry);
17 }
18
19 default <E extends Entry<?>, C extends Entry<?>> Collection<EntryReference<E, C>> resolveReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
20 Collection<E> entry = resolveEntry(reference.entry, strategy);
21 if (reference.context != null) {
22 Collection<C> context = resolveEntry(reference.context, strategy);
23 return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference))
24 .collect(Collectors.toList());
25 } else {
26 return entry.stream()
27 .map(e -> new EntryReference<>(e, null, reference))
28 .collect(Collectors.toList());
29 }
30 }
31
32 default <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolveFirstReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
33 E entry = resolveFirstEntry(reference.entry, strategy);
34 C context = resolveFirstEntry(reference.context, strategy);
35 return new EntryReference<>(entry, context, reference);
36 }
37
38 Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry);
39
40 Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry);
41}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
deleted file mode 100644
index 78231dd..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
+++ /dev/null
@@ -1,227 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Sets;
4import cuchaz.enigma.analysis.IndexTreeBuilder;
5import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
6import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
7import cuchaz.enigma.analysis.index.BridgeMethodIndex;
8import cuchaz.enigma.analysis.index.EntryIndex;
9import cuchaz.enigma.analysis.index.InheritanceIndex;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.translation.VoidTranslator;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import javax.annotation.Nullable;
18import java.util.*;
19import java.util.stream.Collectors;
20
21public class IndexEntryResolver implements EntryResolver {
22 private final EntryIndex entryIndex;
23 private final InheritanceIndex inheritanceIndex;
24 private final BridgeMethodIndex bridgeMethodIndex;
25
26 private final IndexTreeBuilder treeBuilder;
27
28 public IndexEntryResolver(JarIndex index) {
29 this.entryIndex = index.getEntryIndex();
30 this.inheritanceIndex = index.getInheritanceIndex();
31 this.bridgeMethodIndex = index.getBridgeMethodIndex();
32
33 this.treeBuilder = new IndexTreeBuilder(index);
34 }
35
36 @Override
37 @SuppressWarnings("unchecked")
38 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
39 if (entry == null) {
40 return Collections.emptySet();
41 }
42
43 Entry<ClassEntry> classChild = getClassChild(entry);
44 if (classChild != null && !(classChild instanceof ClassEntry)) {
45 AccessFlags access = entryIndex.getEntryAccess(classChild);
46
47 // If we're looking for the closest and this entry exists, we're done looking
48 if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) {
49 return Collections.singleton(entry);
50 }
51
52 if (access == null || !access.isPrivate()) {
53 Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy);
54 if (!resolvedChildren.isEmpty()) {
55 return resolvedChildren.stream()
56 .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild))
57 .collect(Collectors.toList());
58 }
59 }
60 }
61
62 return Collections.singleton(entry);
63 }
64
65 @Nullable
66 private Entry<ClassEntry> getClassChild(Entry<?> entry) {
67 if (entry instanceof ClassEntry) {
68 return null;
69 }
70
71 // get the entry in the hierarchy that is the child of a class
72 List<Entry<?>> ancestry = entry.getAncestry();
73 for (int i = ancestry.size() - 1; i > 0; i--) {
74 Entry<?> child = ancestry.get(i);
75 Entry<ClassEntry> cast = child.castParent(ClassEntry.class);
76 if (cast != null && !(cast instanceof ClassEntry)) {
77 // we found the entry which is a child of a class, we are now able to resolve the owner of this entry
78 return cast;
79 }
80 }
81
82 return null;
83 }
84
85 private Set<Entry<ClassEntry>> resolveChildEntry(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
86 ClassEntry ownerClass = entry.getParent();
87
88 if (entry instanceof MethodEntry) {
89 MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry);
90 if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) {
91 Set<Entry<ClassEntry>> resolvedBridge = resolveChildEntry(bridgeMethod, strategy);
92 if (!resolvedBridge.isEmpty()) {
93 return resolvedBridge;
94 } else {
95 return Collections.singleton(bridgeMethod);
96 }
97 }
98 }
99
100 Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>();
101
102 for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) {
103 Entry<ClassEntry> parentEntry = entry.withParent(parentClass);
104
105 if (strategy == ResolutionStrategy.RESOLVE_ROOT) {
106 resolvedEntries.addAll(resolveRoot(parentEntry, strategy));
107 } else {
108 resolvedEntries.addAll(resolveClosest(parentEntry, strategy));
109 }
110 }
111
112 return resolvedEntries;
113 }
114
115 private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
116 // When resolving root, we want to first look for the lowest entry before returning ourselves
117 Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy);
118
119 if (parentResolution.isEmpty()) {
120 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
121 if (parentAccess != null && !parentAccess.isPrivate()) {
122 return Collections.singleton(entry);
123 }
124 }
125
126 return parentResolution;
127 }
128
129 private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
130 // When resolving closest, we want to first check if we exist before looking further down
131 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
132 if (parentAccess != null && !parentAccess.isPrivate()) {
133 return Collections.singleton(entry);
134 } else {
135 return resolveChildEntry(entry, strategy);
136 }
137 }
138
139 @Override
140 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
141 MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class);
142 if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) {
143 return Collections.singleton(entry);
144 }
145
146 Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod);
147 Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size());
148
149 for (MethodEntry equivalentMethod : equivalentMethods) {
150 Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod);
151 equivalentEntries.add(equivalentEntry);
152 }
153
154 return equivalentEntries;
155 }
156
157 @Override
158 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
159 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
160 if (access == null) {
161 throw new IllegalArgumentException("Could not find method " + methodEntry);
162 }
163
164 if (!canInherit(methodEntry, access)) {
165 return Collections.singleton(methodEntry);
166 }
167
168 Set<MethodEntry> methodEntries = Sets.newHashSet();
169 resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry));
170 return methodEntries;
171 }
172
173 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
174 MethodEntry methodEntry = node.getMethodEntry();
175 if (methodEntries.contains(methodEntry)) {
176 return;
177 }
178
179 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
180 if (flags != null && canInherit(methodEntry, flags)) {
181 // collect the entry
182 methodEntries.add(methodEntry);
183 }
184
185 // look at bridge methods!
186 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
187 while (bridgedMethod != null) {
188 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
189 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
190 }
191
192 // look at interface methods too
193 for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) {
194 resolveEquivalentMethods(methodEntries, implementationsNode);
195 }
196
197 // recurse
198 for (int i = 0; i < node.getChildCount(); i++) {
199 resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
200 }
201 }
202
203 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
204 MethodEntry methodEntry = node.getMethodEntry();
205 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
206 if (flags != null && !flags.isPrivate() && !flags.isStatic()) {
207 // collect the entry
208 methodEntries.add(methodEntry);
209 }
210
211 // look at bridge methods!
212 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
213 while (bridgedMethod != null) {
214 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
215 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
216 }
217
218 // recurse
219 for (int i = 0; i < node.getChildCount(); i++) {
220 resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
221 }
222 }
223
224 private boolean canInherit(MethodEntry entry, AccessFlags access) {
225 return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal();
226 }
227}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
deleted file mode 100644
index 1407bb6..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
+++ /dev/null
@@ -1,54 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
7import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import java.util.stream.Stream;
11
12public class MappingDelta<T> implements Translatable {
13 public static final Object PLACEHOLDER = new Object();
14
15 private final EntryTree<T> baseMappings;
16
17 private final EntryTree<Object> changes;
18
19 public MappingDelta(EntryTree<T> baseMappings, EntryTree<Object> changes) {
20 this.baseMappings = baseMappings;
21 this.changes = changes;
22 }
23
24 public MappingDelta(EntryTree<T> baseMappings) {
25 this(baseMappings, new HashEntryTree<>());
26 }
27
28 public static <T> MappingDelta<T> added(EntryTree<T> mappings) {
29 EntryTree<Object> changes = new HashEntryTree<>();
30 mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER));
31
32 return new MappingDelta<>(new HashEntryTree<>(), changes);
33 }
34
35 public EntryTree<T> getBaseMappings() {
36 return baseMappings;
37 }
38
39 public EntryTree<?> getChanges() {
40 return changes;
41 }
42
43 public Stream<Entry<?>> getChangedRoots() {
44 return changes.getRootNodes().map(EntryTreeNode::getEntry);
45 }
46
47 @Override
48 public MappingDelta<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
49 return new MappingDelta<>(
50 translator.translate(baseMappings),
51 translator.translate(changes)
52 );
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java
deleted file mode 100644
index e40bfe7..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.gson.annotations.SerializedName;
4
5public enum MappingFileNameFormat {
6 @SerializedName("by_obf")
7 BY_OBF,
8 @SerializedName("by_deobf")
9 BY_DEOBF
10}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
deleted file mode 100644
index 5d39e3d..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6
7public class MappingPair<E extends Entry<?>, M> {
8 private final E entry;
9 private M mapping;
10
11 public MappingPair(E entry, @Nullable M mapping) {
12 this.entry = entry;
13 this.mapping = mapping;
14 }
15
16 public MappingPair(E entry) {
17 this(entry, null);
18 }
19
20 public E getEntry() {
21 return entry;
22 }
23
24 @Nullable
25 public M getMapping() {
26 return mapping;
27 }
28
29 public void setMapping(M mapping) {
30 this.mapping = mapping;
31 }
32}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java
deleted file mode 100644
index 07065d6..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java
+++ /dev/null
@@ -1,16 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.gson.annotations.SerializedName;
4
5public class MappingSaveParameters {
6 @SerializedName("file_name_format")
7 private final MappingFileNameFormat fileNameFormat;
8
9 public MappingSaveParameters(MappingFileNameFormat fileNameFormat) {
10 this.fileNameFormat = fileNameFormat;
11 }
12
13 public MappingFileNameFormat getFileNameFormat() {
14 return fileNameFormat;
15 }
16}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
deleted file mode 100644
index dffcb0c..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
+++ /dev/null
@@ -1,76 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.InheritanceIndex;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.throwables.IllegalNameException;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.stream.Collectors;
14
15public class MappingValidator {
16 private final EntryTree<EntryMapping> obfToDeobf;
17 private final Translator deobfuscator;
18 private final JarIndex index;
19
20 public MappingValidator(EntryTree<EntryMapping> obfToDeobf, Translator deobfuscator, JarIndex index) {
21 this.obfToDeobf = obfToDeobf;
22 this.deobfuscator = deobfuscator;
23 this.index = index;
24 }
25
26 public void validateRename(Entry<?> entry, String name) throws IllegalNameException {
27 Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry);
28 for (Entry<?> equivalentEntry : equivalentEntries) {
29 equivalentEntry.validateName(name);
30 validateUnique(equivalentEntry, name);
31 }
32 }
33
34 private void validateUnique(Entry<?> entry, String name) {
35 ClassEntry containingClass = entry.getContainingClass();
36 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass);
37
38 for (ClassEntry relatedClass : relatedClasses) {
39 Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass);
40 Entry<?> translatedEntry = deobfuscator.translate(relatedEntry);
41
42 Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream()
43 .map(deobfuscator::translate)
44 .collect(Collectors.toList());
45
46 if (!isUnique(translatedEntry, translatedSiblings, name)) {
47 Entry<?> parent = translatedEntry.getParent();
48 if (parent != null) {
49 throw new IllegalNameException(name, "Name is not unique in " + parent + "!");
50 } else {
51 throw new IllegalNameException(name, "Name is not unique!");
52 }
53 }
54 }
55 }
56
57 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) {
58 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
59
60 Collection<ClassEntry> relatedClasses = new HashSet<>();
61 relatedClasses.add(classEntry);
62 relatedClasses.addAll(inheritanceIndex.getChildren(classEntry));
63 relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry));
64
65 return relatedClasses;
66 }
67
68 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
69 for (Entry<?> sibling : siblings) {
70 if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) {
71 return false;
72 }
73 }
74 return true;
75 }
76}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
deleted file mode 100644
index 5d9794f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
+++ /dev/null
@@ -1,99 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.ProgressListener;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.FieldEntry;
21import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
22import cuchaz.enigma.translation.representation.entry.MethodEntry;
23
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.Map;
27import java.util.stream.Collectors;
28
29public class MappingsChecker {
30 private final JarIndex index;
31 private final EntryTree<EntryMapping> mappings;
32
33 public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
34 this.index = index;
35 this.mappings = mappings;
36 }
37
38 public Dropped dropBrokenMappings(ProgressListener progress) {
39 Dropped dropped = new Dropped();
40
41 Collection<Entry<?>> obfEntries = mappings.getAllEntries()
42 .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry)
43 .collect(Collectors.toList());
44
45 progress.init(obfEntries.size(), "Checking for dropped mappings");
46
47 int steps = 0;
48 for (Entry<?> entry : obfEntries) {
49 progress.step(steps++, entry.toString());
50 tryDropEntry(dropped, entry);
51 }
52
53 dropped.apply(mappings);
54
55 return dropped;
56 }
57
58 private void tryDropEntry(Dropped dropped, Entry<?> entry) {
59 if (shouldDropEntry(entry)) {
60 EntryMapping mapping = mappings.get(entry);
61 if (mapping != null) {
62 dropped.drop(entry, mapping);
63 }
64 }
65 }
66
67 private boolean shouldDropEntry(Entry<?> entry) {
68 if (!index.getEntryIndex().hasEntry(entry)) {
69 return true;
70 }
71 Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
72 return !resolvedEntries.contains(entry);
73 }
74
75 public static class Dropped {
76 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
77
78 public void drop(Entry<?> entry, EntryMapping mapping) {
79 droppedMappings.put(entry, mapping.getTargetName());
80 }
81
82 void apply(EntryTree<EntryMapping> mappings) {
83 for (Entry<?> entry : droppedMappings.keySet()) {
84 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
85 if (node == null) {
86 continue;
87 }
88
89 for (Entry<?> childEntry : node.getChildrenRecursively()) {
90 mappings.remove(childEntry);
91 }
92 }
93 }
94
95 public Map<Entry<?>, String> getDroppedMappings() {
96 return droppedMappings;
97 }
98 }
99}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
deleted file mode 100644
index 5bc2f67..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
+++ /dev/null
@@ -1,53 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16
17import java.util.Arrays;
18import java.util.List;
19import java.util.regex.Pattern;
20
21public class NameValidator {
22 private static final Pattern IDENTIFIER_PATTERN;
23 private static final Pattern CLASS_PATTERN;
24 private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList(
25 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
26 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
27 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
28 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
29 "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_"
30 );
31
32 static {
33 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
34 IDENTIFIER_PATTERN = Pattern.compile(identifierRegex);
35 CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
36 }
37
38 public static void validateClassName(String name) {
39 if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
40 throw new IllegalNameException(name, "This doesn't look like a legal class name");
41 }
42 }
43
44 public static void validateIdentifier(String name) {
45 if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
46 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
47 }
48 }
49
50 public static boolean isReserved(String name) {
51 return ILLEGAL_IDENTIFIERS.contains(name);
52 }
53}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
deleted file mode 100644
index 1c28e02..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
+++ /dev/null
@@ -1,6 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3public enum ResolutionStrategy {
4 RESOLVE_ROOT,
5 RESOLVE_CLOSEST
6}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
deleted file mode 100644
index 2eab55f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
+++ /dev/null
@@ -1,27 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4import cuchaz.enigma.translation.representation.entry.MethodEntry;
5
6import java.util.Collection;
7import java.util.Collections;
8import java.util.Set;
9
10public enum VoidEntryResolver implements EntryResolver {
11 INSTANCE;
12
13 @Override
14 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
15 return Collections.singleton(entry);
16 }
17
18 @Override
19 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
20 return Collections.singleton(entry);
21 }
22
23 @Override
24 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
25 return Collections.singleton(methodEntry);
26 }
27}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java
deleted file mode 100644
index af92ffb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3public class EnigmaFormat {
4 public static final String COMMENT = "COMMENT";
5 public static final String CLASS = "CLASS";
6 public static final String FIELD = "FIELD";
7 public static final String METHOD = "METHOD";
8 public static final String PARAMETER = "ARG";
9}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
deleted file mode 100644
index 53bbaa3..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
+++ /dev/null
@@ -1,319 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.AccessModifier;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingPair;
9import cuchaz.enigma.translation.mapping.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
12import cuchaz.enigma.translation.representation.MethodDescriptor;
13import cuchaz.enigma.translation.representation.TypeDescriptor;
14import cuchaz.enigma.translation.representation.entry.*;
15import cuchaz.enigma.utils.I18n;
16
17import javax.annotation.Nullable;
18import java.io.IOException;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.ArrayDeque;
24import java.util.Arrays;
25import java.util.Deque;
26import java.util.List;
27import java.util.Locale;
28import java.util.stream.Collectors;
29
30public enum EnigmaMappingsReader implements MappingsReader {
31 FILE {
32 @Override
33 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
34 progress.init(1, I18n.translate("progress.mappings.enigma_file.loading"));
35
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37 readFile(path, mappings);
38
39 progress.step(1, I18n.translate("progress.mappings.enigma_file.done"));
40
41 return mappings;
42 }
43 },
44 DIRECTORY {
45 @Override
46 public EntryTree<EntryMapping> read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
47 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
48
49 List<Path> files = Files.walk(root)
50 .filter(f -> !Files.isDirectory(f))
51 .filter(f -> f.toString().endsWith(".mapping"))
52 .collect(Collectors.toList());
53
54 progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading"));
55 int step = 0;
56
57 for (Path file : files) {
58 progress.step(step++, root.relativize(file).toString());
59 if (Files.isHidden(file)) {
60 continue;
61 }
62 readFile(file, mappings);
63 }
64
65 return mappings;
66 }
67 },
68 ZIP {
69 @Override
70 public EntryTree<EntryMapping> read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
71 try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) {
72 return DIRECTORY.read(fs.getPath("/"), progress, saveParameters);
73 }
74 }
75 };
76
77 protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException {
78 List<String> lines = Files.readAllLines(path, Charsets.UTF_8);
79 Deque<MappingPair<?, RawEntryMapping>> mappingStack = new ArrayDeque<>();
80
81 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
82 String line = lines.get(lineNumber);
83 int indentation = countIndentation(line);
84
85 line = formatLine(line);
86 if (line == null) {
87 continue;
88 }
89
90 cleanMappingStack(indentation, mappingStack, mappings);
91
92 try {
93 MappingPair<?, RawEntryMapping> pair = parseLine(mappingStack.peek(), line);
94 if (pair != null) {
95 mappingStack.push(pair);
96 if (pair.getMapping() != null) {
97
98 }
99 }
100 } catch (Throwable t) {
101 t.printStackTrace();
102 throw new MappingParseException(path::toString, lineNumber, t.toString());
103 }
104 }
105
106 // Clean up rest
107 cleanMappingStack(0, mappingStack, mappings);
108 }
109
110 private void cleanMappingStack(int indentation, Deque<MappingPair<?, RawEntryMapping>> mappingStack, EntryTree<EntryMapping> mappings) {
111 while (indentation < mappingStack.size()) {
112 MappingPair<?, RawEntryMapping> pair = mappingStack.pop();
113 if (pair.getMapping() != null) {
114 mappings.insert(pair.getEntry(), pair.getMapping().bake());
115 }
116 }
117 }
118
119 @Nullable
120 private String formatLine(String line) {
121 line = stripComment(line);
122 line = line.trim();
123
124 if (line.isEmpty()) {
125 return null;
126 }
127
128 return line;
129 }
130
131 private String stripComment(String line) {
132 //Dont support comments on javadoc lines
133 if (line.trim().startsWith(EnigmaFormat.COMMENT)) {
134 return line;
135 }
136
137 int commentPos = line.indexOf('#');
138 if (commentPos >= 0) {
139 return line.substring(0, commentPos);
140 }
141 return line;
142 }
143
144 private int countIndentation(String line) {
145 int indent = 0;
146 for (int i = 0; i < line.length(); i++) {
147 if (line.charAt(i) != '\t') {
148 break;
149 }
150 indent++;
151 }
152 return indent;
153 }
154
155 private MappingPair<?, RawEntryMapping> parseLine(@Nullable MappingPair<?, RawEntryMapping> parent, String line) {
156 String[] tokens = line.trim().split("\\s");
157 String keyToken = tokens[0].toUpperCase(Locale.ROOT);
158 Entry<?> parentEntry = parent == null ? null : parent.getEntry();
159
160 switch (keyToken) {
161 case EnigmaFormat.CLASS:
162 return parseClass(parentEntry, tokens);
163 case EnigmaFormat.FIELD:
164 return parseField(parentEntry, tokens);
165 case EnigmaFormat.METHOD:
166 return parseMethod(parentEntry, tokens);
167 case EnigmaFormat.PARAMETER:
168 return parseArgument(parentEntry, tokens);
169 case EnigmaFormat.COMMENT:
170 readJavadoc(parent, tokens);
171 return null;
172 default:
173 throw new RuntimeException("Unknown token '" + keyToken + "'");
174 }
175 }
176
177 private void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) {
178 if (parent == null)
179 throw new IllegalStateException("Javadoc has no parent!");
180 // Empty string to concat
181 String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : "";
182 if (parent.getMapping() == null) {
183 parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED));
184 }
185 parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine));
186 }
187
188 private MappingPair<ClassEntry, RawEntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) {
189 String obfuscatedName = ClassEntry.getInnerName(tokens[1]);
190 ClassEntry obfuscatedEntry;
191 if (parent instanceof ClassEntry) {
192 obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName);
193 } else {
194 obfuscatedEntry = new ClassEntry(obfuscatedName);
195 }
196
197 String mapping = null;
198 AccessModifier modifier = AccessModifier.UNCHANGED;
199
200 if (tokens.length == 3) {
201 AccessModifier parsedModifier = parseModifier(tokens[2]);
202 if (parsedModifier != null) {
203 modifier = parsedModifier;
204 mapping = obfuscatedName;
205 } else {
206 mapping = tokens[2];
207 }
208 } else if (tokens.length == 4) {
209 mapping = tokens[2];
210 modifier = parseModifier(tokens[3]);
211 }
212
213 if (mapping != null) {
214 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
215 } else {
216 return new MappingPair<>(obfuscatedEntry);
217 }
218 }
219
220 private MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
221 if (!(parent instanceof ClassEntry)) {
222 throw new RuntimeException("Field must be a child of a class!");
223 }
224
225 ClassEntry ownerEntry = (ClassEntry) parent;
226
227 String obfuscatedName = tokens[1];
228 String mapping = obfuscatedName;
229 AccessModifier modifier = AccessModifier.UNCHANGED;
230 TypeDescriptor descriptor;
231
232 if (tokens.length == 3) {
233 mapping = tokens[1];
234 descriptor = new TypeDescriptor(tokens[2]);
235 } else if (tokens.length == 4) {
236 AccessModifier parsedModifier = parseModifier(tokens[3]);
237 if (parsedModifier != null) {
238 descriptor = new TypeDescriptor(tokens[2]);
239 modifier = parsedModifier;
240 } else {
241 mapping = tokens[2];
242 descriptor = new TypeDescriptor(tokens[3]);
243 }
244 } else if (tokens.length == 5) {
245 descriptor = new TypeDescriptor(tokens[3]);
246 mapping = tokens[2];
247 modifier = parseModifier(tokens[4]);
248 } else {
249 throw new RuntimeException("Invalid field declaration");
250 }
251
252 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
253 if (mapping != null) {
254 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
255 } else {
256 return new MappingPair<>(obfuscatedEntry);
257 }
258 }
259
260 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
261 if (!(parent instanceof ClassEntry)) {
262 throw new RuntimeException("Method must be a child of a class!");
263 }
264
265 ClassEntry ownerEntry = (ClassEntry) parent;
266
267 String obfuscatedName = tokens[1];
268 String mapping = null;
269 AccessModifier modifier = AccessModifier.UNCHANGED;
270 MethodDescriptor descriptor;
271
272 if (tokens.length == 3) {
273 descriptor = new MethodDescriptor(tokens[2]);
274 } else if (tokens.length == 4) {
275 AccessModifier parsedModifier = parseModifier(tokens[3]);
276 if (parsedModifier != null) {
277 modifier = parsedModifier;
278 mapping = obfuscatedName;
279 descriptor = new MethodDescriptor(tokens[2]);
280 } else {
281 mapping = tokens[2];
282 descriptor = new MethodDescriptor(tokens[3]);
283 }
284 } else if (tokens.length == 5) {
285 mapping = tokens[2];
286 modifier = parseModifier(tokens[4]);
287 descriptor = new MethodDescriptor(tokens[3]);
288 } else {
289 throw new RuntimeException("Invalid method declaration");
290 }
291
292 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
293 if (mapping != null) {
294 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
295 } else {
296 return new MappingPair<>(obfuscatedEntry);
297 }
298 }
299
300 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
301 if (!(parent instanceof MethodEntry)) {
302 throw new RuntimeException("Method arg must be a child of a method!");
303 }
304
305 MethodEntry ownerEntry = (MethodEntry) parent;
306 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null);
307 String mapping = tokens[2];
308
309 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
310 }
311
312 @Nullable
313 private AccessModifier parseModifier(String token) {
314 if (token.startsWith("ACC:")) {
315 return AccessModifier.valueOf(token.substring(4));
316 }
317 return null;
318 }
319}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
deleted file mode 100644
index be0fceb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
+++ /dev/null
@@ -1,316 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping.serde;
13
14import java.io.IOException;
15import java.io.PrintWriter;
16import java.net.URI;
17import java.net.URISyntaxException;
18import java.nio.file.DirectoryStream;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Objects;
28import java.util.concurrent.atomic.AtomicInteger;
29import java.util.stream.Collectors;
30import java.util.stream.Stream;
31
32import cuchaz.enigma.ProgressListener;
33import cuchaz.enigma.translation.MappingTranslator;
34import cuchaz.enigma.translation.Translator;
35import cuchaz.enigma.translation.mapping.AccessModifier;
36import cuchaz.enigma.translation.mapping.EntryMapping;
37import cuchaz.enigma.translation.mapping.MappingDelta;
38import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
39import cuchaz.enigma.translation.mapping.MappingSaveParameters;
40import cuchaz.enigma.translation.mapping.VoidEntryResolver;
41import cuchaz.enigma.translation.mapping.tree.EntryTree;
42import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
43import cuchaz.enigma.translation.representation.entry.ClassEntry;
44import cuchaz.enigma.translation.representation.entry.Entry;
45import cuchaz.enigma.translation.representation.entry.FieldEntry;
46import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
47import cuchaz.enigma.translation.representation.entry.MethodEntry;
48import cuchaz.enigma.utils.I18n;
49import cuchaz.enigma.utils.LFPrintWriter;
50
51public enum EnigmaMappingsWriter implements MappingsWriter {
52 FILE {
53 @Override
54 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
55 Collection<ClassEntry> classes = mappings.getRootNodes()
56 .filter(entry -> entry instanceof ClassEntry)
57 .map(entry -> (ClassEntry) entry)
58 .collect(Collectors.toList());
59
60 progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing"));
61
62 int steps = 0;
63 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
64 for (ClassEntry classEntry : classes) {
65 progress.step(steps++, classEntry.getFullName());
66 writeRoot(writer, mappings, classEntry);
67 }
68 } catch (IOException e) {
69 e.printStackTrace();
70 }
71 }
72 },
73 DIRECTORY {
74 @Override
75 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
76 Collection<ClassEntry> changedClasses = delta.getChangedRoots()
77 .filter(entry -> entry instanceof ClassEntry)
78 .map(entry -> (ClassEntry) entry)
79 .collect(Collectors.toList());
80
81 applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat());
82
83 progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing"));
84
85 AtomicInteger steps = new AtomicInteger();
86
87 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
88 changedClasses.parallelStream().forEach(classEntry -> {
89 progress.step(steps.getAndIncrement(), classEntry.getFullName());
90
91 try {
92 ClassEntry fileEntry = classEntry;
93 if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) {
94 fileEntry = translator.translate(fileEntry);
95 }
96
97 Path classPath = resolve(path, fileEntry);
98 Files.createDirectories(classPath.getParent());
99 Files.deleteIfExists(classPath);
100
101 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(classPath))) {
102 writeRoot(writer, mappings, classEntry);
103 }
104 } catch (Throwable t) {
105 System.err.println("Failed to write class '" + classEntry.getFullName() + "'");
106 t.printStackTrace();
107 }
108 });
109 }
110
111 private void applyDeletions(Path root, Collection<ClassEntry> changedClasses, EntryTree<EntryMapping> mappings, EntryTree<EntryMapping> oldMappings, MappingFileNameFormat fileNameFormat) {
112 Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE);
113
114 Stream<ClassEntry> deletedClassStream = changedClasses.stream()
115 .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e)));
116
117 if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) {
118 deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate);
119 }
120
121 Collection<ClassEntry> deletedClasses = deletedClassStream.collect(Collectors.toList());
122
123 for (ClassEntry classEntry : deletedClasses) {
124 try {
125 Files.deleteIfExists(resolve(root, classEntry));
126 } catch (IOException e) {
127 System.err.println("Failed to delete deleted class '" + classEntry + "'");
128 e.printStackTrace();
129 }
130 }
131
132 for (ClassEntry classEntry : deletedClasses) {
133 String packageName = classEntry.getPackageName();
134 if (packageName != null) {
135 Path packagePath = Paths.get(packageName);
136 try {
137 deleteDeadPackages(root, packagePath);
138 } catch (IOException e) {
139 System.err.println("Failed to delete dead package '" + packageName + "'");
140 e.printStackTrace();
141 }
142 }
143 }
144 }
145
146 private void deleteDeadPackages(Path root, Path packagePath) throws IOException {
147 for (int i = packagePath.getNameCount() - 1; i >= 0; i--) {
148 Path subPath = packagePath.subpath(0, i + 1);
149 Path packagePart = root.resolve(subPath);
150 if (isEmpty(packagePart)) {
151 Files.deleteIfExists(packagePart);
152 }
153 }
154 }
155
156 private boolean isEmpty(Path path) {
157 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
158 return !stream.iterator().hasNext();
159 } catch (IOException e) {
160 return false;
161 }
162 }
163
164 private Path resolve(Path root, ClassEntry classEntry) {
165 return root.resolve(classEntry.getFullName() + ".mapping");
166 }
167 },
168 ZIP {
169 @Override
170 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) {
171 try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) {
172 DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters);
173 } catch (IOException e) {
174 e.printStackTrace();
175 } catch (URISyntaxException e) {
176 throw new RuntimeException("Unexpected error creating URI for " + zip, e);
177 }
178 }
179 };
180
181 protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) {
182 Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry));
183
184 EntryMapping classEntryMapping = mappings.get(classEntry);
185
186 writer.println(writeClass(classEntry, classEntryMapping).trim());
187 if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) {
188 writeDocs(writer, classEntryMapping, 0);
189 }
190
191 for (Entry<?> child : children) {
192 writeEntry(writer, mappings, child, 1);
193 }
194
195 }
196
197 private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) {
198 String jd = mapping.getJavadoc();
199 if (jd != null) {
200 for (String line : jd.split("\\R")) {
201 writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1));
202 }
203 }
204 }
205
206 protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) {
207 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
208 if (node == null) {
209 return;
210 }
211
212 EntryMapping mapping = node.getValue();
213
214 if (entry instanceof ClassEntry) {
215 String line = writeClass((ClassEntry) entry, mapping);
216 writer.println(indent(line, depth));
217 } else if (entry instanceof MethodEntry) {
218 String line = writeMethod((MethodEntry) entry, mapping);
219 writer.println(indent(line, depth));
220 } else if (entry instanceof FieldEntry) {
221 String line = writeField((FieldEntry) entry, mapping);
222 writer.println(indent(line, depth));
223 } else if (entry instanceof LocalVariableEntry && mapping != null) {
224 String line = writeArgument((LocalVariableEntry) entry, mapping);
225 writer.println(indent(line, depth));
226 }
227 if (mapping != null && mapping.getJavadoc() != null) {
228 writeDocs(writer, mapping, depth);
229 }
230
231 Collection<Entry<?>> children = groupChildren(node.getChildren());
232 for (Entry<?> child : children) {
233 writeEntry(writer, mappings, child, depth + 1);
234 }
235 }
236
237 private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) {
238 Collection<Entry<?>> result = new ArrayList<>(children.size());
239
240 children.stream().filter(e -> e instanceof FieldEntry)
241 .map(e -> (FieldEntry) e)
242 .sorted()
243 .forEach(result::add);
244
245 children.stream().filter(e -> e instanceof MethodEntry)
246 .map(e -> (MethodEntry) e)
247 .sorted()
248 .forEach(result::add);
249
250 children.stream().filter(e -> e instanceof LocalVariableEntry)
251 .map(e -> (LocalVariableEntry) e)
252 .sorted()
253 .forEach(result::add);
254
255 children.stream().filter(e -> e instanceof ClassEntry)
256 .map(e -> (ClassEntry) e)
257 .sorted()
258 .forEach(result::add);
259
260 return result;
261 }
262
263 protected String writeClass(ClassEntry entry, EntryMapping mapping) {
264 StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" ");
265 builder.append(entry.getName()).append(' ');
266 writeMapping(builder, mapping);
267
268 return builder.toString();
269 }
270
271 protected String writeMethod(MethodEntry entry, EntryMapping mapping) {
272 StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " ");
273 builder.append(entry.getName()).append(' ');
274 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
275 writeMapping(builder, mapping);
276 }
277
278 builder.append(entry.getDesc().toString());
279
280 return builder.toString();
281 }
282
283 protected String writeField(FieldEntry entry, EntryMapping mapping) {
284 StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " ");
285 builder.append(entry.getName()).append(' ');
286 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
287 writeMapping(builder, mapping);
288 }
289
290 builder.append(entry.getDesc().toString());
291
292 return builder.toString();
293 }
294
295 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) {
296 return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName();
297 }
298
299 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
300 if (mapping != null) {
301 builder.append(mapping.getTargetName()).append(' ');
302 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) {
303 builder.append(mapping.getAccessModifier().getFormattedName()).append(' ');
304 }
305 }
306 }
307
308 private String indent(String line, int depth) {
309 StringBuilder builder = new StringBuilder();
310 for (int i = 0; i < depth; i++) {
311 builder.append("\t");
312 }
313 builder.append(line.trim());
314 return builder.toString();
315 }
316}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
deleted file mode 100644
index 6c8c343..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
+++ /dev/null
@@ -1,59 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingDelta;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9
10import javax.annotation.Nullable;
11import java.io.IOException;
12import java.nio.file.Path;
13
14public enum MappingFormat {
15 ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE),
16 ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY),
17 ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP),
18 TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()),
19 TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE),
20 SRG_FILE(SrgMappingsWriter.INSTANCE, null),
21 PROGUARD(null, ProguardMappingsReader.INSTANCE);
22
23
24 private final MappingsWriter writer;
25 private final MappingsReader reader;
26
27 MappingFormat(MappingsWriter writer, MappingsReader reader) {
28 this.writer = writer;
29 this.reader = reader;
30 }
31
32 public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
33 write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters);
34 }
35
36 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
37 if (writer == null) {
38 throw new IllegalStateException(name() + " does not support writing");
39 }
40 writer.write(mappings, delta, path, progressListener, saveParameters);
41 }
42
43 public EntryTree<EntryMapping> read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
44 if (reader == null) {
45 throw new IllegalStateException(name() + " does not support reading");
46 }
47 return reader.read(path, progressListener, saveParameters);
48 }
49
50 @Nullable
51 public MappingsWriter getWriter() {
52 return writer;
53 }
54
55 @Nullable
56 public MappingsReader getReader() {
57 return reader;
58 }
59}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
deleted file mode 100644
index 7c8f6cc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3public final class MappingHelper {
4 private static final String TO_ESCAPE = "\\\n\r\0\t";
5 private static final String ESCAPED = "\\nr0t";
6
7 public static String escape(String raw) {
8 StringBuilder builder = new StringBuilder(raw.length() + 1);
9 for (int i = 0; i < raw.length(); i++) {
10 final char c = raw.charAt(i);
11 final int r = TO_ESCAPE.indexOf(c);
12 if (r < 0) {
13 builder.append(c);
14 } else {
15 builder.append('\\').append(ESCAPED.charAt(r));
16 }
17 }
18 return builder.toString();
19 }
20
21 public static String unescape(String str) {
22 int pos = str.indexOf('\\');
23 if (pos < 0) return str;
24
25 StringBuilder ret = new StringBuilder(str.length() - 1);
26 int start = 0;
27
28 do {
29 ret.append(str, start, pos);
30 pos++;
31 int type;
32
33 if (pos >= str.length()) {
34 throw new RuntimeException("incomplete escape sequence at the end");
35 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
36 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
37 } else {
38 ret.append(TO_ESCAPE.charAt(type));
39 }
40
41 start = pos + 1;
42 } while ((pos = str.indexOf('\\', start)) >= 0);
43
44 ret.append(str, start, str.length());
45
46 return ret.toString();
47 }
48
49 private MappingHelper() {
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
deleted file mode 100644
index 4c60787..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
+++ /dev/null
@@ -1,14 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import java.io.IOException;
10import java.nio.file.Path;
11
12public interface MappingsReader {
13 EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException;
14}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
deleted file mode 100644
index 8815986..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
+++ /dev/null
@@ -1,17 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingDelta;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import java.nio.file.Path;
10
11public interface MappingsWriter {
12 void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters);
13
14 default void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
15 write(mappings, MappingDelta.added(mappings), path, progress, saveParameters);
16 }
17}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java
deleted file mode 100644
index b5ede39..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java
+++ /dev/null
@@ -1,134 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.command.MappingCommandsUtil;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15
16import java.io.IOException;
17import java.nio.charset.StandardCharsets;
18import java.nio.file.Files;
19import java.nio.file.Path;
20import java.util.regex.Matcher;
21import java.util.regex.Pattern;
22
23public class ProguardMappingsReader implements MappingsReader {
24 public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader();
25 private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+";
26 private static final String TYPE = NAME + "(?:\\[])*";
27 private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")";
28 private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):");
29 private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")");
30 private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")");
31
32 public ProguardMappingsReader() {}
33
34 @Override
35 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37
38 int lineNumber = 0;
39 ClassEntry currentClass = null;
40 for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) {
41 lineNumber++;
42
43 if (line.startsWith("#") || line.isEmpty()) {
44 continue;
45 }
46
47 Matcher classMatcher = CLASS.matcher(line);
48 Matcher fieldMatcher = FIELD.matcher(line);
49 Matcher methodMatcher = METHOD.matcher(line);
50
51 if (classMatcher.matches()) {
52 String name = classMatcher.group(1);
53 String targetName = classMatcher.group(2);
54
55 mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/'))));
56 } else if (fieldMatcher.matches()) {
57 String type = fieldMatcher.group(1);
58 String name = fieldMatcher.group(2);
59 String targetName = fieldMatcher.group(3);
60
61 if (currentClass == null) {
62 throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line);
63 }
64
65 mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName));
66 } else if (methodMatcher.matches()) {
67 String returnType = methodMatcher.group(1);
68 String name = methodMatcher.group(2);
69 String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(",");
70 String targetName = methodMatcher.group(4);
71
72 if (currentClass == null) {
73 throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line);
74 }
75
76 mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName));
77 } else {
78 throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line);
79 }
80 }
81
82 return MappingCommandsUtil.invert(mappings);
83 }
84
85 private String getDescriptor(String type) {
86 StringBuilder descriptor = new StringBuilder();
87
88 while (type.endsWith("[]")) {
89 descriptor.append("[");
90 type = type.substring(0, type.length() - 2);
91 }
92
93 switch (type) {
94 case "byte":
95 return descriptor + "B";
96 case "char":
97 return descriptor + "C";
98 case "short":
99 return descriptor + "S";
100 case "int":
101 return descriptor + "I";
102 case "long":
103 return descriptor + "J";
104 case "float":
105 return descriptor + "F";
106 case "double":
107 return descriptor + "D";
108 case "boolean":
109 return descriptor + "Z";
110 case "void":
111 return descriptor + "V";
112 }
113
114 descriptor.append("L");
115 descriptor.append(type.replace('.', '/'));
116 descriptor.append(";");
117
118 return descriptor.toString();
119 }
120
121 private String getDescriptor(String returnType, String[] parameterTypes) {
122 StringBuilder descriptor = new StringBuilder();
123 descriptor.append('(');
124
125 for (String parameterType : parameterTypes) {
126 descriptor.append(getDescriptor(parameterType));
127 }
128
129 descriptor.append(')');
130 descriptor.append(getDescriptor(returnType));
131
132 return descriptor.toString();
133 }
134}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
deleted file mode 100644
index afb40e9..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
+++ /dev/null
@@ -1,30 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.translation.mapping.AccessModifier;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5
6import java.util.ArrayList;
7import java.util.List;
8
9final class RawEntryMapping {
10 private final String targetName;
11 private final AccessModifier access;
12 private List<String> javadocs = new ArrayList<>();
13
14 RawEntryMapping(String targetName) {
15 this(targetName, null);
16 }
17
18 RawEntryMapping(String targetName, AccessModifier access) {
19 this.access = access;
20 this.targetName = targetName;
21 }
22
23 void addJavadocLine(String line) {
24 javadocs.add(line);
25 }
26
27 EntryMapping bake() {
28 return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs));
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
deleted file mode 100644
index f67f8fc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
+++ /dev/null
@@ -1,118 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.MappingTranslator;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingDelta;
9import cuchaz.enigma.translation.mapping.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.VoidEntryResolver;
11import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.FieldEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18import cuchaz.enigma.utils.LFPrintWriter;
19
20import java.io.IOException;
21import java.io.PrintWriter;
22import java.nio.file.Files;
23import java.nio.file.Path;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Comparator;
27import java.util.List;
28import java.util.stream.Collectors;
29
30public enum SrgMappingsWriter implements MappingsWriter {
31 INSTANCE;
32
33 @Override
34 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
35 try {
36 Files.deleteIfExists(path);
37 Files.createFile(path);
38 } catch (IOException e) {
39 e.printStackTrace();
40 }
41
42 List<String> classLines = new ArrayList<>();
43 List<String> fieldLines = new ArrayList<>();
44 List<String> methodLines = new ArrayList<>();
45
46 Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream()
47 .map(EntryTreeNode::getEntry)
48 .collect(Collectors.toList());
49 progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating"));
50
51 int steps = 0;
52 for (Entry<?> entry : sorted(rootEntries)) {
53 progress.step(steps++, entry.getName());
54 writeEntry(classLines, fieldLines, methodLines, mappings, entry);
55 }
56
57 progress.init(3, I18n.translate("progress.mappings.srg_file.writing"));
58 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
59 progress.step(0, I18n.translate("type.classes"));
60 classLines.forEach(writer::println);
61 progress.step(1, I18n.translate("type.fields"));
62 fieldLines.forEach(writer::println);
63 progress.step(2, I18n.translate("type.methods"));
64 methodLines.forEach(writer::println);
65 } catch (IOException e) {
66 e.printStackTrace();
67 }
68 }
69
70 private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) {
71 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
72 if (node == null) {
73 return;
74 }
75
76 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
77 if (entry instanceof ClassEntry) {
78 classes.add(generateClassLine((ClassEntry) entry, translator));
79 } else if (entry instanceof FieldEntry) {
80 fields.add(generateFieldLine((FieldEntry) entry, translator));
81 } else if (entry instanceof MethodEntry) {
82 methods.add(generateMethodLine((MethodEntry) entry, translator));
83 }
84
85 for (Entry<?> child : sorted(node.getChildren())) {
86 writeEntry(classes, fields, methods, mappings, child);
87 }
88 }
89
90 private String generateClassLine(ClassEntry sourceEntry, Translator translator) {
91 ClassEntry targetEntry = translator.translate(sourceEntry);
92 return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName();
93 }
94
95 private String generateMethodLine(MethodEntry sourceEntry, Translator translator) {
96 MethodEntry targetEntry = translator.translate(sourceEntry);
97 return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry);
98 }
99
100 private String describeMethod(MethodEntry entry) {
101 return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc();
102 }
103
104 private String generateFieldLine(FieldEntry sourceEntry, Translator translator) {
105 FieldEntry targetEntry = translator.translate(sourceEntry);
106 return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry);
107 }
108
109 private String describeField(FieldEntry entry) {
110 return entry.getParent().getFullName() + "/" + entry.getName();
111 }
112
113 private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) {
114 ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable);
115 sorted.sort(Comparator.comparing(Entry::getName));
116 return sorted;
117 }
118}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
deleted file mode 100644
index 773c95e..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
+++ /dev/null
@@ -1,115 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingPair;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
11import cuchaz.enigma.translation.representation.MethodDescriptor;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18
19import java.io.IOException;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.List;
23
24public enum TinyMappingsReader implements MappingsReader {
25 INSTANCE;
26
27 @Override
28 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
29 return read(path, Files.readAllLines(path, Charsets.UTF_8), progress);
30 }
31
32 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
33 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
34 lines.remove(0);
35
36 progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading"));
37
38 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
39 progress.step(lineNumber, "");
40
41 String line = lines.get(lineNumber);
42
43 if (line.trim().startsWith("#")) {
44 continue;
45 }
46
47 try {
48 MappingPair<?, EntryMapping> mapping = parseLine(line);
49 mappings.insert(mapping.getEntry(), mapping.getMapping());
50 } catch (Throwable t) {
51 t.printStackTrace();
52 throw new MappingParseException(path::toString, lineNumber, t.toString());
53 }
54 }
55
56 return mappings;
57 }
58
59 private MappingPair<?, EntryMapping> parseLine(String line) {
60 String[] tokens = line.split("\t");
61
62 String key = tokens[0];
63 switch (key) {
64 case "CLASS":
65 return parseClass(tokens);
66 case "FIELD":
67 return parseField(tokens);
68 case "METHOD":
69 return parseMethod(tokens);
70 case "MTH-ARG":
71 return parseArgument(tokens);
72 default:
73 throw new RuntimeException("Unknown token '" + key + "'!");
74 }
75 }
76
77 private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) {
78 ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]);
79 String mapping = tokens[2];
80 if (mapping.indexOf('$') > 0) {
81 // inner classes should map to only the final part
82 mapping = mapping.substring(mapping.lastIndexOf('$') + 1);
83 }
84 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
85 }
86
87 private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) {
88 ClassEntry ownerClass = new ClassEntry(tokens[1]);
89 TypeDescriptor descriptor = new TypeDescriptor(tokens[2]);
90
91 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor);
92 String mapping = tokens[4];
93 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
94 }
95
96 private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) {
97 ClassEntry ownerClass = new ClassEntry(tokens[1]);
98 MethodDescriptor descriptor = new MethodDescriptor(tokens[2]);
99
100 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor);
101 String mapping = tokens[4];
102 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
103 }
104
105 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) {
106 ClassEntry ownerClass = new ClassEntry(tokens[1]);
107 MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]);
108 MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor);
109 int variableIndex = Integer.parseInt(tokens[4]);
110
111 String mapping = tokens[5];
112 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
113 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
114 }
115}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java
deleted file mode 100644
index c82f262..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java
+++ /dev/null
@@ -1,148 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Joiner;
4import com.google.common.collect.Lists;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.translation.MappingTranslator;
7import cuchaz.enigma.translation.Translator;
8import cuchaz.enigma.translation.mapping.EntryMapping;
9import cuchaz.enigma.translation.mapping.MappingDelta;
10import cuchaz.enigma.translation.mapping.MappingSaveParameters;
11import cuchaz.enigma.translation.mapping.VoidEntryResolver;
12import cuchaz.enigma.translation.mapping.tree.EntryTree;
13import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18
19import java.io.BufferedWriter;
20import java.io.IOException;
21import java.io.Writer;
22import java.nio.charset.StandardCharsets;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.util.Comparator;
26import java.util.HashSet;
27import java.util.Set;
28
29public class TinyMappingsWriter implements MappingsWriter {
30 private static final String VERSION_CONSTANT = "v1";
31 private static final Joiner TAB_JOINER = Joiner.on('\t');
32
33 //Possibly add a gui or a way to select the namespaces when exporting from the gui
34 public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named");
35
36 // HACK: as of enigma 0.13.1, some fields seem to appear duplicated?
37 private final Set<String> writtenLines = new HashSet<>();
38 private final String nameObf;
39 private final String nameDeobf;
40
41 public TinyMappingsWriter(String nameObf, String nameDeobf) {
42 this.nameObf = nameObf;
43 this.nameDeobf = nameDeobf;
44 }
45
46 @Override
47 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
48 try {
49 Files.deleteIfExists(path);
50 Files.createFile(path);
51 } catch (IOException e) {
52 e.printStackTrace();
53 }
54
55 try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
56 writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf});
57
58 Lists.newArrayList(mappings).stream()
59 .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString))
60 .forEach(entry -> writeEntry(writer, mappings, entry));
61 } catch (IOException e) {
62 e.printStackTrace();
63 }
64 }
65
66 private void writeEntry(Writer writer, EntryTree<EntryMapping> mappings, Entry<?> entry) {
67 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
68 if (node == null) {
69 return;
70 }
71
72 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
73
74 EntryMapping mapping = mappings.get(entry);
75 if (mapping != null && !entry.getName().equals(mapping.getTargetName())) {
76 if (entry instanceof ClassEntry) {
77 writeClass(writer, (ClassEntry) entry, translator);
78 } else if (entry instanceof FieldEntry) {
79 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
80 } else if (entry instanceof MethodEntry) {
81 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
82 }
83 }
84
85 writeChildren(writer, mappings, node);
86 }
87
88 private void writeChildren(Writer writer, EntryTree<EntryMapping> mappings, EntryTreeNode<EntryMapping> node) {
89 node.getChildren().stream()
90 .filter(e -> e instanceof FieldEntry).sorted()
91 .forEach(child -> writeEntry(writer, mappings, child));
92
93 node.getChildren().stream()
94 .filter(e -> e instanceof MethodEntry).sorted()
95 .forEach(child -> writeEntry(writer, mappings, child));
96
97 node.getChildren().stream()
98 .filter(e -> e instanceof ClassEntry).sorted()
99 .forEach(child -> writeEntry(writer, mappings, child));
100 }
101
102 private void writeClass(Writer writer, ClassEntry entry, Translator translator) {
103 ClassEntry translatedEntry = translator.translate(entry);
104
105 String obfClassName = entry.getFullName();
106 String deobfClassName = translatedEntry.getFullName();
107 writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName});
108 }
109
110 private void writeLine(Writer writer, String[] data) {
111 try {
112 String line = TAB_JOINER.join(data) + "\n";
113 if (writtenLines.add(line)) {
114 writer.write(line);
115 }
116 } catch (IOException e) {
117 throw new RuntimeException(e);
118 }
119 }
120
121 private String[] serializeEntry(Entry<?> entry, String... extraFields) {
122 String[] data = null;
123
124 if (entry instanceof FieldEntry) {
125 data = new String[4 + extraFields.length];
126 data[0] = "FIELD";
127 data[1] = entry.getContainingClass().getFullName();
128 data[2] = ((FieldEntry) entry).getDesc().toString();
129 data[3] = entry.getName();
130 } else if (entry instanceof MethodEntry) {
131 data = new String[4 + extraFields.length];
132 data[0] = "METHOD";
133 data[1] = entry.getContainingClass().getFullName();
134 data[2] = ((MethodEntry) entry).getDesc().toString();
135 data[3] = entry.getName();
136 } else if (entry instanceof ClassEntry) {
137 data = new String[2 + extraFields.length];
138 data[0] = "CLASS";
139 data[1] = ((ClassEntry) entry).getFullName();
140 }
141
142 if (data != null) {
143 System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length);
144 }
145
146 return data;
147 }
148}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java
deleted file mode 100644
index d81cbdb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java
+++ /dev/null
@@ -1,295 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingPair;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18
19import java.io.IOException;
20import java.nio.charset.StandardCharsets;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.BitSet;
24import java.util.List;
25
26final class TinyV2Reader implements MappingsReader {
27
28 private static final String MINOR_VERSION = "0";
29 // 0 indent
30 private static final int IN_HEADER = 0;
31 private static final int IN_CLASS = IN_HEADER + 1;
32 // 1 indent
33 private static final int IN_METHOD = IN_CLASS + 1;
34 private static final int IN_FIELD = IN_METHOD + 1;
35 // 2 indent
36 private static final int IN_PARAMETER = IN_FIELD + 1;
37 // general properties
38 private static final int STATE_SIZE = IN_PARAMETER + 1;
39 private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE};
40
41 @Override
42 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
43 return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress);
44 }
45
46 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
47 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
48
49 progress.init(lines.size(), I18n.translate("progress.mappings.tiny_v2.loading"));
50
51 BitSet state = new BitSet(STATE_SIZE);
52 @SuppressWarnings({"unchecked", "rawtypes"})
53 MappingPair<? extends Entry<?>, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE];
54 boolean escapeNames = false;
55
56 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
57 try {
58 progress.step(lineNumber, "");
59 String line = lines.get(lineNumber);
60
61 int indent = 0;
62 while (line.charAt(indent) == '\t')
63 indent++;
64
65 String[] parts = line.substring(indent).split("\t", -1);
66 if (parts.length == 0 || indent >= INDENT_CLEAR_START.length)
67 throw new IllegalArgumentException("Invalid format");
68
69 // clean and register stuff in stack
70 for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) {
71 state.clear(i);
72 if (holds[i] != null) {
73 RawEntryMapping mapping = holds[i].getMapping();
74 if (mapping != null) {
75 EntryMapping baked = mapping.bake();
76 if (baked != null) {
77 mappings.insert(holds[i].getEntry(), baked);
78 }
79 }
80 holds[i] = null;
81 }
82 }
83
84 switch (indent) {
85 case 0:
86 switch (parts[0]) {
87 case "tiny": // header
88 if (lineNumber != 0) {
89 throw new IllegalArgumentException("Header can only be on the first line");
90 }
91 if (parts.length < 5) {
92 throw new IllegalArgumentException("Not enough header columns, needs at least 5");
93 }
94 if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) {
95 throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + "");
96 }
97 state.set(IN_HEADER);
98 break;
99 case "c": // class
100 state.set(IN_CLASS);
101 holds[IN_CLASS] = parseClass(parts, escapeNames);
102 break;
103 default:
104 unsupportKey(parts);
105 }
106
107 break;
108 case 1:
109 if (state.get(IN_HEADER)) {
110 if (parts[0].equals("esacpe-names")) {
111 escapeNames = true;
112 }
113
114 break;
115 }
116
117 if (state.get(IN_CLASS)) {
118 switch (parts[0]) {
119 case "m": // method
120 state.set(IN_METHOD);
121 holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames);
122 break;
123 case "f": // field
124 state.set(IN_FIELD);
125 holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames);
126 break;
127 case "c": // class javadoc
128 addJavadoc(holds[IN_CLASS], parts);
129 break;
130 default:
131 unsupportKey(parts);
132 }
133 break;
134 }
135
136 unsupportKey(parts);
137 case 2:
138 if (state.get(IN_METHOD)) {
139 switch (parts[0]) {
140 case "p": // parameter
141 state.set(IN_PARAMETER);
142 holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames);
143 break;
144 case "v": // local variable
145 // TODO add local var mapping
146 break;
147 case "c": // method javadoc
148 addJavadoc(holds[IN_METHOD], parts);
149 break;
150 default:
151 unsupportKey(parts);
152 }
153 break;
154 }
155
156 if (state.get(IN_FIELD)) {
157 switch (parts[0]) {
158 case "c": // field javadoc
159 addJavadoc(holds[IN_FIELD], parts);
160 break;
161 default:
162 unsupportKey(parts);
163 }
164 break;
165 }
166 unsupportKey(parts);
167 case 3:
168 if (state.get(IN_PARAMETER)) {
169 switch (parts[0]) {
170 case "c":
171 addJavadoc(holds[IN_PARAMETER], parts);
172 break;
173 default:
174 unsupportKey(parts);
175 }
176 break;
177 }
178 unsupportKey(parts);
179 default:
180 unsupportKey(parts);
181 }
182
183 } catch (Throwable t) {
184 t.printStackTrace();
185 throw new MappingParseException(path::toString, lineNumber + 1, t.toString());
186 }
187 }
188
189 return mappings;
190 }
191
192 private void unsupportKey(String[] parts) {
193 throw new IllegalArgumentException("Unsupported key " + parts[0]);
194 }
195
196 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String[] parts) {
197 if (parts.length != 2) {
198 throw new IllegalArgumentException("Invalid javadoc declaration");
199 }
200
201 addJavadoc(pair, parts[1]);
202 }
203
204 private MappingPair<ClassEntry, RawEntryMapping> parseClass(String[] tokens, boolean escapeNames) {
205 ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames));
206 if (tokens.length <= 2)
207 return new MappingPair<>(obfuscatedEntry);
208 String token2 = unescapeOpt(tokens[2], escapeNames);
209 String mapping = token2.substring(token2.lastIndexOf('$') + 1);
210 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
211 }
212
213 private MappingPair<FieldEntry, RawEntryMapping> parseField(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
214 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
215 TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames));
216
217 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
218 if (tokens.length <= 3)
219 return new MappingPair<>(obfuscatedEntry);
220 String mapping = unescapeOpt(tokens[3], escapeNames);
221 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
222 }
223
224 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
225 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
226 MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames));
227
228 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
229 if (tokens.length <= 3)
230 return new MappingPair<>(obfuscatedEntry);
231 String mapping = unescapeOpt(tokens[3], escapeNames);
232 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
233 }
234
235
236
237 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String javadoc) {
238 RawEntryMapping mapping = pair.getMapping();
239 if (mapping == null) {
240 throw new IllegalArgumentException("Javadoc requires a mapping in enigma!");
241 }
242 mapping.addJavadocLine(unescape(javadoc));
243 }
244
245
246
247 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
248 MethodEntry ownerMethod = (MethodEntry) parent.getEntry();
249 int variableIndex = Integer.parseInt(tokens[1]);
250
251 // tokens[2] is the useless obf name
252
253 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
254 if (tokens.length <= 3)
255 return new MappingPair<>(obfuscatedEntry);
256 String mapping = unescapeOpt(tokens[3], escapeNames);
257 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
258 }
259
260 private static final String TO_ESCAPE = "\\\n\r\0\t";
261 private static final String ESCAPED = "\\nr0t";
262
263 private static String unescapeOpt(String raw, boolean escapedStrings) {
264 return escapedStrings ? unescape(raw) : raw;
265 }
266
267 private static String unescape(String str) {
268 // copied from matcher, lazy!
269 int pos = str.indexOf('\\');
270 if (pos < 0) return str;
271
272 StringBuilder ret = new StringBuilder(str.length() - 1);
273 int start = 0;
274
275 do {
276 ret.append(str, start, pos);
277 pos++;
278 int type;
279
280 if (pos >= str.length()) {
281 throw new RuntimeException("incomplete escape sequence at the end");
282 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
283 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
284 } else {
285 ret.append(TO_ESCAPE.charAt(type));
286 }
287
288 start = pos + 1;
289 } while ((pos = str.indexOf('\\', start)) >= 0);
290
291 ret.append(str, start, str.length());
292
293 return ret.toString();
294 }
295}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java
deleted file mode 100644
index 95e04c3..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java
+++ /dev/null
@@ -1,169 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Strings;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.Entry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16import cuchaz.enigma.utils.LFPrintWriter;
17
18import java.io.IOException;
19import java.io.PrintWriter;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.Deque;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.stream.Collectors;
26import java.util.stream.StreamSupport;
27
28public final class TinyV2Writer implements MappingsWriter {
29
30 private static final String MINOR_VERSION = "0";
31 private final String obfHeader;
32 private final String deobfHeader;
33
34 public TinyV2Writer(String obfHeader, String deobfHeader) {
35 this.obfHeader = obfHeader;
36 this.deobfHeader = deobfHeader;
37 }
38
39 @Override
40 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters parameters) {
41 List<EntryTreeNode<EntryMapping>> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList());
42
43 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
44 writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader);
45
46 // no escape names
47
48 for (EntryTreeNode<EntryMapping> node : classes) {
49 writeClass(writer, node, mappings);
50 }
51 } catch (IOException ex) {
52 ex.printStackTrace(); // TODO add some better logging system
53 }
54 }
55
56 private void writeClass(PrintWriter writer, EntryTreeNode<EntryMapping> node, EntryMap<EntryMapping> tree) {
57 writer.print("c\t");
58 ClassEntry classEntry = (ClassEntry) node.getEntry();
59 String fullName = classEntry.getFullName();
60 writer.print(fullName);
61 Deque<String> parts = new LinkedList<>();
62 do {
63 EntryMapping mapping = tree.get(classEntry);
64 if (mapping != null) {
65 parts.addFirst(mapping.getTargetName());
66 } else {
67 parts.addFirst(classEntry.getName());
68 }
69 classEntry = classEntry.getOuterClass();
70 } while (classEntry != null);
71
72 String mappedName = String.join("$", parts);
73
74 writer.print("\t");
75
76 writer.print(mappedName); // todo escaping when we have v2 fixed later
77
78 writer.println();
79
80 writeComment(writer, node.getValue(), 1);
81
82 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
83 Entry entry = child.getEntry();
84 if (entry instanceof FieldEntry) {
85 writeField(writer, child);
86 } else if (entry instanceof MethodEntry) {
87 writeMethod(writer, child);
88 }
89 }
90 }
91
92 private void writeMethod(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
93 writer.print(indent(1));
94 writer.print("m\t");
95 writer.print(((MethodEntry) node.getEntry()).getDesc().toString());
96 writer.print("\t");
97 writer.print(node.getEntry().getName());
98 writer.print("\t");
99 EntryMapping mapping = node.getValue();
100 if (mapping == null) {
101 writer.println(node.getEntry().getName()); // todo fix v2 name inference
102 } else {
103 writer.println(mapping.getTargetName());
104
105 writeComment(writer, mapping, 2);
106 }
107
108 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
109 Entry entry = child.getEntry();
110 if (entry instanceof LocalVariableEntry) {
111 writeParameter(writer, child);
112 }
113 // TODO write actual local variables
114 }
115 }
116
117 private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
118 if (node.getValue() == null)
119 return; // Shortcut
120
121 writer.print(indent(1));
122 writer.print("f\t");
123 writer.print(((FieldEntry) node.getEntry()).getDesc().toString());
124 writer.print("\t");
125 writer.print(node.getEntry().getName());
126 writer.print("\t");
127 EntryMapping mapping = node.getValue();
128 if (mapping == null) {
129 writer.println(node.getEntry().getName()); // todo fix v2 name inference
130 } else {
131 writer.println(mapping.getTargetName());
132
133 writeComment(writer, mapping, 2);
134 }
135 }
136
137 private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
138 if (node.getValue() == null)
139 return; // Shortcut
140
141 writer.print(indent(2));
142 writer.print("p\t");
143 writer.print(((LocalVariableEntry) node.getEntry()).getIndex());
144 writer.print("\t");
145 writer.print(node.getEntry().getName());
146 writer.print("\t");
147 EntryMapping mapping = node.getValue();
148 if (mapping == null) {
149 writer.println(); // todo ???
150 } else {
151 writer.println(mapping.getTargetName());
152
153 writeComment(writer, mapping, 3);
154 }
155 }
156
157 private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) {
158 if (mapping != null && mapping.getJavadoc() != null) {
159 writer.print(indent(indent));
160 writer.print("c\t");
161 writer.print(MappingHelper.escape(mapping.getJavadoc()));
162 writer.println();
163 }
164 }
165
166 private String indent(int level) {
167 return Strings.repeat("\t", level);
168 }
169}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
deleted file mode 100644
index 255fa5f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
+++ /dev/null
@@ -1,110 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.stream.Stream;
14
15public class DeltaTrackingTree<T> implements EntryTree<T> {
16 private final EntryTree<T> delegate;
17
18 private EntryTree<T> deltaReference;
19 private EntryTree<Object> changes = new HashEntryTree<>();
20
21 public DeltaTrackingTree(EntryTree<T> delegate) {
22 this.delegate = delegate;
23 this.deltaReference = new HashEntryTree<>(delegate);
24 }
25
26 public DeltaTrackingTree() {
27 this(new HashEntryTree<>());
28 }
29
30 @Override
31 public void insert(Entry<?> entry, T value) {
32 trackChange(entry);
33 delegate.insert(entry, value);
34 }
35
36 @Nullable
37 @Override
38 public T remove(Entry<?> entry) {
39 trackChange(entry);
40 return delegate.remove(entry);
41 }
42
43 public void trackChange(Entry<?> entry) {
44 changes.insert(entry, MappingDelta.PLACEHOLDER);
45 }
46
47 @Nullable
48 @Override
49 public T get(Entry<?> entry) {
50 return delegate.get(entry);
51 }
52
53 @Override
54 public Collection<Entry<?>> getChildren(Entry<?> entry) {
55 return delegate.getChildren(entry);
56 }
57
58 @Override
59 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
60 return delegate.getSiblings(entry);
61 }
62
63 @Nullable
64 @Override
65 public EntryTreeNode<T> findNode(Entry<?> entry) {
66 return delegate.findNode(entry);
67 }
68
69 @Override
70 public Stream<EntryTreeNode<T>> getRootNodes() {
71 return delegate.getRootNodes();
72 }
73
74 @Override
75 public DeltaTrackingTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
76 DeltaTrackingTree<T> translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings));
77 translatedTree.changes = changes.translate(translator, resolver, mappings);
78 return translatedTree;
79 }
80
81 @Override
82 public Stream<Entry<?>> getAllEntries() {
83 return delegate.getAllEntries();
84 }
85
86 @Override
87 public boolean isEmpty() {
88 return delegate.isEmpty();
89 }
90
91 @Override
92 public Iterator<EntryTreeNode<T>> iterator() {
93 return delegate.iterator();
94 }
95
96 public MappingDelta<T> takeDelta() {
97 MappingDelta<T> delta = new MappingDelta<>(deltaReference, changes);
98 resetDelta();
99 return delta;
100 }
101
102 private void resetDelta() {
103 deltaReference = new HashEntryTree<>(delegate);
104 changes = new HashEntryTree<>();
105 }
106
107 public boolean isDirty() {
108 return !changes.isEmpty();
109 }
110}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
deleted file mode 100644
index daaefcc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
+++ /dev/null
@@ -1,26 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.stream.Stream;
13
14public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>>, Translatable {
15 Collection<Entry<?>> getChildren(Entry<?> entry);
16
17 Collection<Entry<?>> getSiblings(Entry<?> entry);
18
19 @Nullable
20 EntryTreeNode<T> findNode(Entry<?> entry);
21
22 Stream<EntryTreeNode<T>> getRootNodes();
23
24 @Override
25 EntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
26}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
deleted file mode 100644
index affcd50..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.stream.Collectors;
9
10public interface EntryTreeNode<T> {
11 @Nullable
12 T getValue();
13
14 Entry<?> getEntry();
15
16 boolean isEmpty();
17
18 Collection<Entry<?>> getChildren();
19
20 Collection<? extends EntryTreeNode<T>> getChildNodes();
21
22 default Collection<? extends EntryTreeNode<T>> getNodesRecursively() {
23 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
24 nodes.add(this);
25 for (EntryTreeNode<T> node : getChildNodes()) {
26 nodes.addAll(node.getNodesRecursively());
27 }
28 return nodes;
29 }
30
31 default Collection<Entry<?>> getChildrenRecursively() {
32 return getNodesRecursively().stream()
33 .map(EntryTreeNode::getEntry)
34 .collect(Collectors.toList());
35 }
36
37 default boolean hasValue() {
38 return getValue() != null;
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
deleted file mode 100644
index 570941c..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
+++ /dev/null
@@ -1,188 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9import javax.annotation.Nullable;
10import java.util.*;
11import java.util.function.Function;
12import java.util.stream.Stream;
13import java.util.stream.StreamSupport;
14
15public class HashEntryTree<T> implements EntryTree<T> {
16 private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>();
17
18 public HashEntryTree() {
19 }
20
21 public HashEntryTree(EntryTree<T> tree) {
22 for (EntryTreeNode<T> node : tree) {
23 insert(node.getEntry(), node.getValue());
24 }
25 }
26
27 @Override
28 public void insert(Entry<?> entry, T value) {
29 List<HashTreeNode<T>> path = computePath(entry, true);
30 path.get(path.size() - 1).putValue(value);
31 if (value == null) {
32 removeDeadAlong(path);
33 }
34 }
35
36 @Override
37 @Nullable
38 public T remove(Entry<?> entry) {
39 List<HashTreeNode<T>> path = computePath(entry, false);
40 if (path.isEmpty()) {
41 return null;
42 }
43
44 T value = path.get(path.size() - 1).removeValue();
45
46 removeDeadAlong(path);
47
48 return value;
49 }
50
51 @Override
52 @Nullable
53 public T get(Entry<?> entry) {
54 HashTreeNode<T> node = findNode(entry);
55 if (node == null) {
56 return null;
57 }
58 return node.getValue();
59 }
60
61 @Override
62 public boolean contains(Entry<?> entry) {
63 return get(entry) != null;
64 }
65
66 @Override
67 public Collection<Entry<?>> getChildren(Entry<?> entry) {
68 HashTreeNode<T> leaf = findNode(entry);
69 if (leaf == null) {
70 return Collections.emptyList();
71 }
72 return leaf.getChildren();
73 }
74
75 @Override
76 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
77 Entry<?> parent = entry.getParent();
78 if (parent == null) {
79 return getSiblings(entry, root.keySet());
80 }
81 return getSiblings(entry, getChildren(parent));
82 }
83
84 private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> generation) {
85 Set<Entry<?>> siblings = new HashSet<>(generation);
86 siblings.remove(entry);
87 return siblings;
88 }
89
90 @Override
91 @Nullable
92 public HashTreeNode<T> findNode(Entry<?> target) {
93 List<Entry<?>> parentChain = target.getAncestry();
94 if (parentChain.isEmpty()) {
95 return null;
96 }
97
98 HashTreeNode<T> node = root.get(parentChain.get(0));
99 for (int i = 1; i < parentChain.size(); i++) {
100 if (node == null) {
101 return null;
102 }
103 node = node.getChild(parentChain.get(i));
104 }
105
106 return node;
107 }
108
109 private List<HashTreeNode<T>> computePath(Entry<?> target, boolean make) {
110 List<Entry<?>> ancestry = target.getAncestry();
111 if (ancestry.isEmpty()) {
112 return Collections.emptyList();
113 }
114
115 List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size());
116
117 Entry<?> rootEntry = ancestry.get(0);
118 HashTreeNode<T> node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry);
119 if (node == null) {
120 return Collections.emptyList();
121 }
122
123 path.add(node);
124
125 for (int i = 1; i < ancestry.size(); i++) {
126 Entry<?> ancestor = ancestry.get(i);
127 node = make ? node.computeChild(ancestor) : node.getChild(ancestor);
128 if (node == null) {
129 return Collections.emptyList();
130 }
131
132 path.add(node);
133 }
134
135 return path;
136 }
137
138 private void removeDeadAlong(List<HashTreeNode<T>> path) {
139 for (int i = path.size() - 1; i >= 0; i--) {
140 HashTreeNode<T> node = path.get(i);
141 if (node.isEmpty()) {
142 if (i > 0) {
143 HashTreeNode<T> parentNode = path.get(i - 1);
144 parentNode.remove(node.getEntry());
145 } else {
146 root.remove(node.getEntry());
147 }
148 } else {
149 break;
150 }
151 }
152 }
153
154 @Override
155 public Iterator<EntryTreeNode<T>> iterator() {
156 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
157 for (EntryTreeNode<T> node : root.values()) {
158 nodes.addAll(node.getNodesRecursively());
159 }
160 return nodes.iterator();
161 }
162
163 @Override
164 public Stream<Entry<?>> getAllEntries() {
165 return StreamSupport.stream(spliterator(), false)
166 .filter(EntryTreeNode::hasValue)
167 .map(EntryTreeNode::getEntry);
168 }
169
170 @Override
171 public Stream<EntryTreeNode<T>> getRootNodes() {
172 return root.values().stream().map(Function.identity());
173 }
174
175 @Override
176 public boolean isEmpty() {
177 return root.isEmpty();
178 }
179
180 @Override
181 public HashEntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
182 HashEntryTree<T> translatedTree = new HashEntryTree<>();
183 for (EntryTreeNode<T> node : this) {
184 translatedTree.insert(translator.translate(node.getEntry()), node.getValue());
185 }
186 return translatedTree;
187 }
188}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
deleted file mode 100644
index 0a990bd..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
+++ /dev/null
@@ -1,75 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nonnull;
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.Iterator;
10import java.util.Map;
11
12public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> {
13 private final Entry<?> entry;
14 private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>();
15 private T value;
16
17 HashTreeNode(Entry<?> entry) {
18 this.entry = entry;
19 }
20
21 void putValue(T value) {
22 this.value = value;
23 }
24
25 T removeValue() {
26 T value = this.value;
27 this.value = null;
28 return value;
29 }
30
31 @Nullable
32 HashTreeNode<T> getChild(Entry<?> entry) {
33 return children.get(entry);
34 }
35
36 @Nonnull
37 HashTreeNode<T> computeChild(Entry<?> entry) {
38 return children.computeIfAbsent(entry, HashTreeNode::new);
39 }
40
41 void remove(Entry<?> entry) {
42 children.remove(entry);
43 }
44
45 @Override
46 @Nullable
47 public T getValue() {
48 return value;
49 }
50
51 @Override
52 public Entry<?> getEntry() {
53 return entry;
54 }
55
56 @Override
57 public boolean isEmpty() {
58 return children.isEmpty() && value == null;
59 }
60
61 @Override
62 public Collection<Entry<?>> getChildren() {
63 return children.keySet();
64 }
65
66 @Override
67 public Collection<? extends EntryTreeNode<T>> getChildNodes() {
68 return children.values();
69 }
70
71 @Override
72 public Iterator<HashTreeNode<T>> iterator() {
73 return children.values().iterator();
74 }
75}