summaryrefslogtreecommitdiff
path: root/src/main/java/cuchaz/enigma/translation
diff options
context:
space:
mode:
authorGravatar Gegy2019-01-24 14:48:32 +0200
committerGravatar Adrian Siekierka2019-01-24 13:48:32 +0100
commit00fcd0550fcdda621c2e4662f6ddd55ce673b931 (patch)
tree6f9e4c24dbcc6d118fceec56adf7bf9d747a485c /src/main/java/cuchaz/enigma/translation
parentmark as 0.13.0-SNAPSHOT for preliminary development (diff)
downloadenigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.gz
enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.tar.xz
enigma-fork-00fcd0550fcdda621c2e4662f6ddd55ce673b931.zip
[WIP] Mapping rework (#91)
* Move packages * Mapping & entry refactor: first pass * Fix deobf -> obf tree remapping * Resolve various issues * Give all entries the potential for parents and treat inner classes as children * Deobf UI tree elements * Tests pass * Sort mapping output * Fix delta tracking * Index separation and first pass for #97 * Keep track of remapped jar index * Fix child entries not being remapped * Drop non-root entries * Track dropped mappings * Fix enigma mapping ordering * EntryTreeNode interface * Small tweaks * Naive full index remap on rename * Entries can resolve to more than one root entry * Support alternative resolution strategies * Bridge method resolution * Tests pass * Fix mappings being used where there are none * Fix methods with different descriptors being considered unique. closes #89
Diffstat (limited to 'src/main/java/cuchaz/enigma/translation')
-rw-r--r--src/main/java/cuchaz/enigma/translation/MappingTranslator.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/SignatureUpdater.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translatable.java9
-rw-r--r--src/main/java/cuchaz/enigma/translation/TranslationDirection.java36
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translator.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/VoidTranslator.java10
-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.java30
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java201
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java41
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java225
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java56
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java28
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java45
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java91
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java56
-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/EnigmaMappingsReader.java260
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java260
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java12
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java12
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java115
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java100
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java113
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java20
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java36
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java159
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java112
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java132
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java45
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java60
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/Signature.java93
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java268
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java180
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java7
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java99
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java61
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java86
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java45
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java77
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java95
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java71
48 files changed, 3908 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
new file mode 100644
index 0000000..529d0ed
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public class MappingTranslator implements Translator {
8 private final EntryMap<EntryMapping> mappings;
9 private final EntryResolver resolver;
10
11 public MappingTranslator(EntryMap<EntryMapping> mappings, EntryResolver resolver) {
12 this.mappings = mappings;
13 this.resolver = resolver;
14 }
15
16 @SuppressWarnings("unchecked")
17 @Override
18 public <T extends Translatable> T translate(T translatable) {
19 if (translatable == null) {
20 return null;
21 }
22 return (T) translatable.translate(this, resolver, mappings);
23 }
24}
diff --git a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
new file mode 100644
index 0000000..3783053
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
@@ -0,0 +1,92 @@
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;
13
14import com.google.common.collect.Lists;
15
16import java.io.IOException;
17import java.io.StringReader;
18import java.util.List;
19
20public class SignatureUpdater {
21
22 public static String update(String signature, ClassNameUpdater updater) {
23 try {
24 StringBuilder buf = new StringBuilder();
25
26 // read the signature character-by-character
27 StringReader reader = new StringReader(signature);
28 int i;
29 while ((i = reader.read()) != -1) {
30 char c = (char) i;
31
32 // does this character start a class name?
33 if (c == 'L') {
34 // update the class name and add it to the buffer
35 buf.append('L');
36 String className = readClass(reader);
37 if (className == null) {
38 throw new IllegalArgumentException("Malformed signature: " + signature);
39 }
40 buf.append(updater.update(className));
41 buf.append(';');
42 } else {
43 // copy the character into the buffer
44 buf.append(c);
45 }
46 }
47
48 return buf.toString();
49 } catch (IOException ex) {
50 // I'm pretty sure a StringReader will never throw one of these
51 throw new Error(ex);
52 }
53 }
54
55 private static String readClass(StringReader reader) throws IOException {
56 // read all the characters in the buffer until we hit a ';'
57 // remember to treat generics correctly
58 StringBuilder buf = new StringBuilder();
59 int depth = 0;
60 int i;
61 while ((i = reader.read()) != -1) {
62 char c = (char) i;
63
64 if (c == '<') {
65 depth++;
66 } else if (c == '>') {
67 depth--;
68 } else if (depth == 0) {
69 if (c == ';') {
70 return buf.toString();
71 } else {
72 buf.append(c);
73 }
74 }
75 }
76
77 return null;
78 }
79
80 public static List<String> getClasses(String signature) {
81 final List<String> classNames = Lists.newArrayList();
82 update(signature, className -> {
83 classNames.add(className);
84 return className;
85 });
86 return classNames;
87 }
88
89 public interface ClassNameUpdater {
90 String update(String className);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/translation/Translatable.java b/src/main/java/cuchaz/enigma/translation/Translatable.java
new file mode 100644
index 0000000..0370ef1
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/Translatable.java
@@ -0,0 +1,9 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public interface Translatable {
8 Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
9}
diff --git a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
new file mode 100644
index 0000000..2ecb30b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
@@ -0,0 +1,36 @@
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;
13
14public enum TranslationDirection {
15
16 DEOBFUSCATING {
17 @Override
18 public <T> T choose(T deobfChoice, T obfChoice) {
19 if (deobfChoice == null) {
20 return obfChoice;
21 }
22 return deobfChoice;
23 }
24 },
25 OBFUSCATING {
26 @Override
27 public <T> T choose(T deobfChoice, T obfChoice) {
28 if (obfChoice == null) {
29 return deobfChoice;
30 }
31 return obfChoice;
32 }
33 };
34
35 public abstract <T> T choose(T deobfChoice, T obfChoice);
36}
diff --git a/src/main/java/cuchaz/enigma/translation/Translator.java b/src/main/java/cuchaz/enigma/translation/Translator.java
new file mode 100644
index 0000000..de2003e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/Translator.java
@@ -0,0 +1,54 @@
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;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.Map;
20import java.util.stream.Collectors;
21
22public interface Translator {
23 <T extends Translatable> T translate(T translatable);
24
25 default <T extends Translatable> Collection<T> translate(Collection<T> translatable) {
26 return translatable.stream()
27 .map(this::translate)
28 .collect(Collectors.toList());
29 }
30
31 default <T extends Translatable, V> Map<T, V> translateKeys(Map<T, V> translatable) {
32 Map<T, V> result = new HashMap<>(translatable.size());
33 for (Map.Entry<T, V> entry : translatable.entrySet()) {
34 result.put(translate(entry.getKey()), entry.getValue());
35 }
36 return result;
37 }
38
39 default <K extends Translatable, V extends Translatable> Map<K, V> translate(Map<K, V> translatable) {
40 Map<K, V> result = new HashMap<>(translatable.size());
41 for (Map.Entry<K, V> entry : translatable.entrySet()) {
42 result.put(translate(entry.getKey()), translate(entry.getValue()));
43 }
44 return result;
45 }
46
47 default <K extends Translatable, V extends Translatable> Multimap<K, V> translate(Multimap<K, V> translatable) {
48 Multimap<K, V> result = HashMultimap.create(translatable.size(), 1);
49 for (Map.Entry<K, Collection<V>> entry : translatable.asMap().entrySet()) {
50 result.putAll(translate(entry.getKey()), translate(entry.getValue()));
51 }
52 return result;
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
new file mode 100644
index 0000000..c010833
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
@@ -0,0 +1,10 @@
1package cuchaz.enigma.translation;
2
3public enum VoidTranslator implements Translator {
4 INSTANCE;
5
6 @Override
7 public <T extends Translatable> T translate(T translatable) {
8 return translatable;
9 }
10}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
new file mode 100644
index 0000000..5b79b79
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
@@ -0,0 +1,25 @@
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
new file mode 100644
index 0000000..6af4846
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
@@ -0,0 +1,24 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.Collection;
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 Collection<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
new file mode 100644
index 0000000..f11cdef
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
@@ -0,0 +1,30 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4
5public class EntryMapping {
6 private final String targetName;
7 private final AccessModifier accessModifier;
8
9 public EntryMapping(@Nonnull String targetName) {
10 this(targetName, AccessModifier.UNCHANGED);
11 }
12
13 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) {
14 this.targetName = targetName;
15 this.accessModifier = accessModifier;
16 }
17
18 @Nonnull
19 public String getTargetName() {
20 return targetName;
21 }
22
23 @Nonnull
24 public AccessModifier getAccessModifier() {
25 if (accessModifier == null) {
26 return AccessModifier.UNCHANGED;
27 }
28 return accessModifier;
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
new file mode 100644
index 0000000..b7d8d17
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
@@ -0,0 +1,201 @@
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.EntryTreeNode;
10import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
11import cuchaz.enigma.translation.representation.entry.Entry;
12
13import javax.annotation.Nullable;
14import java.util.Collection;
15
16public class EntryRemapper {
17 private final EntryTree<EntryMapping> obfToDeobf;
18 private final DeltaTrackingTree<EntryMapping> deobfToObf;
19
20 private final JarIndex obfIndex;
21
22 private final EntryResolver obfResolver;
23 private EntryResolver deobfResolver;
24
25 private final Translator deobfuscator;
26 private Translator obfuscator;
27
28 private final MappingValidator validator;
29
30 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf, EntryTree<EntryMapping> deobfToObf) {
31 this.obfToDeobf = obfToDeobf;
32 this.deobfToObf = new DeltaTrackingTree<>(deobfToObf);
33
34 this.obfIndex = jarIndex;
35 this.obfResolver = jarIndex.getEntryResolver();
36
37 this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
38 rebuildDeobfIndex();
39
40 this.validator = new MappingValidator(this.deobfToObf, deobfuscator, obfResolver);
41 }
42
43 public EntryRemapper(JarIndex jarIndex) {
44 this(jarIndex, new HashEntryTree<>(), new HashEntryTree<>());
45 }
46
47 public EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> deobfuscationTrees) {
48 this(jarIndex, deobfuscationTrees, inverse(deobfuscationTrees));
49 }
50
51 private static EntryTree<EntryMapping> inverse(EntryTree<EntryMapping> tree) {
52 Translator translator = new MappingTranslator(tree, VoidEntryResolver.INSTANCE);
53 EntryTree<EntryMapping> inverse = new HashEntryTree<>();
54
55 // Naive approach, could operate on the nodes of the tree. However, this runs infrequently.
56 Collection<Entry<?>> entries = tree.getAllEntries();
57 for (Entry<?> sourceEntry : entries) {
58 Entry<?> targetEntry = translator.translate(sourceEntry);
59 inverse.insert(targetEntry, new EntryMapping(sourceEntry.getName()));
60 }
61
62 return inverse;
63 }
64
65 private void rebuildDeobfIndex() {
66 JarIndex deobfIndex = obfIndex.remapped(deobfuscator);
67
68 this.deobfResolver = deobfIndex.getEntryResolver();
69 this.obfuscator = new MappingTranslator(deobfToObf, deobfResolver);
70 }
71
72 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
73 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, ResolutionStrategy.RESOLVE_ROOT);
74 for (E resolvedEntry : resolvedEntries) {
75 if (deobfMapping != null) {
76 validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
77 }
78
79 setObfToDeobf(resolvedEntry, deobfMapping);
80 }
81
82 // Temporary hack, not very performant
83 rebuildDeobfIndex();
84 }
85
86 public <E extends Entry<?>> void mapFromDeobf(E deobfuscatedEntry, @Nullable EntryMapping deobfMapping) {
87 E obfuscatedEntry = obfuscate(deobfuscatedEntry);
88 mapFromObf(obfuscatedEntry, deobfMapping);
89 }
90
91 public void removeByObf(Entry<?> obfuscatedEntry) {
92 mapFromObf(obfuscatedEntry, null);
93 }
94
95 public void removeByDeobf(Entry<?> deobfuscatedEntry) {
96 mapFromObf(obfuscate(deobfuscatedEntry), null);
97 }
98
99 private <E extends Entry<?>> void setObfToDeobf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
100 E prevDeobf = deobfuscate(obfuscatedEntry);
101 obfToDeobf.insert(obfuscatedEntry, deobfMapping);
102
103 E newDeobf = deobfuscate(obfuscatedEntry);
104
105 // Reconstruct the children of this node in the deobf -> obf tree with our new mapping
106 // We only need to do this for deobf -> obf because the obf tree is always consistent on the left hand side
107 // We lookup by obf, and the obf never changes. This is not the case for deobf so we need to update the tree.
108
109 EntryTreeNode<EntryMapping> node = deobfToObf.findNode(prevDeobf);
110 if (node != null) {
111 for (EntryTreeNode<EntryMapping> child : node.getNodesRecursively()) {
112 Entry<?> entry = child.getEntry();
113 EntryMapping mapping = new EntryMapping(obfuscate(entry).getName());
114
115 deobfToObf.insert(entry.replaceAncestor(prevDeobf, newDeobf), mapping);
116 deobfToObf.remove(entry);
117 }
118 } else {
119 deobfToObf.insert(newDeobf, new EntryMapping(obfuscatedEntry.getName()));
120 }
121 }
122
123 @Nullable
124 public EntryMapping getDeobfMapping(Entry<?> entry) {
125 return obfToDeobf.get(entry);
126 }
127
128 @Nullable
129 public EntryMapping getObfMapping(Entry<?> entry) {
130 return deobfToObf.get(entry);
131 }
132
133 public boolean hasDeobfMapping(Entry<?> obfEntry) {
134 return obfToDeobf.contains(obfEntry);
135 }
136
137 public boolean hasObfMapping(Entry<?> deobfEntry) {
138 return deobfToObf.contains(deobfEntry);
139 }
140
141 public <T extends Translatable> T deobfuscate(T translatable) {
142 return deobfuscator.translate(translatable);
143 }
144
145 public <T extends Translatable> T obfuscate(T translatable) {
146 return obfuscator.translate(translatable);
147 }
148
149 public Translator getDeobfuscator() {
150 return deobfuscator;
151 }
152
153 public Translator getObfuscator() {
154 return obfuscator;
155 }
156
157 public Collection<Entry<?>> getObfEntries() {
158 return obfToDeobf.getAllEntries();
159 }
160
161 public Collection<Entry<?>> getObfRootEntries() {
162 return obfToDeobf.getRootEntries();
163 }
164
165 public Collection<Entry<?>> getDeobfEntries() {
166 return deobfToObf.getAllEntries();
167 }
168
169 public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) {
170 return obfToDeobf.getChildren(obfuscatedEntry);
171 }
172
173 public Collection<Entry<?>> getDeobfChildren(Entry<?> deobfuscatedEntry) {
174 return deobfToObf.getChildren(deobfuscatedEntry);
175 }
176
177 public EntryTree<EntryMapping> getObfToDeobf() {
178 return obfToDeobf;
179 }
180
181 public DeltaTrackingTree<EntryMapping> getDeobfToObf() {
182 return deobfToObf;
183 }
184
185 public MappingDelta takeMappingDelta() {
186 MappingDelta delta = deobfToObf.takeDelta();
187 return delta.translate(obfuscator, VoidEntryResolver.INSTANCE, deobfToObf);
188 }
189
190 public boolean isDirty() {
191 return deobfToObf.isDirty();
192 }
193
194 public EntryResolver getObfResolver() {
195 return obfResolver;
196 }
197
198 public EntryResolver getDeobfResolver() {
199 return deobfResolver;
200 }
201}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
new file mode 100644
index 0000000..521f72d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
@@ -0,0 +1,41 @@
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
new file mode 100644
index 0000000..1f2290a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
@@ -0,0 +1,225 @@
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.getBridgeFromAccessed((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 }
95 }
96 }
97
98 Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>();
99
100 for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) {
101 Entry<ClassEntry> parentEntry = entry.withParent(parentClass);
102
103 if (strategy == ResolutionStrategy.RESOLVE_ROOT) {
104 resolvedEntries.addAll(resolveRoot(parentEntry, strategy));
105 } else {
106 resolvedEntries.addAll(resolveClosest(parentEntry, strategy));
107 }
108 }
109
110 return resolvedEntries;
111 }
112
113 private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
114 // When resolving root, we want to first look for the lowest entry before returning ourselves
115 Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy);
116
117 if (parentResolution.isEmpty()) {
118 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
119 if (parentAccess != null && !parentAccess.isPrivate()) {
120 return Collections.singleton(entry);
121 }
122 }
123
124 return parentResolution;
125 }
126
127 private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
128 // When resolving closest, we want to first check if we exist before looking further down
129 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
130 if (parentAccess != null && !parentAccess.isPrivate()) {
131 return Collections.singleton(entry);
132 } else {
133 return resolveChildEntry(entry, strategy);
134 }
135 }
136
137 @Override
138 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
139 MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class);
140 if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) {
141 return Collections.singleton(entry);
142 }
143
144 Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod);
145 Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size());
146
147 for (MethodEntry equivalentMethod : equivalentMethods) {
148 Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod);
149 equivalentEntries.add(equivalentEntry);
150 }
151
152 return equivalentEntries;
153 }
154
155 @Override
156 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
157 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
158 if (access == null) {
159 throw new IllegalArgumentException("Could not find method " + methodEntry);
160 }
161
162 if (!canInherit(methodEntry, access)) {
163 return Collections.singleton(methodEntry);
164 }
165
166 Set<MethodEntry> methodEntries = Sets.newHashSet();
167 resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry));
168 return methodEntries;
169 }
170
171 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
172 MethodEntry methodEntry = node.getMethodEntry();
173 if (methodEntries.contains(methodEntry)) {
174 return;
175 }
176
177 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
178 if (flags != null && canInherit(methodEntry, flags)) {
179 // collect the entry
180 methodEntries.add(methodEntry);
181 }
182
183 // look at bridge methods!
184 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry);
185 while (bridgedMethod != null) {
186 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
187 bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod);
188 }
189
190 // look at interface methods too
191 for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) {
192 resolveEquivalentMethods(methodEntries, implementationsNode);
193 }
194
195 // recurse
196 for (int i = 0; i < node.getChildCount(); i++) {
197 resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
198 }
199 }
200
201 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
202 MethodEntry methodEntry = node.getMethodEntry();
203 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
204 if (flags != null && !flags.isPrivate() && !flags.isStatic()) {
205 // collect the entry
206 methodEntries.add(methodEntry);
207 }
208
209 // look at bridge methods!
210 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(methodEntry);
211 while (bridgedMethod != null) {
212 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
213 bridgedMethod = bridgeMethodIndex.getBridgeFromAccessed(bridgedMethod);
214 }
215
216 // recurse
217 for (int i = 0; i < node.getChildCount(); i++) {
218 resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
219 }
220 }
221
222 private boolean canInherit(MethodEntry entry, AccessFlags access) {
223 return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal();
224 }
225}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
new file mode 100644
index 0000000..4fba49d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
@@ -0,0 +1,56 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9public class MappingDelta implements Translatable {
10 public static final Object PLACEHOLDER = new Object();
11
12 private final EntryTree<Object> additions;
13 private final EntryTree<Object> deletions;
14
15 public MappingDelta(EntryTree<Object> additions, EntryTree<Object> deletions) {
16 this.additions = additions;
17 this.deletions = deletions;
18 }
19
20 public MappingDelta() {
21 this(new HashEntryTree<>(), new HashEntryTree<>());
22 }
23
24 public static MappingDelta added(EntryTree<EntryMapping> mappings) {
25 EntryTree<Object> additions = new HashEntryTree<>();
26 for (Entry<?> entry : mappings.getAllEntries()) {
27 additions.insert(entry, PLACEHOLDER);
28 }
29
30 return new MappingDelta(additions, new HashEntryTree<>());
31 }
32
33 public EntryTree<?> getAdditions() {
34 return additions;
35 }
36
37 public EntryTree<?> getDeletions() {
38 return deletions;
39 }
40
41 @Override
42 public MappingDelta translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
43 return new MappingDelta(
44 translate(translator, additions),
45 translate(translator, deletions)
46 );
47 }
48
49 private EntryTree<Object> translate(Translator translator, EntryTree<Object> tree) {
50 EntryTree<Object> translatedTree = new HashEntryTree<>();
51 for (Entry<?> entry : tree.getAllEntries()) {
52 translatedTree.insert(translator.translate(entry), PLACEHOLDER);
53 }
54 return translatedTree;
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
new file mode 100644
index 0000000..9ed7e8a
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
@@ -0,0 +1,28 @@
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 final 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}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
new file mode 100644
index 0000000..422bf38
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
@@ -0,0 +1,45 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.throwables.IllegalNameException;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.translation.representation.entry.Entry;
7
8import java.util.Collection;
9
10public class MappingValidator {
11 private final EntryTree<EntryMapping> deobfToObf;
12 private final Translator deobfuscator;
13 private final EntryResolver entryResolver;
14
15 public MappingValidator(EntryTree<EntryMapping> deobfToObf, Translator deobfuscator, EntryResolver entryResolver) {
16 this.deobfToObf = deobfToObf;
17 this.deobfuscator = deobfuscator;
18 this.entryResolver = entryResolver;
19 }
20
21 public void validateRename(Entry<?> entry, String name) throws IllegalNameException {
22 Collection<Entry<?>> equivalentEntries = entryResolver.resolveEquivalentEntries(entry);
23 for (Entry<?> equivalentEntry : equivalentEntries) {
24 equivalentEntry.validateName(name);
25 validateUnique(equivalentEntry, name);
26 }
27 }
28
29 private void validateUnique(Entry<?> entry, String name) {
30 Entry<?> translatedEntry = deobfuscator.translate(entry);
31 Collection<Entry<?>> siblings = deobfToObf.getSiblings(translatedEntry);
32 if (!isUnique(translatedEntry, siblings, name)) {
33 throw new IllegalNameException(name, "Name is not unique in " + translatedEntry.getParent() + "!");
34 }
35 }
36
37 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
38 for (Entry<?> child : siblings) {
39 if (entry.canConflictWith(child) && child.getName().equals(name)) {
40 return false;
41 }
42 }
43 return true;
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
new file mode 100644
index 0000000..77d75ec
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
@@ -0,0 +1,91 @@
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.analysis.index.JarIndex;
15import cuchaz.enigma.translation.mapping.tree.EntryTree;
16import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.Entry;
19import cuchaz.enigma.translation.representation.entry.FieldEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22import java.util.Collection;
23import java.util.HashMap;
24import java.util.Map;
25
26public class MappingsChecker {
27 private final JarIndex index;
28 private final EntryTree<EntryMapping> mappings;
29
30 public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
31 this.index = index;
32 this.mappings = mappings;
33 }
34
35 public Dropped dropBrokenMappings() {
36 Dropped dropped = new Dropped();
37
38 Collection<Entry<?>> obfEntries = mappings.getAllEntries();
39 for (Entry<?> entry : obfEntries) {
40 if (entry instanceof ClassEntry || entry instanceof MethodEntry || entry instanceof FieldEntry) {
41 tryDropEntry(dropped, entry);
42 }
43 }
44
45 dropped.apply(mappings);
46
47 return dropped;
48 }
49
50 private void tryDropEntry(Dropped dropped, Entry<?> entry) {
51 if (shouldDropEntry(entry)) {
52 EntryMapping mapping = mappings.get(entry);
53 if (mapping != null) {
54 dropped.drop(entry, mapping);
55 }
56 }
57 }
58
59 private boolean shouldDropEntry(Entry<?> entry) {
60 if (!index.getEntryIndex().hasEntry(entry)) {
61 return true;
62 }
63 Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
64 return !resolvedEntries.contains(entry);
65 }
66
67 public static class Dropped {
68 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
69
70 public void drop(Entry<?> entry, EntryMapping mapping) {
71 droppedMappings.put(entry, mapping.getTargetName());
72 }
73
74 void apply(EntryTree<EntryMapping> mappings) {
75 for (Entry<?> entry : droppedMappings.keySet()) {
76 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
77 if (node == null) {
78 continue;
79 }
80
81 for (Entry<?> childEntry : node.getChildrenRecursively()) {
82 mappings.remove(childEntry);
83 }
84 }
85 }
86
87 public Map<Entry<?>, String> getDroppedMappings() {
88 return droppedMappings;
89 }
90 }
91}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
new file mode 100644
index 0000000..19473ea
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
@@ -0,0 +1,56 @@
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, boolean packageRequired) {
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 if (packageRequired && ClassEntry.getPackageName(name) == null) {
43 throw new IllegalNameException(name, "Class must be in a package");
44 }
45 }
46
47 public static void validateIdentifier(String name) {
48 if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
49 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
50 }
51 }
52
53 public static boolean isReserved(String name) {
54 return ILLEGAL_IDENTIFIERS.contains(name);
55 }
56}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
new file mode 100644
index 0000000..1c28e02
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
@@ -0,0 +1,6 @@
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
new file mode 100644
index 0000000..2eab55f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
@@ -0,0 +1,27 @@
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/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
new file mode 100644
index 0000000..d36bc0b
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
@@ -0,0 +1,260 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.AccessModifier;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingPair;
8import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.*;
13
14import javax.annotation.Nullable;
15import java.io.IOException;
16import java.nio.file.Files;
17import java.nio.file.Path;
18import java.util.ArrayDeque;
19import java.util.Deque;
20import java.util.List;
21import java.util.Locale;
22import java.util.stream.Collectors;
23
24public enum EnigmaMappingsReader implements MappingsReader {
25 FILE {
26 @Override
27 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
28 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
29 readFile(path, mappings);
30 return mappings;
31 }
32 },
33 DIRECTORY {
34 @Override
35 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37
38 List<Path> files = Files.walk(path)
39 .filter(f -> !Files.isDirectory(f))
40 .filter(f -> f.toString().endsWith(".mapping"))
41 .collect(Collectors.toList());
42 for (Path file : files) {
43 if (Files.isHidden(file)) {
44 continue;
45 }
46 readFile(file, mappings);
47 }
48
49 return mappings;
50 }
51 };
52
53 protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException {
54 List<String> lines = Files.readAllLines(path, Charsets.UTF_8);
55 Deque<Entry<?>> mappingStack = new ArrayDeque<>();
56
57 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
58 String line = lines.get(lineNumber);
59 int indentation = countIndentation(line);
60
61 line = formatLine(line);
62 if (line == null) {
63 continue;
64 }
65
66 while (indentation < mappingStack.size()) {
67 mappingStack.pop();
68 }
69
70 try {
71 MappingPair<?, EntryMapping> pair = parseLine(mappingStack.peek(), line);
72 mappingStack.push(pair.getEntry());
73 if (pair.getMapping() != null) {
74 mappings.insert(pair.getEntry(), pair.getMapping());
75 }
76 } catch (Throwable t) {
77 t.printStackTrace();
78 throw new MappingParseException(path::toString, lineNumber, t.toString());
79 }
80 }
81 }
82
83 @Nullable
84 private String formatLine(String line) {
85 line = stripComment(line);
86 line = line.trim();
87
88 if (line.isEmpty()) {
89 return null;
90 }
91
92 return line;
93 }
94
95 private String stripComment(String line) {
96 int commentPos = line.indexOf('#');
97 if (commentPos >= 0) {
98 return line.substring(0, commentPos);
99 }
100 return line;
101 }
102
103 private int countIndentation(String line) {
104 int indent = 0;
105 for (int i = 0; i < line.length(); i++) {
106 if (line.charAt(i) != '\t') {
107 break;
108 }
109 indent++;
110 }
111 return indent;
112 }
113
114 private MappingPair<?, EntryMapping> parseLine(@Nullable Entry<?> parent, String line) {
115 String[] tokens = line.trim().split("\\s");
116 String keyToken = tokens[0].toLowerCase(Locale.ROOT);
117
118 switch (keyToken) {
119 case "class":
120 return parseClass(parent, tokens);
121 case "field":
122 return parseField(parent, tokens);
123 case "method":
124 return parseMethod(parent, tokens);
125 case "arg":
126 return parseArgument(parent, tokens);
127 default:
128 throw new RuntimeException("Unknown token '" + keyToken + "'");
129 }
130 }
131
132 private MappingPair<ClassEntry, EntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) {
133 String obfuscatedName = ClassEntry.getInnerName(tokens[1]);
134 ClassEntry obfuscatedEntry;
135 if (parent instanceof ClassEntry) {
136 obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName);
137 } else {
138 obfuscatedEntry = new ClassEntry(obfuscatedName);
139 }
140
141 String mapping = null;
142 AccessModifier modifier = AccessModifier.UNCHANGED;
143
144 if (tokens.length == 3) {
145 AccessModifier parsedModifier = parseModifier(tokens[2]);
146 if (parsedModifier != null) {
147 modifier = parsedModifier;
148 mapping = obfuscatedName;
149 } else {
150 mapping = tokens[2];
151 }
152 } else if (tokens.length == 4) {
153 mapping = tokens[2];
154 modifier = parseModifier(tokens[3]);
155 }
156
157 if (mapping != null) {
158 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
159 } else {
160 return new MappingPair<>(obfuscatedEntry);
161 }
162 }
163
164 private MappingPair<FieldEntry, EntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
165 if (!(parent instanceof ClassEntry)) {
166 throw new RuntimeException("Field must be a child of a class!");
167 }
168
169 ClassEntry ownerEntry = (ClassEntry) parent;
170
171 String obfuscatedName = tokens[1];
172 String mapping = obfuscatedName;
173 AccessModifier modifier = AccessModifier.UNCHANGED;
174 TypeDescriptor descriptor;
175
176 if (tokens.length == 4) {
177 AccessModifier parsedModifier = parseModifier(tokens[3]);
178 if (parsedModifier != null) {
179 descriptor = new TypeDescriptor(tokens[2]);
180 modifier = parsedModifier;
181 } else {
182 mapping = tokens[2];
183 descriptor = new TypeDescriptor(tokens[3]);
184 }
185 } else if (tokens.length == 5) {
186 descriptor = new TypeDescriptor(tokens[3]);
187 mapping = tokens[2];
188 modifier = parseModifier(tokens[4]);
189 } else {
190 throw new RuntimeException("Invalid method declaration");
191 }
192
193 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
194 if (mapping != null) {
195 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
196 } else {
197 return new MappingPair<>(obfuscatedEntry);
198 }
199 }
200
201 private MappingPair<MethodEntry, EntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
202 if (!(parent instanceof ClassEntry)) {
203 throw new RuntimeException("Method must be a child of a class!");
204 }
205
206 ClassEntry ownerEntry = (ClassEntry) parent;
207
208 String obfuscatedName = tokens[1];
209 String mapping = null;
210 AccessModifier modifier = AccessModifier.UNCHANGED;
211 MethodDescriptor descriptor;
212
213 if (tokens.length == 3) {
214 descriptor = new MethodDescriptor(tokens[2]);
215 } else if (tokens.length == 4) {
216 AccessModifier parsedModifier = parseModifier(tokens[3]);
217 if (parsedModifier != null) {
218 modifier = parsedModifier;
219 mapping = obfuscatedName;
220 descriptor = new MethodDescriptor(tokens[2]);
221 } else {
222 mapping = tokens[2];
223 descriptor = new MethodDescriptor(tokens[3]);
224 }
225 } else if (tokens.length == 5) {
226 mapping = tokens[2];
227 modifier = parseModifier(tokens[4]);
228 descriptor = new MethodDescriptor(tokens[3]);
229 } else {
230 throw new RuntimeException("Invalid method declaration");
231 }
232
233 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
234 if (mapping != null) {
235 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping, modifier));
236 } else {
237 return new MappingPair<>(obfuscatedEntry);
238 }
239 }
240
241 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
242 if (!(parent instanceof MethodEntry)) {
243 throw new RuntimeException("Method arg must be a child of a method!");
244 }
245
246 MethodEntry ownerEntry = (MethodEntry) parent;
247 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true);
248 String mapping = tokens[2];
249
250 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
251 }
252
253 @Nullable
254 private AccessModifier parseModifier(String token) {
255 if (token.startsWith("ACC:")) {
256 return AccessModifier.valueOf(token.substring(4));
257 }
258 return null;
259 }
260}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
new file mode 100644
index 0000000..3eef739
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
@@ -0,0 +1,260 @@
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 cuchaz.enigma.ProgressListener;
15import cuchaz.enigma.translation.MappingTranslator;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.AccessModifier;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.MappingDelta;
20import cuchaz.enigma.translation.mapping.VoidEntryResolver;
21import cuchaz.enigma.translation.mapping.tree.EntryTree;
22import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
23import cuchaz.enigma.translation.representation.entry.*;
24
25import java.io.IOException;
26import java.io.PrintWriter;
27import java.nio.file.DirectoryStream;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.nio.file.Paths;
31import java.util.ArrayList;
32import java.util.Collection;
33import java.util.concurrent.atomic.AtomicInteger;
34import java.util.stream.Collectors;
35
36public enum EnigmaMappingsWriter implements MappingsWriter {
37 FILE {
38 @Override
39 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
40 Collection<ClassEntry> classes = mappings.getRootEntries().stream()
41 .filter(entry -> entry instanceof ClassEntry)
42 .map(entry -> (ClassEntry) entry)
43 .collect(Collectors.toList());
44
45 progress.init(classes.size(), "Writing classes");
46
47 int steps = 0;
48 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) {
49 for (ClassEntry classEntry : classes) {
50 progress.step(steps++, classEntry.getFullName());
51 writeRoot(writer, mappings, classEntry);
52 }
53 } catch (IOException e) {
54 e.printStackTrace();
55 }
56 }
57 },
58 DIRECTORY {
59 @Override
60 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
61 applyDeletions(delta.getDeletions(), path);
62
63 Collection<ClassEntry> classes = delta.getAdditions().getRootEntries().stream()
64 .filter(entry -> entry instanceof ClassEntry)
65 .map(entry -> (ClassEntry) entry)
66 .collect(Collectors.toList());
67
68 progress.init(classes.size(), "Writing classes");
69
70 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
71 AtomicInteger steps = new AtomicInteger();
72
73 classes.parallelStream().forEach(classEntry -> {
74 progress.step(steps.getAndIncrement(), classEntry.getFullName());
75
76 try {
77 Path classPath = resolve(path, translator.translate(classEntry));
78 Files.deleteIfExists(classPath);
79 Files.createDirectories(classPath.getParent());
80
81 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(classPath))) {
82 writeRoot(writer, mappings, classEntry);
83 }
84 } catch (Throwable t) {
85 System.err.println("Failed to write class '" + classEntry.getFullName() + "'");
86 t.printStackTrace();
87 }
88 });
89 }
90
91 private void applyDeletions(EntryTree<?> deletions, Path root) {
92 Collection<ClassEntry> deletedClasses = deletions.getRootEntries().stream()
93 .filter(e -> e instanceof ClassEntry)
94 .map(e -> (ClassEntry) e)
95 .collect(Collectors.toList());
96
97 for (ClassEntry classEntry : deletedClasses) {
98 try {
99 Files.deleteIfExists(resolve(root, classEntry));
100 } catch (IOException e) {
101 System.err.println("Failed to delete deleted class '" + classEntry + "'");
102 e.printStackTrace();
103 }
104 }
105
106 for (ClassEntry classEntry : deletedClasses) {
107 String packageName = classEntry.getPackageName();
108 if (packageName != null) {
109 Path packagePath = Paths.get(packageName);
110 try {
111 deleteDeadPackages(root, packagePath);
112 } catch (IOException e) {
113 System.err.println("Failed to delete dead package '" + packageName + "'");
114 e.printStackTrace();
115 }
116 }
117 }
118 }
119
120 private void deleteDeadPackages(Path root, Path packagePath) throws IOException {
121 for (int i = packagePath.getNameCount() - 1; i >= 0; i--) {
122 Path subPath = packagePath.subpath(0, i + 1);
123 Path packagePart = root.resolve(subPath);
124 if (isEmpty(packagePart)) {
125 Files.deleteIfExists(packagePart);
126 }
127 }
128 }
129
130 private boolean isEmpty(Path path) {
131 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
132 return !stream.iterator().hasNext();
133 } catch (IOException e) {
134 return false;
135 }
136 }
137
138 private Path resolve(Path root, ClassEntry classEntry) {
139 return root.resolve(classEntry.getFullName() + ".mapping");
140 }
141 };
142
143 protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) {
144 Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry));
145
146 writer.println(writeClass(classEntry, mappings.get(classEntry)).trim());
147 for (Entry<?> child : children) {
148 writeEntry(writer, mappings, child, 1);
149 }
150 }
151
152 protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) {
153 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
154 if (node == null) {
155 return;
156 }
157
158 EntryMapping mapping = node.getValue();
159 if (entry instanceof ClassEntry) {
160 String line = writeClass((ClassEntry) entry, mapping);
161 writer.println(indent(line, depth));
162 } else if (entry instanceof MethodEntry) {
163 String line = writeMethod((MethodEntry) entry, mapping);
164 writer.println(indent(line, depth));
165 } else if (entry instanceof FieldEntry) {
166 String line = writeField((FieldEntry) entry, mapping);
167 writer.println(indent(line, depth));
168 } else if (entry instanceof LocalVariableEntry) {
169 String line = writeArgument((LocalVariableEntry) entry, mapping);
170 writer.println(indent(line, depth));
171 }
172
173 Collection<Entry<?>> children = groupChildren(node.getChildren());
174 for (Entry<?> child : children) {
175 writeEntry(writer, mappings, child, depth + 1);
176 }
177 }
178
179 private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) {
180 Collection<Entry<?>> result = new ArrayList<>(children.size());
181
182 children.stream().filter(e -> e instanceof ClassEntry)
183 .map(e -> (ClassEntry) e)
184 .sorted()
185 .forEach(result::add);
186
187 children.stream().filter(e -> e instanceof FieldEntry)
188 .map(e -> (FieldEntry) e)
189 .sorted()
190 .forEach(result::add);
191
192 children.stream().filter(e -> e instanceof MethodEntry)
193 .map(e -> (MethodEntry) e)
194 .sorted()
195 .forEach(result::add);
196
197 children.stream().filter(e -> e instanceof LocalVariableEntry)
198 .map(e -> (LocalVariableEntry) e)
199 .sorted()
200 .forEach(result::add);
201
202 return result;
203 }
204
205 protected String writeClass(ClassEntry entry, EntryMapping mapping) {
206 StringBuilder builder = new StringBuilder("CLASS ");
207 builder.append(entry.getFullName()).append(' ');
208 writeMapping(builder, mapping);
209
210 return builder.toString();
211 }
212
213 protected String writeMethod(MethodEntry entry, EntryMapping mapping) {
214 StringBuilder builder = new StringBuilder("METHOD ");
215 builder.append(entry.getName()).append(' ');
216 writeMapping(builder, mapping);
217
218 builder.append(entry.getDesc().toString());
219
220 return builder.toString();
221 }
222
223 protected String writeField(FieldEntry entry, EntryMapping mapping) {
224 StringBuilder builder = new StringBuilder("FIELD ");
225 builder.append(entry.getName()).append(' ');
226 writeMapping(builder, mapping);
227
228 builder.append(entry.getDesc().toString());
229
230 return builder.toString();
231 }
232
233 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) {
234 StringBuilder builder = new StringBuilder("ARG ");
235 builder.append(entry.getIndex()).append(' ');
236
237 String mappedName = mapping != null ? mapping.getTargetName() : entry.getName();
238 builder.append(mappedName);
239
240 return builder.toString();
241 }
242
243 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
244 if (mapping != null) {
245 builder.append(mapping.getTargetName()).append(' ');
246 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) {
247 builder.append(mapping.getAccessModifier().getFormattedName()).append(' ');
248 }
249 }
250 }
251
252 private String indent(String line, int depth) {
253 StringBuilder builder = new StringBuilder();
254 for (int i = 0; i < depth; i++) {
255 builder.append("\t");
256 }
257 builder.append(line.trim());
258 return builder.toString();
259 }
260}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
new file mode 100644
index 0000000..4db1645
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
@@ -0,0 +1,54 @@
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.tree.EntryTree;
8
9import javax.annotation.Nullable;
10import java.io.IOException;
11import java.nio.file.Path;
12
13public enum MappingFormat {
14 ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE),
15 ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY),
16 TINY_FILE(null, TinyMappingsReader.INSTANCE),
17 SRG_FILE(SrgMappingsWriter.INSTANCE, null);
18
19 private final MappingsWriter writer;
20 private final MappingsReader reader;
21
22 MappingFormat(MappingsWriter writer, MappingsReader reader) {
23 this.writer = writer;
24 this.reader = reader;
25 }
26
27 public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener) {
28 write(mappings, MappingDelta.added(mappings), path, progressListener);
29 }
30
31 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progressListener) {
32 if (writer == null) {
33 throw new IllegalStateException(name() + " does not support writing");
34 }
35 writer.write(mappings, delta, path, progressListener);
36 }
37
38 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
39 if (reader == null) {
40 throw new IllegalStateException(name() + " does not support reading");
41 }
42 return reader.read(path);
43 }
44
45 @Nullable
46 public MappingsWriter getWriter() {
47 return writer;
48 }
49
50 @Nullable
51 public MappingsReader getReader() {
52 return reader;
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
new file mode 100644
index 0000000..f239ee6
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
@@ -0,0 +1,12 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.throwables.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6
7import java.io.IOException;
8import java.nio.file.Path;
9
10public interface MappingsReader {
11 EntryTree<EntryMapping> read(Path path) throws MappingParseException, IOException;
12}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
new file mode 100644
index 0000000..b519668
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
@@ -0,0 +1,12 @@
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.tree.EntryTree;
7
8import java.nio.file.Path;
9
10public interface MappingsWriter {
11 void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress);
12}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
new file mode 100644
index 0000000..15ba4d7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
@@ -0,0 +1,115 @@
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.VoidEntryResolver;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
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.MethodEntry;
16
17import java.io.IOException;
18import java.io.PrintWriter;
19import java.nio.file.Files;
20import java.nio.file.Path;
21import java.util.ArrayList;
22import java.util.Collection;
23import java.util.Comparator;
24import java.util.List;
25import java.util.stream.Collectors;
26
27public enum SrgMappingsWriter implements MappingsWriter {
28 INSTANCE;
29
30 @Override
31 public void write(EntryTree<EntryMapping> mappings, MappingDelta delta, Path path, ProgressListener progress) {
32 try {
33 Files.deleteIfExists(path);
34 Files.createFile(path);
35 } catch (IOException e) {
36 e.printStackTrace();
37 }
38
39 List<String> classLines = new ArrayList<>();
40 List<String> fieldLines = new ArrayList<>();
41 List<String> methodLines = new ArrayList<>();
42
43 Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream()
44 .map(EntryTreeNode::getEntry)
45 .collect(Collectors.toList());
46 progress.init(rootEntries.size(), "Generating mappings");
47
48 int steps = 0;
49 for (Entry<?> entry : sorted(rootEntries)) {
50 progress.step(steps++, entry.getName());
51 writeEntry(classLines, fieldLines, methodLines, mappings, entry);
52 }
53
54 progress.init(3, "Writing mappings");
55 try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(path))) {
56 progress.step(0, "Classes");
57 classLines.forEach(writer::println);
58 progress.step(1, "Fields");
59 fieldLines.forEach(writer::println);
60 progress.step(2, "Methods");
61 methodLines.forEach(writer::println);
62 } catch (IOException e) {
63 e.printStackTrace();
64 }
65 }
66
67 private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) {
68 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
69 if (node == null) {
70 return;
71 }
72
73 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
74 if (entry instanceof ClassEntry) {
75 classes.add(generateClassLine((ClassEntry) entry, translator));
76 } else if (entry instanceof FieldEntry) {
77 fields.add(generateFieldLine((FieldEntry) entry, translator));
78 } else if (entry instanceof MethodEntry) {
79 methods.add(generateMethodLine((MethodEntry) entry, translator));
80 }
81
82 for (Entry<?> child : sorted(node.getChildren())) {
83 writeEntry(classes, fields, methods, mappings, child);
84 }
85 }
86
87 private String generateClassLine(ClassEntry sourceEntry, Translator translator) {
88 ClassEntry targetEntry = translator.translate(sourceEntry);
89 return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName();
90 }
91
92 private String generateMethodLine(MethodEntry sourceEntry, Translator translator) {
93 MethodEntry targetEntry = translator.translate(sourceEntry);
94 return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry);
95 }
96
97 private String describeMethod(MethodEntry entry) {
98 return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc();
99 }
100
101 private String generateFieldLine(FieldEntry sourceEntry, Translator translator) {
102 FieldEntry targetEntry = translator.translate(sourceEntry);
103 return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry);
104 }
105
106 private String describeField(FieldEntry entry) {
107 return entry.getParent().getFullName() + "/" + entry.getName();
108 }
109
110 private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) {
111 ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable);
112 sorted.sort(Comparator.comparing(Entry::getName));
113 return sorted;
114 }
115}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
new file mode 100644
index 0000000..e0afc3e
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
@@ -0,0 +1,100 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingPair;
7import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.FieldEntry;
13import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15
16import java.io.IOException;
17import java.nio.file.Files;
18import java.nio.file.Path;
19import java.util.List;
20
21public enum TinyMappingsReader implements MappingsReader {
22 INSTANCE;
23
24 @Override
25 public EntryTree<EntryMapping> read(Path path) throws IOException, MappingParseException {
26 return read(path, Files.readAllLines(path, Charsets.UTF_8));
27 }
28
29 private EntryTree<EntryMapping> read(Path path, List<String> lines) throws MappingParseException {
30 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
31 lines.remove(0);
32
33 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
34 String line = lines.get(lineNumber);
35
36 try {
37 MappingPair<?, EntryMapping> mapping = parseLine(line);
38 mappings.insert(mapping.getEntry(), mapping.getMapping());
39 } catch (Throwable t) {
40 t.printStackTrace();
41 throw new MappingParseException(path::toString, lineNumber, t.toString());
42 }
43 }
44
45 return mappings;
46 }
47
48 private MappingPair<?, EntryMapping> parseLine(String line) {
49 String[] tokens = line.split("\t");
50
51 String key = tokens[0];
52 switch (key) {
53 case "CLASS":
54 return parseClass(tokens);
55 case "FIELD":
56 return parseField(tokens);
57 case "METHOD":
58 return parseMethod(tokens);
59 case "MTH-ARG":
60 return parseArgument(tokens);
61 default:
62 throw new RuntimeException("Unknown token '" + key + "'!");
63 }
64 }
65
66 private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) {
67 ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]);
68 String mapping = tokens[2];
69 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
70 }
71
72 private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) {
73 ClassEntry ownerClass = new ClassEntry(tokens[1]);
74 TypeDescriptor descriptor = new TypeDescriptor(tokens[2]);
75
76 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor);
77 String mapping = tokens[4];
78 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
79 }
80
81 private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) {
82 ClassEntry ownerClass = new ClassEntry(tokens[1]);
83 MethodDescriptor descriptor = new MethodDescriptor(tokens[2]);
84
85 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor);
86 String mapping = tokens[4];
87 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
88 }
89
90 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) {
91 ClassEntry ownerClass = new ClassEntry(tokens[1]);
92 MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]);
93 MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor);
94 int variableIndex = Integer.parseInt(tokens[4]);
95
96 String mapping = tokens[5];
97 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true);
98 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
99 }
100}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
new file mode 100644
index 0000000..98a01df
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
@@ -0,0 +1,113 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.mapping.MappingDelta;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.Iterator;
9
10public class DeltaTrackingTree<T> implements EntryTree<T> {
11 private final EntryTree<T> delegate;
12
13 private EntryTree<Object> additions = new HashEntryTree<>();
14 private EntryTree<Object> deletions = new HashEntryTree<>();
15
16 public DeltaTrackingTree(EntryTree<T> delegate) {
17 this.delegate = delegate;
18 }
19
20 public DeltaTrackingTree() {
21 this(new HashEntryTree<>());
22 }
23
24 @Override
25 public void insert(Entry<?> entry, T value) {
26 if (value != null) {
27 trackAddition(entry);
28 } else {
29 trackDeletion(entry);
30 }
31 delegate.insert(entry, value);
32 }
33
34 @Nullable
35 @Override
36 public T remove(Entry<?> entry) {
37 T value = delegate.remove(entry);
38 trackDeletion(entry);
39 return value;
40 }
41
42 public void trackAddition(Entry<?> entry) {
43 deletions.remove(entry);
44 additions.insert(entry, MappingDelta.PLACEHOLDER);
45 }
46
47 public void trackDeletion(Entry<?> entry) {
48 additions.remove(entry);
49 deletions.insert(entry, MappingDelta.PLACEHOLDER);
50 }
51
52 @Nullable
53 @Override
54 public T get(Entry<?> entry) {
55 return delegate.get(entry);
56 }
57
58 @Override
59 public Collection<Entry<?>> getChildren(Entry<?> entry) {
60 return delegate.getChildren(entry);
61 }
62
63 @Override
64 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
65 return delegate.getSiblings(entry);
66 }
67
68 @Nullable
69 @Override
70 public EntryTreeNode<T> findNode(Entry<?> entry) {
71 return delegate.findNode(entry);
72 }
73
74 @Override
75 public Collection<EntryTreeNode<T>> getAllNodes() {
76 return delegate.getAllNodes();
77 }
78
79 @Override
80 public Collection<Entry<?>> getRootEntries() {
81 return delegate.getRootEntries();
82 }
83
84 @Override
85 public Collection<Entry<?>> getAllEntries() {
86 return delegate.getAllEntries();
87 }
88
89 @Override
90 public boolean isEmpty() {
91 return delegate.isEmpty();
92 }
93
94 @Override
95 public Iterator<EntryTreeNode<T>> iterator() {
96 return delegate.iterator();
97 }
98
99 public MappingDelta takeDelta() {
100 MappingDelta delta = new MappingDelta(additions, deletions);
101 resetDelta();
102 return delta;
103 }
104
105 private void resetDelta() {
106 additions = new HashEntryTree<>();
107 deletions = new HashEntryTree<>();
108 }
109
110 public boolean isDirty() {
111 return !additions.isEmpty() || !deletions.isEmpty();
112 }
113}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
new file mode 100644
index 0000000..73fe12d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
@@ -0,0 +1,20 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.mapping.EntryMap;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8
9public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>> {
10 Collection<Entry<?>> getChildren(Entry<?> entry);
11
12 Collection<Entry<?>> getSiblings(Entry<?> entry);
13
14 @Nullable
15 EntryTreeNode<T> findNode(Entry<?> entry);
16
17 Collection<EntryTreeNode<T>> getAllNodes();
18
19 Collection<Entry<?>> getRootEntries();
20}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
new file mode 100644
index 0000000..734b60c
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
@@ -0,0 +1,36 @@
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}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
new file mode 100644
index 0000000..ff88bf9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
@@ -0,0 +1,159 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.*;
7import java.util.stream.Collectors;
8
9public class HashEntryTree<T> implements EntryTree<T> {
10 private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>();
11
12 @Override
13 public void insert(Entry<?> entry, T value) {
14 List<HashTreeNode<T>> path = computePath(entry);
15 path.get(path.size() - 1).putValue(value);
16 if (value == null) {
17 removeDeadAlong(path);
18 }
19 }
20
21 @Override
22 @Nullable
23 public T remove(Entry<?> entry) {
24 List<HashTreeNode<T>> path = computePath(entry);
25 T value = path.get(path.size() - 1).removeValue();
26
27 removeDeadAlong(path);
28
29 return value;
30 }
31
32 @Override
33 @Nullable
34 public T get(Entry<?> entry) {
35 HashTreeNode<T> node = findNode(entry);
36 if (node == null) {
37 return null;
38 }
39 return node.getValue();
40 }
41
42 @Override
43 public boolean contains(Entry<?> entry) {
44 return get(entry) != null;
45 }
46
47 @Override
48 public Collection<Entry<?>> getChildren(Entry<?> entry) {
49 HashTreeNode<T> leaf = findNode(entry);
50 if (leaf == null) {
51 return Collections.emptyList();
52 }
53 return leaf.getChildren();
54 }
55
56 @Override
57 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
58 List<HashTreeNode<T>> path = computePath(entry);
59 if (path.size() <= 1) {
60 return getSiblings(entry, root.keySet());
61 }
62 HashTreeNode<T> parent = path.get(path.size() - 2);
63 return getSiblings(entry, parent.getChildren());
64 }
65
66 private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> children) {
67 Set<Entry<?>> siblings = new HashSet<>(children);
68 siblings.remove(entry);
69 return siblings;
70 }
71
72 @Override
73 @Nullable
74 public HashTreeNode<T> findNode(Entry<?> target) {
75 List<Entry<?>> parentChain = target.getAncestry();
76 if (parentChain.isEmpty()) {
77 return null;
78 }
79
80 HashTreeNode<T> node = root.get(parentChain.get(0));
81 for (int i = 1; i < parentChain.size(); i++) {
82 if (node == null) {
83 return null;
84 }
85 node = node.getChild(parentChain.get(i), false);
86 }
87
88 return node;
89 }
90
91 private List<HashTreeNode<T>> computePath(Entry<?> target) {
92 List<Entry<?>> ancestry = target.getAncestry();
93 if (ancestry.isEmpty()) {
94 return Collections.emptyList();
95 }
96
97 List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size());
98
99 Entry<?> rootEntry = ancestry.get(0);
100 HashTreeNode<T> node = root.computeIfAbsent(rootEntry, HashTreeNode::new);
101 path.add(node);
102
103 for (int i = 1; i < ancestry.size(); i++) {
104 node = node.getChild(ancestry.get(i), true);
105 path.add(node);
106 }
107
108 return path;
109 }
110
111 private void removeDeadAlong(List<HashTreeNode<T>> path) {
112 for (int i = path.size() - 1; i >= 0; i--) {
113 HashTreeNode<T> node = path.get(i);
114 if (node.isEmpty()) {
115 if (i > 0) {
116 HashTreeNode<T> parentNode = path.get(i - 1);
117 parentNode.remove(node.getEntry());
118 } else {
119 root.remove(node.getEntry());
120 }
121 } else {
122 break;
123 }
124 }
125 }
126
127 @Override
128 @SuppressWarnings("unchecked")
129 public Iterator<EntryTreeNode<T>> iterator() {
130 Collection<EntryTreeNode<T>> values = (Collection) root.values();
131 return values.iterator();
132 }
133
134 @Override
135 public Collection<EntryTreeNode<T>> getAllNodes() {
136 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
137 for (EntryTreeNode<T> node : root.values()) {
138 nodes.addAll(node.getNodesRecursively());
139 }
140 return nodes;
141 }
142
143 @Override
144 public Collection<Entry<?>> getAllEntries() {
145 return getAllNodes().stream()
146 .map(EntryTreeNode::getEntry)
147 .collect(Collectors.toList());
148 }
149
150 @Override
151 public Collection<Entry<?>> getRootEntries() {
152 return root.keySet();
153 }
154
155 @Override
156 public boolean isEmpty() {
157 return root.isEmpty();
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
new file mode 100644
index 0000000..90e9164
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
@@ -0,0 +1,72 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.Collection;
7import java.util.HashMap;
8import java.util.Iterator;
9import java.util.Map;
10
11public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> {
12 private final Entry<?> entry;
13 private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>();
14 private T value;
15
16 HashTreeNode(Entry<?> entry) {
17 this.entry = entry;
18 }
19
20 void putValue(T value) {
21 this.value = value;
22 }
23
24 T removeValue() {
25 T value = this.value;
26 this.value = null;
27 return value;
28 }
29
30 HashTreeNode<T> getChild(Entry<?> entry, boolean create) {
31 if (create) {
32 return children.computeIfAbsent(entry, HashTreeNode::new);
33 } else {
34 return children.get(entry);
35 }
36 }
37
38 void remove(Entry<?> entry) {
39 children.remove(entry);
40 }
41
42 @Override
43 @Nullable
44 public T getValue() {
45 return value;
46 }
47
48 @Override
49 public Entry<?> getEntry() {
50 return entry;
51 }
52
53 @Override
54 public boolean isEmpty() {
55 return children.isEmpty() && value == null;
56 }
57
58 @Override
59 public Collection<Entry<?>> getChildren() {
60 return children.keySet();
61 }
62
63 @Override
64 public Collection<? extends EntryTreeNode<T>> getChildNodes() {
65 return children.values();
66 }
67
68 @Override
69 public Iterator<HashTreeNode<T>> iterator() {
70 return children.values().iterator();
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
new file mode 100644
index 0000000..0534edd
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
@@ -0,0 +1,112 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.analysis.Access;
4import org.objectweb.asm.Opcodes;
5
6import java.lang.reflect.Modifier;
7
8public class AccessFlags {
9 public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE);
10 public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC);
11
12 private int flags;
13
14 public AccessFlags(int flags) {
15 this.flags = flags;
16 }
17
18 public boolean isPrivate() {
19 return Modifier.isPrivate(this.flags);
20 }
21
22 public boolean isProtected() {
23 return Modifier.isProtected(this.flags);
24 }
25
26 public boolean isPublic() {
27 return Modifier.isPublic(this.flags);
28 }
29
30 public boolean isSynthetic() {
31 return (this.flags & Opcodes.ACC_SYNTHETIC) != 0;
32 }
33
34 public boolean isStatic() {
35 return Modifier.isStatic(this.flags);
36 }
37
38 public boolean isEnum() {
39 return (flags & Opcodes.ACC_ENUM) != 0;
40 }
41
42 public boolean isBridge() {
43 return (flags & Opcodes.ACC_BRIDGE) != 0;
44 }
45
46 public boolean isFinal() {
47 return (flags & Opcodes.ACC_FINAL) != 0;
48 }
49
50 public AccessFlags setPrivate() {
51 this.setVisibility(Opcodes.ACC_PRIVATE);
52 return this;
53 }
54
55 public AccessFlags setProtected() {
56 this.setVisibility(Opcodes.ACC_PROTECTED);
57 return this;
58 }
59
60 public AccessFlags setPublic() {
61 this.setVisibility(Opcodes.ACC_PUBLIC);
62 return this;
63 }
64
65 public AccessFlags setBridge() {
66 flags |= Opcodes.ACC_BRIDGE;
67 return this;
68 }
69
70 @Deprecated
71 public AccessFlags setBridged() {
72 return setBridge();
73 }
74
75 public void setVisibility(int visibility) {
76 this.resetVisibility();
77 this.flags |= visibility;
78 }
79
80 private void resetVisibility() {
81 this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
82 }
83
84 public int getFlags() {
85 return this.flags;
86 }
87
88 @Override
89 public boolean equals(Object obj) {
90 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags;
91 }
92
93 @Override
94 public int hashCode() {
95 return flags;
96 }
97
98 @Override
99 public String toString() {
100 StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase());
101 if (isStatic()) {
102 builder.append(" static");
103 }
104 if (isSynthetic()) {
105 builder.append(" synthetic");
106 }
107 if (isBridge()) {
108 builder.append(" bridge");
109 }
110 return builder.toString();
111 }
112}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
new file mode 100644
index 0000000..c59751f
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
@@ -0,0 +1,132 @@
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.representation;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.EntryMap;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21import cuchaz.enigma.utils.Utils;
22
23import java.util.ArrayList;
24import java.util.List;
25import java.util.function.Function;
26
27public class MethodDescriptor implements Translatable {
28
29 private List<TypeDescriptor> argumentDescs;
30 private TypeDescriptor returnDesc;
31
32 public MethodDescriptor(String desc) {
33 try {
34 this.argumentDescs = Lists.newArrayList();
35 int i = 0;
36 while (i < desc.length()) {
37 char c = desc.charAt(i);
38 if (c == '(') {
39 assert (this.argumentDescs.isEmpty());
40 assert (this.returnDesc == null);
41 i++;
42 } else if (c == ')') {
43 i++;
44 break;
45 } else {
46 String type = TypeDescriptor.parseFirst(desc.substring(i));
47 this.argumentDescs.add(new TypeDescriptor(type));
48 i += type.length();
49 }
50 }
51 this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i)));
52 } catch (Exception ex) {
53 throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex);
54 }
55 }
56
57 public MethodDescriptor(List<TypeDescriptor> argumentDescs, TypeDescriptor returnDesc) {
58 this.argumentDescs = argumentDescs;
59 this.returnDesc = returnDesc;
60 }
61
62 public List<TypeDescriptor> getArgumentDescs() {
63 return this.argumentDescs;
64 }
65
66 public TypeDescriptor getReturnDesc() {
67 return this.returnDesc;
68 }
69
70 @Override
71 public String toString() {
72 StringBuilder buf = new StringBuilder();
73 buf.append("(");
74 for (TypeDescriptor desc : this.argumentDescs) {
75 buf.append(desc);
76 }
77 buf.append(")");
78 buf.append(this.returnDesc);
79 return buf.toString();
80 }
81
82 public Iterable<TypeDescriptor> types() {
83 List<TypeDescriptor> descs = Lists.newArrayList();
84 descs.addAll(this.argumentDescs);
85 descs.add(this.returnDesc);
86 return descs;
87 }
88
89 @Override
90 public boolean equals(Object other) {
91 return other instanceof MethodDescriptor && equals((MethodDescriptor) other);
92 }
93
94 public boolean equals(MethodDescriptor other) {
95 return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc);
96 }
97
98 @Override
99 public int hashCode() {
100 return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode());
101 }
102
103 public boolean hasClass(ClassEntry classEntry) {
104 for (TypeDescriptor desc : types()) {
105 if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) {
106 return true;
107 }
108 }
109 return false;
110 }
111
112 public MethodDescriptor remap(Function<String, String> remapper) {
113 List<TypeDescriptor> argumentDescs = new ArrayList<>(this.argumentDescs.size());
114 for (TypeDescriptor desc : this.argumentDescs) {
115 argumentDescs.add(desc.remap(remapper));
116 }
117 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper));
118 }
119
120 @Override
121 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
122 List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size());
123 for (TypeDescriptor argument : argumentDescs) {
124 translatedArguments.add(translator.translate(argument));
125 }
126 return new MethodDescriptor(translatedArguments, translator.translate(returnDesc));
127 }
128
129 public boolean canConflictWith(MethodDescriptor descriptor) {
130 return descriptor.argumentDescs.equals(argumentDescs);
131 }
132}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java
new file mode 100644
index 0000000..9c9fa3d
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/ProcyonEntryFactory.java
@@ -0,0 +1,45 @@
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.representation;
13
14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MemberReference;
16import com.strobel.assembler.metadata.MethodDefinition;
17import cuchaz.enigma.translation.representation.entry.*;
18
19public class ProcyonEntryFactory {
20 private final ReferencedEntryPool entryPool;
21
22 public ProcyonEntryFactory(ReferencedEntryPool entryPool) {
23 this.entryPool = entryPool;
24 }
25
26 public FieldEntry getFieldEntry(MemberReference def) {
27 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
28 return entryPool.getField(classEntry, def.getName(), def.getErasedSignature());
29 }
30
31 public FieldDefEntry getFieldDefEntry(FieldDefinition def) {
32 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
33 return new FieldDefEntry(classEntry, def.getName(), new TypeDescriptor(def.getErasedSignature()), Signature.createTypedSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
34 }
35
36 public MethodEntry getMethodEntry(MemberReference def) {
37 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
38 return entryPool.getMethod(classEntry, def.getName(), def.getErasedSignature());
39 }
40
41 public MethodDefEntry getMethodDefEntry(MethodDefinition def) {
42 ClassEntry classEntry = entryPool.getClass(def.getDeclaringType().getInternalName());
43 return new MethodDefEntry(classEntry, def.getName(), new MethodDescriptor(def.getErasedSignature()), Signature.createSignature(def.getSignature()), new AccessFlags(def.getModifiers()));
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java
new file mode 100644
index 0000000..631b375
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/ReferencedEntryPool.java
@@ -0,0 +1,60 @@
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.representation;
13
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.FieldEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17
18import java.util.HashMap;
19import java.util.Map;
20
21public class ReferencedEntryPool {
22 private final Map<String, ClassEntry> classEntries = new HashMap<>();
23 private final Map<String, Map<String, MethodEntry>> methodEntries = new HashMap<>();
24 private final Map<String, Map<String, FieldEntry>> fieldEntries = new HashMap<>();
25
26 public ClassEntry getClass(String name) {
27 // TODO: FIXME - I'm a hack!
28 if ("[T".equals(name) || "[[T".equals(name) || "[[[T".equals(name)) {
29 name = name.replaceAll("T", "Ljava/lang/Object;");
30 }
31
32 final String computeName = name;
33 return this.classEntries.computeIfAbsent(name, s -> new ClassEntry(computeName));
34 }
35
36 public MethodEntry getMethod(ClassEntry ownerEntry, String name, String desc) {
37 return getMethod(ownerEntry, name, new MethodDescriptor(desc));
38 }
39
40 public MethodEntry getMethod(ClassEntry ownerEntry, String name, MethodDescriptor desc) {
41 String key = name + desc.toString();
42 return getClassMethods(ownerEntry.getFullName()).computeIfAbsent(key, s -> new MethodEntry(ownerEntry, name, desc));
43 }
44
45 public FieldEntry getField(ClassEntry ownerEntry, String name, String desc) {
46 return getField(ownerEntry, name, new TypeDescriptor(desc));
47 }
48
49 public FieldEntry getField(ClassEntry ownerEntry, String name, TypeDescriptor desc) {
50 return getClassFields(ownerEntry.getFullName()).computeIfAbsent(name, s -> new FieldEntry(ownerEntry, name, desc));
51 }
52
53 private Map<String, MethodEntry> getClassMethods(String name) {
54 return methodEntries.computeIfAbsent(name, s -> new HashMap<>());
55 }
56
57 private Map<String, FieldEntry> getClassFields(String name) {
58 return fieldEntries.computeIfAbsent(name, s -> new HashMap<>());
59 }
60}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java
new file mode 100644
index 0000000..dc241b7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/Signature.java
@@ -0,0 +1,93 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor;
4import cuchaz.enigma.translation.Translatable;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.mapping.EntryMap;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10import org.objectweb.asm.signature.SignatureReader;
11import org.objectweb.asm.signature.SignatureVisitor;
12import org.objectweb.asm.signature.SignatureWriter;
13
14import java.util.function.Function;
15import java.util.regex.Pattern;
16
17public class Signature implements Translatable {
18 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*");
19
20 private final String signature;
21 private final boolean isType;
22
23 private Signature(String signature, boolean isType) {
24 if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) {
25 signature = signature.replaceAll(":Ljava/lang/Object;:", "::");
26 }
27
28 this.signature = signature;
29 this.isType = isType;
30 }
31
32 public static Signature createTypedSignature(String signature) {
33 if (signature != null && !signature.isEmpty()) {
34 return new Signature(signature, true);
35 }
36 return new Signature(null, true);
37 }
38
39 public static Signature createSignature(String signature) {
40 if (signature != null && !signature.isEmpty()) {
41 return new Signature(signature, false);
42 }
43 return new Signature(null, false);
44 }
45
46 public String getSignature() {
47 return signature;
48 }
49
50 public boolean isType() {
51 return isType;
52 }
53
54 public Signature remap(Function<String, String> remapper) {
55 if (signature == null) {
56 return this;
57 }
58 SignatureWriter writer = new SignatureWriter();
59 SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer);
60 if (isType) {
61 new SignatureReader(signature).acceptType(visitor);
62 } else {
63 new SignatureReader(signature).accept(visitor);
64 }
65 return new Signature(writer.toString(), isType);
66 }
67
68 @Override
69 public boolean equals(Object obj) {
70 if (obj instanceof Signature) {
71 Signature other = (Signature) obj;
72 return (other.signature == null && signature == null || other.signature != null
73 && signature != null && other.signature.equals(signature))
74 && other.isType == this.isType;
75 }
76 return false;
77 }
78
79 @Override
80 public int hashCode() {
81 return signature.hashCode() | (isType ? 1 : 0) << 16;
82 }
83
84 @Override
85 public String toString() {
86 return signature;
87 }
88
89 @Override
90 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
91 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
92 }
93}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
new file mode 100644
index 0000000..f7ba849
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
@@ -0,0 +1,268 @@
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.representation;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.translation.Translatable;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.EntryMap;
21import cuchaz.enigma.translation.representation.entry.ClassEntry;
22
23import java.util.Map;
24import java.util.function.Function;
25
26public class TypeDescriptor implements Translatable {
27
28 protected final String desc;
29
30 public TypeDescriptor(String desc) {
31 Preconditions.checkNotNull(desc, "Desc cannot be null");
32
33 // don't deal with generics
34 // this is just for raw jvm types
35 if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) {
36 throw new IllegalArgumentException("don't use with generic types or templates: " + desc);
37 }
38
39 this.desc = desc;
40 }
41
42 public static String parseFirst(String in) {
43
44 if (in == null || in.length() <= 0) {
45 throw new IllegalArgumentException("No desc to parse, input is empty!");
46 }
47
48 // read one desc from the input
49
50 char c = in.charAt(0);
51
52 // first check for void
53 if (c == 'V') {
54 return "V";
55 }
56
57 // then check for primitives
58 Primitive primitive = Primitive.get(c);
59 if (primitive != null) {
60 return in.substring(0, 1);
61 }
62
63 // then check for classes
64 if (c == 'L') {
65 return readClass(in);
66 }
67
68 // then check for templates
69 if (c == 'T') {
70 return readClass(in);
71 }
72
73 // then check for arrays
74 int dim = countArrayDimension(in);
75 if (dim > 0) {
76 String arrayType = TypeDescriptor.parseFirst(in.substring(dim));
77 return in.substring(0, dim + arrayType.length());
78 }
79
80 throw new IllegalArgumentException("don't know how to parse: " + in);
81 }
82
83 private static int countArrayDimension(String in) {
84 int i = 0;
85 while (i < in.length() && in.charAt(i) == '[')
86 i++;
87 return i;
88 }
89
90 private static String readClass(String in) {
91 // read all the characters in the buffer until we hit a ';'
92 // include the parameters too
93 StringBuilder buf = new StringBuilder();
94 int depth = 0;
95 for (int i = 0; i < in.length(); i++) {
96 char c = in.charAt(i);
97 buf.append(c);
98
99 if (c == '<') {
100 depth++;
101 } else if (c == '>') {
102 depth--;
103 } else if (depth == 0 && c == ';') {
104 return buf.toString();
105 }
106 }
107 return null;
108 }
109
110 public static TypeDescriptor of(String name) {
111 return new TypeDescriptor("L" + name + ";");
112 }
113
114 @Override
115 public String toString() {
116 return this.desc;
117 }
118
119 public boolean isVoid() {
120 return this.desc.length() == 1 && this.desc.charAt(0) == 'V';
121 }
122
123 public boolean isPrimitive() {
124 return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null;
125 }
126
127 public Primitive getPrimitive() {
128 if (!isPrimitive()) {
129 throw new IllegalStateException("not a primitive");
130 }
131 return Primitive.get(this.desc.charAt(0));
132 }
133
134 public boolean isType() {
135 return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';';
136 }
137
138 public ClassEntry getTypeEntry() {
139 if (isType()) {
140 String name = this.desc.substring(1, this.desc.length() - 1);
141
142 int pos = name.indexOf('<');
143 if (pos >= 0) {
144 // remove the parameters from the class name
145 name = name.substring(0, pos);
146 }
147
148 return new ClassEntry(name);
149
150 } else if (isArray() && getArrayType().isType()) {
151 return getArrayType().getTypeEntry();
152 } else {
153 throw new IllegalStateException("desc doesn't have a class");
154 }
155 }
156
157 public boolean isArray() {
158 return this.desc.charAt(0) == '[';
159 }
160
161 public int getArrayDimension() {
162 if (!isArray()) {
163 throw new IllegalStateException("not an array");
164 }
165 return countArrayDimension(this.desc);
166 }
167
168 public TypeDescriptor getArrayType() {
169 if (!isArray()) {
170 throw new IllegalStateException("not an array");
171 }
172 return new TypeDescriptor(this.desc.substring(getArrayDimension()));
173 }
174
175 public boolean containsType() {
176 return isType() || (isArray() && getArrayType().containsType());
177 }
178
179 @Override
180 public boolean equals(Object other) {
181 return other instanceof TypeDescriptor && equals((TypeDescriptor) other);
182 }
183
184 public boolean equals(TypeDescriptor other) {
185 return this.desc.equals(other.desc);
186 }
187
188 @Override
189 public int hashCode() {
190 return this.desc.hashCode();
191 }
192
193 public TypeDescriptor remap(Function<String, String> remapper) {
194 String desc = this.desc;
195 if (isType() || (isArray() && containsType())) {
196 String replacedName = remapper.apply(this.getTypeEntry().getFullName());
197 if (replacedName != null) {
198 if (this.isType()) {
199 desc = "L" + replacedName + ";";
200 } else {
201 desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";";
202 }
203 }
204 }
205 return new TypeDescriptor(desc);
206 }
207
208 private static String getArrayPrefix(int dimension) {
209 StringBuilder buf = new StringBuilder();
210 for (int i = 0; i < dimension; i++) {
211 buf.append("[");
212 }
213 return buf.toString();
214 }
215
216 public int getSize() {
217 switch (desc.charAt(0)) {
218 case 'J':
219 case 'D':
220 if (desc.length() == 1) {
221 return 2;
222 } else {
223 return 1;
224 }
225 default:
226 return 1;
227 }
228 }
229
230 @Override
231 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
232 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
233 }
234
235 public enum Primitive {
236 BYTE('B'),
237 CHARACTER('C'),
238 SHORT('S'),
239 INTEGER('I'),
240 LONG('J'),
241 FLOAT('F'),
242 DOUBLE('D'),
243 BOOLEAN('Z');
244
245 private static final Map<Character, Primitive> lookup;
246
247 static {
248 lookup = Maps.newTreeMap();
249 for (Primitive val : values()) {
250 lookup.put(val.getCode(), val);
251 }
252 }
253
254 private char code;
255
256 Primitive(char code) {
257 this.code = code;
258 }
259
260 public static Primitive get(char code) {
261 return lookup.get(code);
262 }
263
264 public char getCode() {
265 return this.code;
266 }
267 }
268}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
new file mode 100644
index 0000000..b9391b0
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
@@ -0,0 +1,92 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import com.strobel.assembler.metadata.TypeDefinition;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.representation.AccessFlags;
19import cuchaz.enigma.translation.representation.Signature;
20
21import javax.annotation.Nullable;
22import java.util.Arrays;
23
24public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> {
25 private final AccessFlags access;
26 private final Signature signature;
27 private final ClassEntry superClass;
28 private final ClassEntry[] interfaces;
29
30 public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
31 this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces);
32 }
33
34 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
35 super(parent, className);
36 Preconditions.checkNotNull(signature, "Class signature cannot be null");
37 Preconditions.checkNotNull(access, "Class access cannot be null");
38
39 this.signature = signature;
40 this.access = access;
41 this.superClass = superClass;
42 this.interfaces = interfaces != null ? interfaces : new ClassEntry[0];
43 }
44
45 public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) {
46 ClassEntry superClass = superName != null ? new ClassEntry(superName) : null;
47 ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new);
48 return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses);
49 }
50
51 public static ClassDefEntry parse(TypeDefinition def) {
52 String name = def.getInternalName();
53 Signature signature = Signature.createSignature(def.getSignature());
54 AccessFlags access = new AccessFlags(def.getModifiers());
55 ClassEntry superClass = def.getBaseType() != null ? ClassEntry.parse(def.getBaseType()) : null;
56 ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(ClassEntry::parse).toArray(ClassEntry[]::new);
57 return new ClassDefEntry(name, signature, access, superClass, interfaces);
58 }
59
60 public Signature getSignature() {
61 return signature;
62 }
63
64 @Override
65 public AccessFlags getAccess() {
66 return access;
67 }
68
69 @Nullable
70 public ClassEntry getSuperClass() {
71 return superClass;
72 }
73
74 public ClassEntry[] getInterfaces() {
75 return interfaces;
76 }
77
78 @Override
79 public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
80 Signature translatedSignature = translator.translate(signature);
81 String translatedName = mapping != null ? mapping.getTargetName() : name;
82 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
83 ClassEntry translatedSuper = translator.translate(superClass);
84 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new);
85 return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces);
86 }
87
88 @Override
89 public ClassDefEntry withParent(ClassEntry parent) {
90 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
new file mode 100644
index 0000000..dcbb8d9
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
@@ -0,0 +1,180 @@
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.representation.entry;
13
14import com.strobel.assembler.metadata.TypeReference;
15import cuchaz.enigma.throwables.IllegalNameException;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.NameValidator;
19
20import javax.annotation.Nullable;
21import java.util.List;
22import java.util.Objects;
23
24public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> {
25 private final String fullName;
26
27 public ClassEntry(String className) {
28 this(getOuterClass(className), getInnerName(className));
29 }
30
31 public ClassEntry(@Nullable ClassEntry parent, String className) {
32 super(parent, className);
33 if (parent != null) {
34 fullName = parent.getFullName() + "$" + name;
35 } else {
36 fullName = name;
37 }
38
39 if (parent == null && className.indexOf('.') >= 0) {
40 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
41 }
42 }
43
44 public static ClassEntry parse(TypeReference typeReference) {
45 return new ClassEntry(typeReference.getInternalName());
46 }
47
48 @Override
49 public Class<ClassEntry> getParentType() {
50 return ClassEntry.class;
51 }
52
53 @Override
54 public String getName() {
55 return this.name;
56 }
57
58 public String getFullName() {
59 return fullName;
60 }
61
62 @Override
63 public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) {
64 String translatedName = mapping != null ? mapping.getTargetName() : name;
65 return new ClassEntry(parent, translatedName);
66 }
67
68 @Override
69 public ClassEntry getContainingClass() {
70 return this;
71 }
72
73 @Override
74 public int hashCode() {
75 return fullName.hashCode();
76 }
77
78 @Override
79 public boolean equals(Object other) {
80 return other instanceof ClassEntry && equals((ClassEntry) other);
81 }
82
83 public boolean equals(ClassEntry other) {
84 return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name);
85 }
86
87 @Override
88 public boolean canConflictWith(Entry<?> entry) {
89 return true;
90 }
91
92 @Override
93 public void validateName(String name) throws IllegalNameException {
94 NameValidator.validateClassName(name, !isInnerClass());
95 }
96
97 @Override
98 public ClassEntry withParent(ClassEntry parent) {
99 return new ClassEntry(parent, name);
100 }
101
102 @Override
103 public String toString() {
104 return getFullName();
105 }
106
107 public String getPackageName() {
108 return getPackageName(this.name);
109 }
110
111 public String getSimpleName() {
112 int packagePos = name.lastIndexOf('/');
113 if (packagePos > 0) {
114 return name.substring(packagePos + 1);
115 }
116 return name;
117 }
118
119 public boolean isInnerClass() {
120 return parent != null;
121 }
122
123 @Nullable
124 public ClassEntry getOuterClass() {
125 return parent;
126 }
127
128 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
129 assert (classChain.contains(this));
130 StringBuilder buf = new StringBuilder();
131 for (ClassEntry chainEntry : classChain) {
132 if (buf.length() == 0) {
133 buf.append(chainEntry.getFullName());
134 } else {
135 buf.append("$");
136 buf.append(chainEntry.getSimpleName());
137 }
138
139 if (chainEntry == this) {
140 break;
141 }
142 }
143 return new ClassEntry(buf.toString());
144 }
145
146 public boolean isJre() {
147 String packageName = getPackageName();
148 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
149 }
150
151 public static String getPackageName(String name) {
152 int pos = name.lastIndexOf('/');
153 if (pos > 0) {
154 return name.substring(0, pos);
155 }
156 return null;
157 }
158
159 @Nullable
160 public static ClassEntry getOuterClass(String name) {
161 int index = name.lastIndexOf('$');
162 if (index >= 0) {
163 return new ClassEntry(name.substring(0, index));
164 }
165 return null;
166 }
167
168 public static String getInnerName(String name) {
169 int innerClassPos = name.lastIndexOf('$');
170 if (innerClassPos > 0) {
171 return name.substring(innerClassPos + 1);
172 }
173 return name;
174 }
175
176 @Override
177 public int compareTo(ClassEntry entry) {
178 return name.compareTo(entry.name);
179 }
180}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
new file mode 100644
index 0000000..82536c7
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
@@ -0,0 +1,7 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public interface DefEntry<P extends Entry<?>> extends Entry<P> {
6 AccessFlags getAccess();
7}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
new file mode 100644
index 0000000..1a2ca78
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
@@ -0,0 +1,99 @@
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.representation.entry;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.mapping.NameValidator;
17
18import javax.annotation.Nullable;
19import java.util.ArrayList;
20import java.util.List;
21
22public interface Entry<P extends Entry<?>> extends Translatable {
23 String getName();
24
25 @Nullable
26 P getParent();
27
28 Class<P> getParentType();
29
30 Entry<P> withParent(P parent);
31
32 boolean canConflictWith(Entry<?> entry);
33
34 @Nullable
35 default ClassEntry getContainingClass() {
36 P parent = getParent();
37 if (parent == null) {
38 return null;
39 }
40 if (parent instanceof ClassEntry) {
41 return (ClassEntry) parent;
42 }
43 return parent.getContainingClass();
44 }
45
46 default List<Entry<?>> getAncestry() {
47 P parent = getParent();
48 List<Entry<?>> entries = new ArrayList<>();
49 if (parent != null) {
50 entries.addAll(parent.getAncestry());
51 }
52 entries.add(this);
53 return entries;
54 }
55
56 @Nullable
57 @SuppressWarnings("unchecked")
58 default <E extends Entry<?>> E findAncestor(Class<E> type) {
59 List<Entry<?>> ancestry = getAncestry();
60 for (int i = ancestry.size() - 1; i >= 0; i--) {
61 Entry<?> ancestor = ancestry.get(i);
62 if (type.isAssignableFrom(ancestor.getClass())) {
63 return (E) ancestor;
64 }
65 }
66 return null;
67 }
68
69 @SuppressWarnings("unchecked")
70 default <E extends Entry<?>> Entry<P> replaceAncestor(E target, E replacement) {
71 if (replacement.equals(target)) {
72 return this;
73 }
74
75 if (equals(target)) {
76 return (Entry<P>) replacement;
77 }
78
79 P parent = getParent();
80 if (parent == null) {
81 return this;
82 }
83
84 return withParent((P) parent.replaceAncestor(target, replacement));
85 }
86
87 default void validateName(String name) throws IllegalNameException {
88 NameValidator.validateIdentifier(name);
89 }
90
91 @SuppressWarnings("unchecked")
92 @Nullable
93 default <C extends Entry<?>> Entry<C> castParent(Class<C> parentType) {
94 if (parentType.equals(getParentType())) {
95 return (Entry<C>) this;
96 }
97 return null;
98 }
99}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
new file mode 100644
index 0000000..d487f71
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
@@ -0,0 +1,61 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19import cuchaz.enigma.translation.representation.TypeDescriptor;
20
21import javax.annotation.Nullable;
22
23public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
28 super(owner, name, desc);
29 Preconditions.checkNotNull(access, "Field access cannot be null");
30 Preconditions.checkNotNull(signature, "Field signature cannot be null");
31 this.access = access;
32 this.signature = signature;
33 }
34
35 public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
36 return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access));
37 }
38
39 @Override
40 public AccessFlags getAccess() {
41 return access;
42 }
43
44 public Signature getSignature() {
45 return signature;
46 }
47
48 @Override
49 public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
50 TypeDescriptor translatedDesc = translator.translate(desc);
51 Signature translatedSignature = translator.translate(signature);
52 String translatedName = mapping != null ? mapping.getTargetName() : name;
53 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
54 return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess);
55 }
56
57 @Override
58 public FieldDefEntry withParent(ClassEntry owner) {
59 return new FieldDefEntry(owner, this.name, this.desc, signature, access);
60 }
61}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
new file mode 100644
index 0000000..2ec2471
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
@@ -0,0 +1,86 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.TypeDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<FieldEntry> {
23 protected final TypeDescriptor desc;
24
25 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) {
26 super(parent, name);
27
28 Preconditions.checkNotNull(parent, "Owner cannot be null");
29 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
30
31 this.desc = desc;
32 }
33
34 public static FieldEntry parse(String owner, String name, String desc) {
35 return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
36 }
37
38 @Override
39 public Class<ClassEntry> getParentType() {
40 return ClassEntry.class;
41 }
42
43 public TypeDescriptor getDesc() {
44 return this.desc;
45 }
46
47 @Override
48 public FieldEntry withParent(ClassEntry parent) {
49 return new FieldEntry(parent, this.name, this.desc);
50 }
51
52 @Override
53 protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 String translatedName = mapping != null ? mapping.getTargetName() : name;
55 return new FieldEntry(parent, translatedName, translator.translate(desc));
56 }
57
58 @Override
59 public int hashCode() {
60 return Utils.combineHashesOrdered(this.parent, this.name, this.desc);
61 }
62
63 @Override
64 public boolean equals(Object other) {
65 return other instanceof FieldEntry && equals((FieldEntry) other);
66 }
67
68 public boolean equals(FieldEntry other) {
69 return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc);
70 }
71
72 @Override
73 public boolean canConflictWith(Entry<?> entry) {
74 return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent);
75 }
76
77 @Override
78 public String toString() {
79 return this.parent.getFullName() + "." + this.name + ":" + this.desc;
80 }
81
82 @Override
83 public int compareTo(FieldEntry entry) {
84 return (name + desc.toString()).compareTo(entry.name + entry.desc.toString());
85 }
86}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
new file mode 100644
index 0000000..86bdf61
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
@@ -0,0 +1,45 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableDefEntry extends LocalVariableEntry {
16 protected final TypeDescriptor desc;
17
18 public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc) {
19 super(ownerEntry, index, name, parameter);
20 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
21
22 this.desc = desc;
23 }
24
25 public TypeDescriptor getDesc() {
26 return desc;
27 }
28
29 @Override
30 public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
31 TypeDescriptor translatedDesc = translator.translate(desc);
32 String translatedName = mapping != null ? mapping.getTargetName() : name;
33 return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc);
34 }
35
36 @Override
37 public LocalVariableDefEntry withParent(MethodEntry entry) {
38 return new LocalVariableDefEntry(entry, index, name, parameter, desc);
39 }
40
41 @Override
42 public String toString() {
43 return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
new file mode 100644
index 0000000..df96b59
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
@@ -0,0 +1,92 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.utils.Utils;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Comparable<LocalVariableEntry> {
16
17 protected final int index;
18 protected final boolean parameter;
19
20 @Deprecated
21 public LocalVariableEntry(MethodEntry parent, int index, String name) {
22 this(parent, index, name, true);
23 }
24
25 public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter) {
26 super(parent, name);
27
28 Preconditions.checkNotNull(parent, "Variable owner cannot be null");
29 Preconditions.checkArgument(index >= 0, "Index must be positive");
30
31 this.index = index;
32 this.parameter = parameter;
33 }
34
35 @Override
36 public Class<MethodEntry> getParentType() {
37 return MethodEntry.class;
38 }
39
40 public boolean isParameter() {
41 return this.parameter;
42 }
43
44 public int getIndex() {
45 return index;
46 }
47
48 @Override
49 public String getName() {
50 return this.name;
51 }
52
53 @Override
54 public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) {
55 String translatedName = mapping != null ? mapping.getTargetName() : name;
56 return new LocalVariableEntry(parent, index, translatedName, parameter);
57 }
58
59 @Override
60 public LocalVariableEntry withParent(MethodEntry parent) {
61 return new LocalVariableEntry(parent, index, name, parameter);
62 }
63
64 @Override
65 public int hashCode() {
66 return Utils.combineHashesOrdered(this.parent, this.index);
67 }
68
69 @Override
70 public boolean equals(Object other) {
71 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
72 }
73
74 public boolean equals(LocalVariableEntry other) {
75 return this.parent.equals(other.parent) && this.index == other.index;
76 }
77
78 @Override
79 public boolean canConflictWith(Entry<?> entry) {
80 return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent);
81 }
82
83 @Override
84 public String toString() {
85 return this.parent + "(" + this.index + ":" + this.name + ")";
86 }
87
88 @Override
89 public int compareTo(LocalVariableEntry entry) {
90 return Integer.compare(index, entry.index);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
new file mode 100644
index 0000000..3ecd470
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
@@ -0,0 +1,77 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.MethodDescriptor;
19import cuchaz.enigma.translation.representation.Signature;
20
21import javax.annotation.Nullable;
22
23public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) {
28 super(owner, name, descriptor);
29 Preconditions.checkNotNull(access, "Method access cannot be null");
30 Preconditions.checkNotNull(signature, "Method signature cannot be null");
31 this.access = access;
32 this.signature = signature;
33 }
34
35 public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
36 return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
37 }
38
39 @Override
40 public AccessFlags getAccess() {
41 return access;
42 }
43
44 public Signature getSignature() {
45 return signature;
46 }
47
48 @Override
49 public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
50 MethodDescriptor translatedDesc = translator.translate(descriptor);
51 Signature translatedSignature = translator.translate(signature);
52 String translatedName = mapping != null ? mapping.getTargetName() : name;
53 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
54 return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess);
55 }
56
57 @Override
58 public MethodDefEntry withParent(ClassEntry parent) {
59 return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access);
60 }
61
62 public int getArgumentIndex(ClassDefEntry ownerEntry, int localVariableIndex) {
63 int argumentIndex = localVariableIndex;
64
65 // Enum constructors have an implicit "name" and "ordinal" parameter as well as "this"
66 if (ownerEntry.getAccess().isEnum() && getName().startsWith("<")) {
67 argumentIndex -= 2;
68 }
69
70 // If we're not static, "this" is bound to index 0
71 if (!getAccess().isStatic()) {
72 argumentIndex -= 1;
73 }
74
75 return argumentIndex;
76 }
77}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
new file mode 100644
index 0000000..3a1dbb3
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
@@ -0,0 +1,95 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.MethodDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable<MethodEntry> {
23
24 protected final MethodDescriptor descriptor;
25
26 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) {
27 super(parent, name);
28
29 Preconditions.checkNotNull(parent, "Parent cannot be null");
30 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
31
32 this.descriptor = descriptor;
33 }
34
35 public static MethodEntry parse(String owner, String name, String desc) {
36 return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
37 }
38
39 @Override
40 public Class<ClassEntry> getParentType() {
41 return ClassEntry.class;
42 }
43
44 public MethodDescriptor getDesc() {
45 return this.descriptor;
46 }
47
48 public boolean isConstructor() {
49 return name.equals("<init>") || name.equals("<clinit>");
50 }
51
52 @Override
53 public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 String translatedName = mapping != null ? mapping.getTargetName() : name;
55 return new MethodEntry(parent, translatedName, translator.translate(descriptor));
56 }
57
58 @Override
59 public MethodEntry withParent(ClassEntry parent) {
60 return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor);
61 }
62
63 @Override
64 public int hashCode() {
65 return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor);
66 }
67
68 @Override
69 public boolean equals(Object other) {
70 return other instanceof MethodEntry && equals((MethodEntry) other);
71 }
72
73 public boolean equals(MethodEntry other) {
74 return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
75 }
76
77 @Override
78 public boolean canConflictWith(Entry<?> entry) {
79 if (entry instanceof MethodEntry) {
80 MethodEntry methodEntry = (MethodEntry) entry;
81 return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor);
82 }
83 return false;
84 }
85
86 @Override
87 public String toString() {
88 return this.parent.getFullName() + "." + this.name + this.descriptor;
89 }
90
91 @Override
92 public int compareTo(MethodEntry entry) {
93 return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString());
94 }
95}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
new file mode 100644
index 0000000..7ba7c19
--- /dev/null
+++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
@@ -0,0 +1,71 @@
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.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMap;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.ResolutionStrategy;
21
22import javax.annotation.Nullable;
23
24public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
25 protected final P parent;
26 protected final String name;
27
28 protected ParentedEntry(P parent, String name) {
29 this.parent = parent;
30 this.name = name;
31
32 Preconditions.checkNotNull(name, "Name cannot be null");
33 }
34
35 @Override
36 public abstract ParentedEntry<P> withParent(P parent);
37
38 protected abstract ParentedEntry<P> translate(Translator translator, @Nullable EntryMapping mapping);
39
40 @Override
41 public String getName() {
42 return name;
43 }
44
45 @Override
46 @Nullable
47 public P getParent() {
48 return parent;
49 }
50
51 @Override
52 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
53 P parent = getParent();
54 EntryMapping mapping = resolveMapping(resolver, mappings);
55 if (parent == null) {
56 return translate(translator, mapping);
57 }
58 P translatedParent = translator.translate(parent);
59 return withParent(translatedParent).translate(translator, mapping);
60 }
61
62 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings) {
63 for (ParentedEntry<P> entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) {
64 EntryMapping mapping = mappings.get(entry);
65 if (mapping != null) {
66 return mapping;
67 }
68 }
69 return null;
70 }
71}