summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar 2xsaiko2021-07-08 16:30:39 +0200
committerGravatar GitHub2021-07-08 16:30:39 +0200
commit8efb62490ec153246d467a1a72c513781db887bf (patch)
tree0f2eedd97d9c0d95544ff7d24f2b479cf10271fe
parentAdd --single-class-tree argument that puts all classes into deobf panel & hid... (diff)
downloadenigma-8efb62490ec153246d467a1a72c513781db887bf.tar.gz
enigma-8efb62490ec153246d467a1a72c513781db887bf.tar.xz
enigma-8efb62490ec153246d467a1a72c513781db887bf.zip
Entry Changes (#364)
* Initial refactor: Allow EntryMapping to have null targetName, add EntryChange in favor of entry changing methods in GuiController * Fix resetting name not actually removing it, and renaming a class causing name collisions with itself Closes #246. * Use name proposer for setting default deobf name Closes #314 * Make network protocol use EntryChange directly * Handle writing other data correctly when the deobf name is null * b * Add some new abstraction stuff * Use pattern matching instanceof * Move classes out of newabstraction package * Make EntryChange final * Regenerate equals and hashCode * Convert EntryMapping to record * Make TristateChange final * Safety guard null accessModifier initialization
-rw-r--r--enigma-server/docs/protocol.md164
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java12
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java9
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java64
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java51
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java66
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java42
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java56
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java47
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java130
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java16
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java56
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java47
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java66
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java55
-rw-r--r--enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java41
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java22
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java99
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java22
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java22
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java2
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java19
-rw-r--r--enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java7
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java97
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java67
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java53
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java42
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java13
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java56
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java96
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java9
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java46
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java2
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java6
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java11
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java8
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java28
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java14
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java12
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java5
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java78
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java10
-rw-r--r--enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java14
-rw-r--r--enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java20
53 files changed, 872 insertions, 1000 deletions
diff --git a/enigma-server/docs/protocol.md b/enigma-server/docs/protocol.md
index c14ecb81..83ef4c01 100644
--- a/enigma-server/docs/protocol.md
+++ b/enigma-server/docs/protocol.md
@@ -75,21 +75,15 @@ struct Packet {
75The IDs for client-to-server packets are as follows: 75The IDs for client-to-server packets are as follows:
76- 0: `Login` 76- 0: `Login`
77- 1: `ConfirmChange` 77- 1: `ConfirmChange`
78- 2: `Rename`
79- 3: `RemoveMapping`
80- 4: `ChangeDocs`
81- 5: `MarkDeobfuscated`
82- 6: `Message` 78- 6: `Message`
79- 7: `EntryChange`
83 80
84The IDs for server-to-client packets are as follows: 81The IDs for server-to-client packets are as follows:
85- 0: `Kick` 82- 0: `Kick`
86- 1: `SyncMappings` 83- 1: `SyncMappings`
87- 2: `Rename`
88- 3: `RemoveMapping`
89- 4: `ChangeDocs`
90- 5: `MarkDeobfuscated`
91- 6: `Message` 84- 6: `Message`
92- 7: `UserList` 85- 7: `UserList`
86- 8: `EntryChange`
93 87
94### The utf struct 88### The utf struct
95```c 89```c
@@ -196,6 +190,45 @@ struct Message {
196- `entry`: The entry that was modified. 190- `entry`: The entry that was modified.
197- `new_name`: The new name for the entry. 191- `new_name`: The new name for the entry.
198 192
193### The entry_change struct
194```c
195typedef enum tristate_change {
196 TRISTATE_CHANGE_UNCHANGED = 0,
197 TRISTATE_CHANGE_RESET = 1,
198 TRISTATE_CHANGE_SET = 2
199} tristate_change_t;
200
201typedef enum access_modifier {
202 ACCESS_MODIFIER_UNCHANGED = 0,
203 ACCESS_MODIFIER_PUBLIC = 1,
204 ACCESS_MODIFIER_PROTECTED = 2,
205 ACCESS_MODIFIER_PRIVATE = 3
206} access_modifier_t;
207
208// Contains 4 packed values:
209// bitmask type
210// 00000011 tristate_change_t deobf_name_change;
211// 00001100 tristate_change_t access_change;
212// 00110000 tristate_change_t javadoc_change;
213// 11000000 access_modifier_t access_modifiers;
214typedef uint8_t entry_change_flags;
215
216struct entry_change {
217 Entry entry;
218 entry_change_flags flags;
219 if <deobf_name_change == TRISTATE_CHANGE_SET> {
220 utf deobf_name;
221 }
222 if <javadoc_change == TRISTATE_CHANGE_SET> {
223 utf javadoc;
224 }
225}
226```
227- `entry`: The entry this change gets applied to.
228- `flags`: See definition of `entry_change_flags`.
229- `deobf_name`: The new deobfuscated name, if deobf_name_change == TRISTATE_CHANGE_SET
230- `javadoc`: The new javadoc, if javadoc_change == TRISTATE_CHANGE_SET
231- `access_modifiers`: The new access modifier, if access_change == TRISTATE_CHANGE_SET (otherwise 0)
199 232
200### Login (client-to-server) 233### Login (client-to-server)
201```c 234```c
@@ -223,44 +256,6 @@ struct ConfirmChangeC2SPacket {
223``` 256```
224- `sync_id`: the sync ID to confirm. 257- `sync_id`: the sync ID to confirm.
225 258
226### Rename (client-to-server)
227```c
228struct RenameC2SPacket {
229 Entry obf_entry;
230 utf new_name;
231 boolean refresh_class_tree;
232}
233```
234- `obf_entry`: the obfuscated name and descriptor of the entry to rename.
235- `new_name`: what to rename the entry to.
236- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change.
237
238### RemoveMapping (client-to-server)
239```c
240struct RemoveMappingC2SPacket {
241 Entry obf_entry;
242}
243```
244- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for.
245
246### ChangeDocs (client-to-server)
247```c
248struct ChangeDocsC2SPacket {
249 Entry obf_entry;
250 utf new_docs;
251}
252```
253- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for.
254- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation.
255
256### MarkDeobfuscated (client-to-server)
257```c
258struct MarkDeobfuscatedC2SPacket {
259 Entry obf_entry;
260}
261```
262- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated.
263
264### Message (client-to-server) 259### Message (client-to-server)
265```c 260```c
266struct MessageC2SPacket { 261struct MessageC2SPacket {
@@ -269,6 +264,14 @@ struct MessageC2SPacket {
269``` 264```
270- `message`: The text message the user sent. 265- `message`: The text message the user sent.
271 266
267### EntryChange (client-to-server)
268```c
269struct EntryChangeC2SPacket {
270 entry_change change;
271}
272```
273- `change`: The change to apply.
274
272### Kick (server-to-client) 275### Kick (server-to-client)
273```c 276```c
274struct KickS2CPacket { 277struct KickS2CPacket {
@@ -286,13 +289,8 @@ struct SyncMappingsS2CPacket {
286struct MappingNode { 289struct MappingNode {
287 NoParentEntry obf_entry; 290 NoParentEntry obf_entry;
288 boolean is_named; 291 boolean is_named;
289 if<is_named> { 292 utf name;
290 utf name; 293 utf javadoc;
291 boolean has_javadoc;
292 if<has_javadoc> {
293 utf javadoc;
294 }
295 }
296 unsigned short children_count; 294 unsigned short children_count;
297 MappingNode children[children_count]; 295 MappingNode children[children_count];
298} 296}
@@ -300,56 +298,10 @@ typedef { Entry but without the has_parent or parent fields } NoParentEntry;
300``` 298```
301- `roots`: The root mapping nodes, containing all the entries without parents. 299- `roots`: The root mapping nodes, containing all the entries without parents.
302- `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry. 300- `obf_entry`: The value of a node, containing the obfuscated name and descriptor of the entry.
303- `name`: The deobfuscated name of the entry, if it has a mapping. 301- `name`: The deobfuscated name of the entry, if it exists, otherwise the empty string.
304- `javadoc`: The documentation for the entry, if it is named and has documentation. 302- `javadoc`: The documentation for the entry, if it exists, otherwise the empty string.
305- `children`: The children of this node 303- `children`: The children of this node
306 304
307### Rename (server-to-client)
308```c
309struct RenameS2CPacket {
310 unsigned short sync_id;
311 Entry obf_entry;
312 utf new_name;
313 boolean refresh_class_tree;
314}
315```
316- `sync_id`: the sync ID of the change for locking purposes.
317- `obf_entry`: the obfuscated name and descriptor of the entry to rename.
318- `new_name`: what to rename the entry to.
319- `refresh_class_tree`: whether the class tree on the sidebar of Enigma needs refreshing as a result of this change.
320
321### RemoveMapping (server-to-client)
322```c
323struct RemoveMappingS2CPacket {
324 unsigned short sync_id;
325 Entry obf_entry;
326}
327```
328- `sync_id`: the sync ID of the change for locking purposes.
329- `obf_entry`: the obfuscated name and descriptor of the entry to remove the mapping for.
330
331### ChangeDocs (server-to-client)
332```c
333struct ChangeDocsS2CPacket {
334 unsigned short sync_id;
335 Entry obf_entry;
336 utf new_docs;
337}
338```
339- `sync_id`: the sync ID of the change for locking purposes.
340- `obf_entry`: the obfuscated name and descriptor of the entry to change the documentation for.
341- `new_docs`: the new documentation for this entry, or an empty string to remove the documentation.
342
343### MarkDeobfuscated (server-to-client)
344```c
345struct MarkDeobfuscatedS2CPacket {
346 unsigned short sync_id;
347 Entry obf_entry;
348}
349```
350- `sync_id`: the sync ID of the change for locking purposes.
351- `obf_entry`: the obfuscated name and descriptor of the entry to mark as deobfuscated.
352
353### Message (server-to-client) 305### Message (server-to-client)
354```c 306```c
355struct MessageS2CPacket { 307struct MessageS2CPacket {
@@ -364,3 +316,13 @@ struct UserListS2CPacket {
364 utf user[len]; 316 utf user[len];
365} 317}
366``` 318```
319
320### EntryChange (server-to-client)
321```c
322struct EntryChangeS2CPacket {
323 uint16_t sync_id;
324 entry_change change;
325}
326```
327- `sync_id`: The sync ID of the change for locking purposes.
328- `change`: The change to apply. \ No newline at end of file
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java
index 1b0191be..a651fe84 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/ClientPacketHandler.java
@@ -1,24 +1,16 @@
1package cuchaz.enigma.network; 1package cuchaz.enigma.network;
2 2
3import cuchaz.enigma.analysis.EntryReference; 3import cuchaz.enigma.translation.mapping.EntryChange;
4import cuchaz.enigma.translation.mapping.EntryMapping; 4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.tree.EntryTree; 5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.network.packet.Packet; 6import cuchaz.enigma.network.packet.Packet;
7import cuchaz.enigma.translation.representation.entry.Entry;
8import cuchaz.enigma.utils.validation.ValidationContext;
9 7
10import java.util.List; 8import java.util.List;
11 9
12public interface ClientPacketHandler { 10public interface ClientPacketHandler {
13 void openMappings(EntryTree<EntryMapping> mappings); 11 void openMappings(EntryTree<EntryMapping> mappings);
14 12
15 void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree); 13 boolean applyChangeFromServer(EntryChange<?> change);
16
17 void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference);
18
19 void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs);
20
21 void markAsDeobfuscated(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference);
22 14
23 void disconnectIfConnected(String reason); 15 void disconnectIfConnected(String reason);
24 16
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
index 75981c3b..1ce359b6 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/EnigmaServer.java
@@ -8,6 +8,7 @@ import java.util.*;
8import java.util.concurrent.CopyOnWriteArrayList; 8import java.util.concurrent.CopyOnWriteArrayList;
9 9
10import cuchaz.enigma.network.packet.*; 10import cuchaz.enigma.network.packet.*;
11import cuchaz.enigma.translation.mapping.EntryChange;
11import cuchaz.enigma.translation.mapping.EntryMapping; 12import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.EntryRemapper; 13import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.representation.entry.Entry; 14import cuchaz.enigma.translation.representation.entry.Entry;
@@ -16,7 +17,7 @@ public abstract class EnigmaServer {
16 17
17 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347 18 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347
18 public static final int DEFAULT_PORT = 34712; 19 public static final int DEFAULT_PORT = 34712;
19 public static final int PROTOCOL_VERSION = 0; 20 public static final int PROTOCOL_VERSION = 1;
20 public static final int CHECKSUM_SIZE = 20; 21 public static final int CHECKSUM_SIZE = 20;
21 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet 22 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet
22 23
@@ -234,11 +235,11 @@ public abstract class EnigmaServer {
234 235
235 public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) { 236 public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) {
236 EntryMapping oldMapping = mappings.getDeobfMapping(entry); 237 EntryMapping oldMapping = mappings.getDeobfMapping(entry);
237 String oldName = oldMapping == null ? null : oldMapping.getTargetName(); 238 String oldName = oldMapping.targetName();
238 if (oldName == null) { 239 if (oldName == null) {
239 sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry)); 240 sendPacket(client, new EntryChangeS2CPacket(DUMMY_SYNC_ID, EntryChange.modify(entry).clearDeobfName()));
240 } else { 241 } else {
241 sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree)); 242 sendPacket(client, new EntryChangeS2CPacket(0, EntryChange.modify(entry).withDeobfName(oldName)));
242 } 243 }
243 } 244 }
244 245
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
deleted file mode 100644
index f3e07c2d..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
+++ /dev/null
@@ -1,64 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.network.EnigmaServer;
9import cuchaz.enigma.network.Message;
10import cuchaz.enigma.network.ServerPacketHandler;
11import cuchaz.enigma.translation.representation.entry.Entry;
12import cuchaz.enigma.utils.validation.PrintValidatable;
13import cuchaz.enigma.utils.validation.ValidationContext;
14
15public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> {
16 private Entry<?> entry;
17 private String newDocs;
18
19 ChangeDocsC2SPacket() {
20 }
21
22 public ChangeDocsC2SPacket(Entry<?> entry, String newDocs) {
23 this.entry = entry;
24 this.newDocs = newDocs;
25 }
26
27 @Override
28 public void read(DataInput input) throws IOException {
29 this.entry = PacketHelper.readEntry(input);
30 this.newDocs = PacketHelper.readString(input);
31 }
32
33 @Override
34 public void write(DataOutput output) throws IOException {
35 PacketHelper.writeEntry(output, entry);
36 PacketHelper.writeString(output, newDocs);
37 }
38
39 @Override
40 public void handle(ServerPacketHandler handler) {
41 ValidationContext vc = new ValidationContext();
42 vc.setActiveElement(PrintValidatable.INSTANCE);
43
44 EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry);
45
46 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
47 if (!valid) {
48 String oldDocs = mapping == null ? null : mapping.getJavadoc();
49 handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs));
50 return;
51 }
52
53 if (mapping == null) {
54 mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName());
55 }
56 handler.getServer().getMappings().mapFromObf(vc, entry, mapping.withDocs(newDocs.isBlank() ? null : newDocs));
57
58 if (!vc.canProceed()) return;
59
60 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
61 handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs));
62 handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry));
63 }
64}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
deleted file mode 100644
index 78fa4fa9..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.network.ClientPacketHandler;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.validation.PrintValidatable;
11import cuchaz.enigma.utils.validation.ValidationContext;
12
13public class ChangeDocsS2CPacket implements Packet<ClientPacketHandler> {
14 private int syncId;
15 private Entry<?> entry;
16 private String newDocs;
17
18 ChangeDocsS2CPacket() {
19 }
20
21 public ChangeDocsS2CPacket(int syncId, Entry<?> entry, String newDocs) {
22 this.syncId = syncId;
23 this.entry = entry;
24 this.newDocs = newDocs;
25 }
26
27 @Override
28 public void read(DataInput input) throws IOException {
29 this.syncId = input.readUnsignedShort();
30 this.entry = PacketHelper.readEntry(input);
31 this.newDocs = PacketHelper.readString(input);
32 }
33
34 @Override
35 public void write(DataOutput output) throws IOException {
36 output.writeShort(syncId);
37 PacketHelper.writeEntry(output, entry);
38 PacketHelper.writeString(output, newDocs);
39 }
40
41 @Override
42 public void handle(ClientPacketHandler controller) {
43 ValidationContext vc = new ValidationContext();
44 vc.setActiveElement(PrintValidatable.INSTANCE);
45
46 controller.changeDocs(vc, new EntryReference<>(entry, entry.getName()), newDocs);
47
48 if (!vc.canProceed()) return;
49 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
50 }
51}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java
new file mode 100644
index 00000000..b97877c6
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeC2SPacket.java
@@ -0,0 +1,66 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.Message;
8import cuchaz.enigma.network.ServerPacketHandler;
9import cuchaz.enigma.translation.mapping.EntryChange;
10import cuchaz.enigma.translation.mapping.EntryUtil;
11import cuchaz.enigma.utils.validation.PrintValidatable;
12import cuchaz.enigma.utils.validation.ValidationContext;
13
14public class EntryChangeC2SPacket implements Packet<ServerPacketHandler> {
15
16 private EntryChange<?> change;
17
18 EntryChangeC2SPacket() {
19 }
20
21 public EntryChangeC2SPacket(EntryChange<?> change) {
22 this.change = change;
23 }
24
25 @Override
26 public void read(DataInput input) throws IOException {
27 this.change = PacketHelper.readEntryChange(input);
28 }
29
30 @Override
31 public void write(DataOutput output) throws IOException {
32 PacketHelper.writeEntryChange(output, change);
33 }
34
35 @Override
36 public void handle(ServerPacketHandler handler) {
37 ValidationContext vc = new ValidationContext();
38 vc.setActiveElement(PrintValidatable.INSTANCE);
39
40 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), this.change.getTarget());
41
42 if (valid) {
43 EntryUtil.applyChange(vc, handler.getServer().getMappings(), this.change);
44 valid = vc.canProceed();
45 }
46
47 if (!valid) {
48 handler.getServer().sendCorrectMapping(handler.getClient(), this.change.getTarget(), true);
49 return;
50 }
51
52 int syncId = handler.getServer().lockEntry(handler.getClient(), this.change.getTarget());
53 handler.getServer().sendToAllExcept(handler.getClient(), new EntryChangeS2CPacket(syncId, this.change));
54
55 if (this.change.getDeobfName().isSet()) {
56 handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), this.change.getTarget(), this.change.getDeobfName().getNewValue()));
57 } else if (this.change.getDeobfName().isReset()) {
58 handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), this.change.getTarget()));
59 }
60
61 if (!this.change.getJavadoc().isUnchanged()) {
62 handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), this.change.getTarget()));
63 }
64 }
65
66}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java
new file mode 100644
index 00000000..a237b916
--- /dev/null
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/EntryChangeS2CPacket.java
@@ -0,0 +1,42 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.ClientPacketHandler;
8import cuchaz.enigma.translation.mapping.EntryChange;
9
10public class EntryChangeS2CPacket implements Packet<ClientPacketHandler> {
11
12 private int syncId;
13 private EntryChange<?> change;
14
15 public EntryChangeS2CPacket(int syncId, EntryChange<?> change) {
16 this.syncId = syncId;
17 this.change = change;
18 }
19
20 EntryChangeS2CPacket() {
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.syncId = input.readUnsignedShort();
26 this.change = PacketHelper.readEntryChange(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 output.writeShort(this.syncId);
32 PacketHelper.writeEntryChange(output, this.change);
33 }
34
35 @Override
36 public void handle(ClientPacketHandler handler) {
37 if (handler.applyChangeFromServer(this.change)) {
38 handler.sendPacket(new ConfirmChangeC2SPacket(this.syncId));
39 }
40 }
41
42}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
deleted file mode 100644
index 732c7448..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
+++ /dev/null
@@ -1,56 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.Message;
8import cuchaz.enigma.network.ServerPacketHandler;
9import cuchaz.enigma.translation.mapping.EntryMapping;
10import cuchaz.enigma.translation.representation.entry.Entry;
11import cuchaz.enigma.utils.validation.PrintValidatable;
12import cuchaz.enigma.utils.validation.ValidationContext;
13
14public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> {
15 private Entry<?> entry;
16
17 MarkDeobfuscatedC2SPacket() {
18 }
19
20 public MarkDeobfuscatedC2SPacket(Entry<?> entry) {
21 this.entry = entry;
22 }
23
24 @Override
25 public void read(DataInput input) throws IOException {
26 this.entry = PacketHelper.readEntry(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 PacketHelper.writeEntry(output, entry);
32 }
33
34 @Override
35 public void handle(ServerPacketHandler handler) {
36 ValidationContext vc = new ValidationContext();
37 vc.setActiveElement(PrintValidatable.INSTANCE);
38
39 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
40
41 if (!valid) {
42 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
43 return;
44 }
45
46 handler.getServer().getMappings().mapFromObf(vc, entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()));
47
48 if (!vc.canProceed()) return;
49
50 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated");
51
52 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
53 handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry));
54 handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry));
55 }
56}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
deleted file mode 100644
index 969d13c5..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
+++ /dev/null
@@ -1,47 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.network.ClientPacketHandler;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.validation.PrintValidatable;
11import cuchaz.enigma.utils.validation.ValidationContext;
12
13public class MarkDeobfuscatedS2CPacket implements Packet<ClientPacketHandler> {
14 private int syncId;
15 private Entry<?> entry;
16
17 MarkDeobfuscatedS2CPacket() {
18 }
19
20 public MarkDeobfuscatedS2CPacket(int syncId, Entry<?> entry) {
21 this.syncId = syncId;
22 this.entry = entry;
23 }
24
25 @Override
26 public void read(DataInput input) throws IOException {
27 this.syncId = input.readUnsignedShort();
28 this.entry = PacketHelper.readEntry(input);
29 }
30
31 @Override
32 public void write(DataOutput output) throws IOException {
33 output.writeShort(syncId);
34 PacketHelper.writeEntry(output, entry);
35 }
36
37 @Override
38 public void handle(ClientPacketHandler controller) {
39 ValidationContext vc = new ValidationContext();
40 vc.setActiveElement(PrintValidatable.INSTANCE);
41
42 controller.markAsDeobfuscated(vc, new EntryReference<>(entry, entry.getName()));
43
44 if (!vc.canProceed()) return;
45 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
46 }
47}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
index 464606e0..2649cdc4 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
@@ -1,18 +1,17 @@
1package cuchaz.enigma.network.packet; 1package cuchaz.enigma.network.packet;
2 2
3import cuchaz.enigma.translation.representation.MethodDescriptor;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.translation.representation.entry.FieldEntry;
8import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import java.io.DataInput; 3import java.io.DataInput;
12import java.io.DataOutput; 4import java.io.DataOutput;
13import java.io.IOException; 5import java.io.IOException;
14import java.nio.charset.StandardCharsets; 6import java.nio.charset.StandardCharsets;
15 7
8import cuchaz.enigma.translation.mapping.AccessModifier;
9import cuchaz.enigma.translation.mapping.EntryChange;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.*;
13import cuchaz.enigma.utils.TristateChange;
14
16public class PacketHelper { 15public class PacketHelper {
17 16
18 private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3; 17 private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3;
@@ -37,35 +36,40 @@ public class PacketHelper {
37 } 36 }
38 37
39 switch (type) { 38 switch (type) {
40 case ENTRY_CLASS: { 39 case ENTRY_CLASS: {
41 if (parent != null && !(parent instanceof ClassEntry)) { 40 if (parent != null && !(parent instanceof ClassEntry)) {
42 throw new IOException("Class requires class parent"); 41 throw new IOException("Class requires class parent");
42 }
43
44 return new ClassEntry((ClassEntry) parent, name, javadocs);
43 } 45 }
44 return new ClassEntry((ClassEntry) parent, name, javadocs); 46 case ENTRY_FIELD: {
45 } 47 if (!(parent instanceof ClassEntry parentClass)) {
46 case ENTRY_FIELD: { 48 throw new IOException("Field requires class parent");
47 if (!(parent instanceof ClassEntry)) { 49 }
48 throw new IOException("Field requires class parent"); 50
51 TypeDescriptor desc = new TypeDescriptor(readString(input));
52 return new FieldEntry(parentClass, name, desc, javadocs);
49 } 53 }
50 TypeDescriptor desc = new TypeDescriptor(readString(input)); 54 case ENTRY_METHOD: {
51 return new FieldEntry((ClassEntry) parent, name, desc, javadocs); 55 if (!(parent instanceof ClassEntry parentClass)) {
52 } 56 throw new IOException("Method requires class parent");
53 case ENTRY_METHOD: { 57 }
54 if (!(parent instanceof ClassEntry)) { 58
55 throw new IOException("Method requires class parent"); 59 MethodDescriptor desc = new MethodDescriptor(readString(input));
60 return new MethodEntry(parentClass, name, desc, javadocs);
56 } 61 }
57 MethodDescriptor desc = new MethodDescriptor(readString(input)); 62 case ENTRY_LOCAL_VAR: {
58 return new MethodEntry((ClassEntry) parent, name, desc, javadocs); 63 if (!(parent instanceof MethodEntry parentMethod)) {
59 } 64 throw new IOException("Local variable requires method parent");
60 case ENTRY_LOCAL_VAR: { 65 }
61 if (!(parent instanceof MethodEntry)) { 66
62 throw new IOException("Local variable requires method parent"); 67 int index = input.readUnsignedShort();
68 boolean parameter = input.readBoolean();
69 return new LocalVariableEntry(parentMethod, index, name, parameter, javadocs);
63 } 70 }
64 int index = input.readUnsignedShort(); 71 default:
65 boolean parameter = input.readBoolean(); 72 throw new IOException("Received unknown entry type " + type);
66 return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs);
67 }
68 default: throw new IOException("Received unknown entry type " + type);
69 } 73 }
70 } 74 }
71 75
@@ -132,4 +136,64 @@ public class PacketHelper {
132 output.write(bytes); 136 output.write(bytes);
133 } 137 }
134 138
139 public static EntryChange<?> readEntryChange(DataInput input) throws IOException {
140 Entry<?> e = readEntry(input);
141 EntryChange<?> change = EntryChange.modify(e);
142
143 int flags = input.readUnsignedByte();
144 TristateChange.Type deobfNameT = TristateChange.Type.values()[flags & 0x3];
145 TristateChange.Type accessT = TristateChange.Type.values()[flags >> 2 & 0x3];
146 TristateChange.Type javadocT = TristateChange.Type.values()[flags >> 4 & 0x3];
147
148 switch (deobfNameT) {
149 case RESET:
150 change = change.clearDeobfName();
151 break;
152 case SET:
153 change = change.withDeobfName(readString(input));
154 break;
155 }
156
157 switch (accessT) {
158 case RESET:
159 change = change.clearAccess();
160 break;
161 case SET:
162 change = change.withAccess(AccessModifier.values()[flags >> 6 & 0x3]);
163 break;
164 }
165
166 switch (javadocT) {
167 case RESET:
168 change = change.clearJavadoc();
169 break;
170 case SET:
171 change = change.withJavadoc(readString(input));
172 break;
173 }
174
175 return change;
176 }
177
178 public static void writeEntryChange(DataOutput output, EntryChange<?> change) throws IOException {
179 writeEntry(output, change.getTarget());
180 int flags = change.getDeobfName().getType().ordinal() |
181 change.getAccess().getType().ordinal() << 2 |
182 change.getJavadoc().getType().ordinal() << 4;
183
184 if (change.getAccess().isSet()) {
185 flags |= change.getAccess().getNewValue().ordinal() << 6;
186 }
187
188 output.writeByte(flags);
189
190 if (change.getDeobfName().isSet()) {
191 writeString(output, change.getDeobfName().getNewValue());
192 }
193
194 if (change.getJavadoc().isSet()) {
195 writeString(output, change.getJavadoc().getNewValue());
196 }
197 }
198
135} 199}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
index 3b8af81c..59999ccc 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
@@ -1,12 +1,12 @@
1package cuchaz.enigma.network.packet; 1package cuchaz.enigma.network.packet;
2 2
3import cuchaz.enigma.network.ClientPacketHandler;
4import cuchaz.enigma.network.ServerPacketHandler;
5
6import java.util.HashMap; 3import java.util.HashMap;
7import java.util.Map; 4import java.util.Map;
8import java.util.function.Supplier; 5import java.util.function.Supplier;
9 6
7import cuchaz.enigma.network.ClientPacketHandler;
8import cuchaz.enigma.network.ServerPacketHandler;
9
10public class PacketRegistry { 10public class PacketRegistry {
11 11
12 private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>(); 12 private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>();
@@ -27,20 +27,14 @@ public class PacketRegistry {
27 static { 27 static {
28 registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new); 28 registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::new);
29 registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new); 29 registerC2S(1, ConfirmChangeC2SPacket.class, ConfirmChangeC2SPacket::new);
30 registerC2S(2, RenameC2SPacket.class, RenameC2SPacket::new);
31 registerC2S(3, RemoveMappingC2SPacket.class, RemoveMappingC2SPacket::new);
32 registerC2S(4, ChangeDocsC2SPacket.class, ChangeDocsC2SPacket::new);
33 registerC2S(5, MarkDeobfuscatedC2SPacket.class, MarkDeobfuscatedC2SPacket::new);
34 registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new); 30 registerC2S(6, MessageC2SPacket.class, MessageC2SPacket::new);
31 registerC2S(7, EntryChangeC2SPacket.class, EntryChangeC2SPacket::new);
35 32
36 registerS2C(0, KickS2CPacket.class, KickS2CPacket::new); 33 registerS2C(0, KickS2CPacket.class, KickS2CPacket::new);
37 registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new); 34 registerS2C(1, SyncMappingsS2CPacket.class, SyncMappingsS2CPacket::new);
38 registerS2C(2, RenameS2CPacket.class, RenameS2CPacket::new);
39 registerS2C(3, RemoveMappingS2CPacket.class, RemoveMappingS2CPacket::new);
40 registerS2C(4, ChangeDocsS2CPacket.class, ChangeDocsS2CPacket::new);
41 registerS2C(5, MarkDeobfuscatedS2CPacket.class, MarkDeobfuscatedS2CPacket::new);
42 registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new); 35 registerS2C(6, MessageS2CPacket.class, MessageS2CPacket::new);
43 registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new); 36 registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new);
37 registerS2C(8, EntryChangeS2CPacket.class, EntryChangeS2CPacket::new);
44 } 38 }
45 39
46 public static int getC2SId(Packet<ServerPacketHandler> packet) { 40 public static int getC2SId(Packet<ServerPacketHandler> packet) {
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
deleted file mode 100644
index 298e674f..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
+++ /dev/null
@@ -1,56 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.Message;
8import cuchaz.enigma.network.ServerPacketHandler;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.validation.PrintValidatable;
11import cuchaz.enigma.utils.validation.ValidationContext;
12
13public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> {
14 private Entry<?> entry;
15
16 RemoveMappingC2SPacket() {
17 }
18
19 public RemoveMappingC2SPacket(Entry<?> entry) {
20 this.entry = entry;
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.entry = PacketHelper.readEntry(input);
26 }
27
28 @Override
29 public void write(DataOutput output) throws IOException {
30 PacketHelper.writeEntry(output, entry);
31 }
32
33 @Override
34 public void handle(ServerPacketHandler handler) {
35 ValidationContext vc = new ValidationContext();
36 vc.setActiveElement(PrintValidatable.INSTANCE);
37
38 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
39
40 if (valid) {
41 handler.getServer().getMappings().removeByObf(vc, entry);
42 valid = vc.canProceed();
43 }
44
45 if (!valid) {
46 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
47 return;
48 }
49
50 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry);
51
52 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
53 handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry));
54 handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry));
55 }
56}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
deleted file mode 100644
index e336c7b2..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
+++ /dev/null
@@ -1,47 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.network.ClientPacketHandler;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.validation.PrintValidatable;
11import cuchaz.enigma.utils.validation.ValidationContext;
12
13public class RemoveMappingS2CPacket implements Packet<ClientPacketHandler> {
14 private int syncId;
15 private Entry<?> entry;
16
17 RemoveMappingS2CPacket() {
18 }
19
20 public RemoveMappingS2CPacket(int syncId, Entry<?> entry) {
21 this.syncId = syncId;
22 this.entry = entry;
23 }
24
25 @Override
26 public void read(DataInput input) throws IOException {
27 this.syncId = input.readUnsignedShort();
28 this.entry = PacketHelper.readEntry(input);
29 }
30
31 @Override
32 public void write(DataOutput output) throws IOException {
33 output.writeShort(syncId);
34 PacketHelper.writeEntry(output, entry);
35 }
36
37 @Override
38 public void handle(ClientPacketHandler controller) {
39 ValidationContext vc = new ValidationContext();
40 vc.setActiveElement(PrintValidatable.INSTANCE);
41
42 controller.removeMapping(vc, new EntryReference<>(entry, entry.getName()));
43
44 if (!vc.canProceed()) return;
45 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
46 }
47}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
deleted file mode 100644
index 694d4321..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
+++ /dev/null
@@ -1,66 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.network.Message;
8import cuchaz.enigma.network.ServerPacketHandler;
9import cuchaz.enigma.translation.mapping.EntryMapping;
10import cuchaz.enigma.translation.representation.entry.Entry;
11import cuchaz.enigma.utils.validation.PrintValidatable;
12import cuchaz.enigma.utils.validation.ValidationContext;
13
14public class RenameC2SPacket implements Packet<ServerPacketHandler> {
15 private Entry<?> entry;
16 private String newName;
17 private boolean refreshClassTree;
18
19 RenameC2SPacket() {
20 }
21
22 public RenameC2SPacket(Entry<?> entry, String newName, boolean refreshClassTree) {
23 this.entry = entry;
24 this.newName = newName;
25 this.refreshClassTree = refreshClassTree;
26 }
27
28 @Override
29 public void read(DataInput input) throws IOException {
30 this.entry = PacketHelper.readEntry(input);
31 this.newName = PacketHelper.readString(input);
32 this.refreshClassTree = input.readBoolean();
33 }
34
35 @Override
36 public void write(DataOutput output) throws IOException {
37 PacketHelper.writeEntry(output, entry);
38 PacketHelper.writeString(output, newName);
39 output.writeBoolean(refreshClassTree);
40 }
41
42 @Override
43 public void handle(ServerPacketHandler handler) {
44 ValidationContext vc = new ValidationContext();
45 vc.setActiveElement(PrintValidatable.INSTANCE);
46
47 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
48
49 if (valid) {
50 EntryMapping previous = handler.getServer().getMappings().getDeobfMapping(entry);
51 handler.getServer().getMappings().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName));
52 valid = vc.canProceed();
53 }
54
55 if (!valid) {
56 handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree);
57 return;
58 }
59
60 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName);
61
62 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
63 handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree));
64 handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName));
65 }
66}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
deleted file mode 100644
index fdf06540..00000000
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
+++ /dev/null
@@ -1,55 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.network.ClientPacketHandler;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.utils.validation.PrintValidatable;
11import cuchaz.enigma.utils.validation.ValidationContext;
12
13public class RenameS2CPacket implements Packet<ClientPacketHandler> {
14 private int syncId;
15 private Entry<?> entry;
16 private String newName;
17 private boolean refreshClassTree;
18
19 RenameS2CPacket() {
20 }
21
22 public RenameS2CPacket(int syncId, Entry<?> entry, String newName, boolean refreshClassTree) {
23 this.syncId = syncId;
24 this.entry = entry;
25 this.newName = newName;
26 this.refreshClassTree = refreshClassTree;
27 }
28
29 @Override
30 public void read(DataInput input) throws IOException {
31 this.syncId = input.readUnsignedShort();
32 this.entry = PacketHelper.readEntry(input);
33 this.newName = PacketHelper.readString(input);
34 this.refreshClassTree = input.readBoolean();
35 }
36
37 @Override
38 public void write(DataOutput output) throws IOException {
39 output.writeShort(syncId);
40 PacketHelper.writeEntry(output, entry);
41 PacketHelper.writeString(output, newName);
42 output.writeBoolean(refreshClassTree);
43 }
44
45 @Override
46 public void handle(ClientPacketHandler controller) {
47 ValidationContext vc = new ValidationContext();
48 vc.setActiveElement(PrintValidatable.INSTANCE);
49
50 controller.rename(vc, new EntryReference<>(entry, entry.getName()), newName, refreshClassTree);
51
52 if (!vc.canProceed()) return;
53 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
54 }
55}
diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
index 92a78748..6d9c0bcb 100644
--- a/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
+++ b/enigma-server/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
@@ -1,19 +1,19 @@
1package cuchaz.enigma.network.packet; 1package cuchaz.enigma.network.packet;
2 2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.tree.EntryTree;
5import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
6import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
7import cuchaz.enigma.network.ClientPacketHandler;
8import cuchaz.enigma.network.EnigmaServer;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11import java.io.DataInput; 3import java.io.DataInput;
12import java.io.DataOutput; 4import java.io.DataOutput;
13import java.io.IOException; 5import java.io.IOException;
14import java.util.Collection; 6import java.util.Collection;
15import java.util.List; 7import java.util.List;
16 8
9import cuchaz.enigma.network.ClientPacketHandler;
10import cuchaz.enigma.network.EnigmaServer;
11import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.tree.EntryTree;
13import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
14import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
15import cuchaz.enigma.translation.representation.entry.Entry;
16
17public class SyncMappingsS2CPacket implements Packet<ClientPacketHandler> { 17public class SyncMappingsS2CPacket implements Packet<ClientPacketHandler> {
18 private EntryTree<EntryMapping> mappings; 18 private EntryTree<EntryMapping> mappings;
19 19
@@ -35,16 +35,9 @@ public class SyncMappingsS2CPacket implements Packet<ClientPacketHandler> {
35 35
36 private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException { 36 private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException {
37 Entry<?> entry = PacketHelper.readEntry(input, parent, false); 37 Entry<?> entry = PacketHelper.readEntry(input, parent, false);
38 EntryMapping mapping = null; 38 String name = PacketHelper.readString(input);
39 if (input.readBoolean()) { 39 String javadoc = PacketHelper.readString(input);
40 String name = input.readUTF(); 40 EntryMapping mapping = new EntryMapping(!name.isEmpty() ? name : null, !javadoc.isEmpty() ? javadoc : null);
41 if (input.readBoolean()) {
42 String javadoc = input.readUTF();
43 mapping = new EntryMapping(name, javadoc);
44 } else {
45 mapping = new EntryMapping(name);
46 }
47 }
48 mappings.insert(entry, mapping); 41 mappings.insert(entry, mapping);
49 int size = input.readUnsignedShort(); 42 int size = input.readUnsignedShort();
50 for (int i = 0; i < size; i++) { 43 for (int i = 0; i < size; i++) {
@@ -64,14 +57,10 @@ public class SyncMappingsS2CPacket implements Packet<ClientPacketHandler> {
64 private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException { 57 private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException {
65 PacketHelper.writeEntry(output, node.getEntry(), false); 58 PacketHelper.writeEntry(output, node.getEntry(), false);
66 EntryMapping value = node.getValue(); 59 EntryMapping value = node.getValue();
67 output.writeBoolean(value != null); 60 if (value == null) value = EntryMapping.DEFAULT;
68 if (value != null) { 61
69 PacketHelper.writeString(output, value.getTargetName()); 62 PacketHelper.writeString(output, value.targetName() != null ? value.targetName() : "");
70 output.writeBoolean(value.getJavadoc() != null); 63 PacketHelper.writeString(output, value.javadoc() != null ? value.javadoc() : "");
71 if (value.getJavadoc() != null) {
72 PacketHelper.writeString(output, value.getJavadoc());
73 }
74 }
75 Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes(); 64 Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes();
76 output.writeShort(children.size()); 65 output.writeShort(children.size());
77 for (EntryTreeNode<EntryMapping> child : children) { 66 for (EntryTreeNode<EntryMapping> child : children) {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
index a657d171..cddedad7 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/Gui.java
@@ -49,11 +49,9 @@ import cuchaz.enigma.gui.renderer.InheritanceTreeCellRenderer;
49import cuchaz.enigma.gui.renderer.MessageListCellRenderer; 49import cuchaz.enigma.gui.renderer.MessageListCellRenderer;
50import cuchaz.enigma.gui.util.*; 50import cuchaz.enigma.gui.util.*;
51import cuchaz.enigma.network.Message; 51import cuchaz.enigma.network.Message;
52import cuchaz.enigma.network.packet.MarkDeobfuscatedC2SPacket;
53import cuchaz.enigma.network.packet.MessageC2SPacket; 52import cuchaz.enigma.network.packet.MessageC2SPacket;
54import cuchaz.enigma.network.packet.RemoveMappingC2SPacket;
55import cuchaz.enigma.network.packet.RenameC2SPacket;
56import cuchaz.enigma.source.Token; 53import cuchaz.enigma.source.Token;
54import cuchaz.enigma.translation.mapping.EntryChange;
57import cuchaz.enigma.translation.mapping.EntryRemapper; 55import cuchaz.enigma.translation.mapping.EntryRemapper;
58import cuchaz.enigma.translation.representation.entry.ClassEntry; 56import cuchaz.enigma.translation.representation.entry.ClassEntry;
59import cuchaz.enigma.translation.representation.entry.Entry; 57import cuchaz.enigma.translation.representation.entry.Entry;
@@ -740,12 +738,10 @@ public class Gui implements LanguageChangeListener {
740 738
741 Entry<?> obfEntry = cursorReference.entry; 739 Entry<?> obfEntry = cursorReference.entry;
742 740
743 if (controller.project.getMapper().extendedDeobfuscate(obfEntry).isDeobfuscated()) { 741 if (this.controller.project.getMapper().getDeobfMapping(obfEntry).targetName() != null) {
744 if (!validateImmediateAction(vc -> this.controller.removeMapping(vc, cursorReference))) return; 742 validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).clearDeobfName()));
745 this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry()));
746 } else { 743 } else {
747 if (!validateImmediateAction(vc -> this.controller.markAsDeobfuscated(vc, cursorReference))) return; 744 validateImmediateAction(vc -> this.controller.applyChange(vc, EntryChange.modify(obfEntry).withDefaultDeobfName(this.getController().project)));
748 this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry()));
749 } 745 }
750 } 746 }
751 747
@@ -813,7 +809,7 @@ public class Gui implements LanguageChangeListener {
813 this.frame.repaint(); 809 this.frame.repaint();
814 } 810 }
815 811
816 public void onPanelRename(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) { 812 public void onRenameFromClassTree(ValidationContext vc, Object prevData, Object data, DefaultMutableTreeNode node) {
817 if (data instanceof String) { 813 if (data instanceof String) {
818 // package rename 814 // package rename
819 for (int i = 0; i < node.getChildCount(); i++) { 815 for (int i = 0; i < node.getChildCount(); i++) {
@@ -821,7 +817,7 @@ public class Gui implements LanguageChangeListener {
821 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject(); 817 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
822 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName()); 818 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
823 819
824 onPanelRename(vc, prevDataChild, dataChild, node); 820 onRenameFromClassTree(vc, prevDataChild, dataChild, node);
825 } 821 }
826 node.setUserObject(data); 822 node.setUserObject(data);
827 // Ob package will never be modified, just reload deob view 823 // Ob package will never be modified, just reload deob view
@@ -839,9 +835,9 @@ public class Gui implements LanguageChangeListener {
839 .filter(e -> mapper.deobfuscate(e).equals(deobf)) 835 .filter(e -> mapper.deobfuscate(e).equals(deobf))
840 .findAny().orElse(deobf); 836 .findAny().orElse(deobf);
841 837
842 this.controller.rename(vc, new EntryReference<>(obf, obf.getFullName()), ((ClassEntry) data).getFullName(), false); 838 this.controller.applyChange(vc, EntryChange.modify(obf).withDeobfName(((ClassEntry) data).getFullName()));
843 if (!vc.canProceed()) return; 839 } else {
844 this.controller.sendPacket(new RenameC2SPacket(obf, ((ClassEntry) data).getFullName(), false)); 840 throw new IllegalStateException(String.format("unhandled rename object data: '%s'", data));
845 } 841 }
846 } 842 }
847 843
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
index 2b75655a..4a15b418 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java
@@ -18,6 +18,7 @@ import java.io.IOException;
18import java.nio.file.Path; 18import java.nio.file.Path;
19import java.util.Collection; 19import java.util.Collection;
20import java.util.List; 20import java.util.List;
21import java.util.Objects;
21import java.util.Set; 22import java.util.Set;
22import java.util.concurrent.CompletableFuture; 23import java.util.concurrent.CompletableFuture;
23import java.util.concurrent.ExecutionException; 24import java.util.concurrent.ExecutionException;
@@ -39,10 +40,12 @@ import cuchaz.enigma.classprovider.ClasspathClassProvider;
39import cuchaz.enigma.gui.config.NetConfig; 40import cuchaz.enigma.gui.config.NetConfig;
40import cuchaz.enigma.gui.config.UiConfig; 41import cuchaz.enigma.gui.config.UiConfig;
41import cuchaz.enigma.gui.dialog.ProgressDialog; 42import cuchaz.enigma.gui.dialog.ProgressDialog;
43import cuchaz.enigma.gui.newabstraction.EntryValidation;
42import cuchaz.enigma.gui.stats.StatsGenerator; 44import cuchaz.enigma.gui.stats.StatsGenerator;
43import cuchaz.enigma.gui.stats.StatsMember; 45import cuchaz.enigma.gui.stats.StatsMember;
44import cuchaz.enigma.gui.util.History; 46import cuchaz.enigma.gui.util.History;
45import cuchaz.enigma.network.*; 47import cuchaz.enigma.network.*;
48import cuchaz.enigma.network.packet.EntryChangeC2SPacket;
46import cuchaz.enigma.network.packet.LoginC2SPacket; 49import cuchaz.enigma.network.packet.LoginC2SPacket;
47import cuchaz.enigma.network.packet.Packet; 50import cuchaz.enigma.network.packet.Packet;
48import cuchaz.enigma.source.DecompiledClassSource; 51import cuchaz.enigma.source.DecompiledClassSource;
@@ -62,6 +65,7 @@ import cuchaz.enigma.translation.representation.entry.FieldEntry;
62import cuchaz.enigma.translation.representation.entry.MethodEntry; 65import cuchaz.enigma.translation.representation.entry.MethodEntry;
63import cuchaz.enigma.utils.I18n; 66import cuchaz.enigma.utils.I18n;
64import cuchaz.enigma.utils.Utils; 67import cuchaz.enigma.utils.Utils;
68import cuchaz.enigma.utils.validation.PrintValidatable;
65import cuchaz.enigma.utils.validation.ValidationContext; 69import cuchaz.enigma.utils.validation.ValidationContext;
66 70
67public class GuiController implements ClientPacketHandler { 71public class GuiController implements ClientPacketHandler {
@@ -401,19 +405,6 @@ public class GuiController implements ClientPacketHandler {
401 }); 405 });
402 } 406 }
403 407
404 public void onModifierChanged(ValidationContext vc, Entry<?> entry, AccessModifier modifier) {
405 EntryRemapper mapper = project.getMapper();
406
407 EntryMapping mapping = mapper.getDeobfMapping(entry);
408 if (mapping != null) {
409 mapper.mapFromObf(vc, entry, new EntryMapping(mapping.getTargetName(), modifier));
410 } else {
411 mapper.mapFromObf(vc, entry, new EntryMapping(entry.getName(), modifier));
412 }
413
414 chp.invalidateMapped();
415 }
416
417 public StructureTreeNode getClassStructure(ClassEntry entry, StructureTreeOptions options) { 408 public StructureTreeNode getClassStructure(ClassEntry entry, StructureTreeOptions options) {
418 StructureTreeNode rootNode = new StructureTreeNode(this.project, entry, entry); 409 StructureTreeNode rootNode = new StructureTreeNode(this.project, entry, entry);
419 rootNode.load(this.project, options); 410 rootNode.load(this.project, options);
@@ -471,74 +462,54 @@ public class GuiController implements ClientPacketHandler {
471 } 462 }
472 463
473 @Override 464 @Override
474 public void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) { 465 public boolean applyChangeFromServer(EntryChange<?> change) {
475 rename(vc, reference, newName, refreshClassTree, false); 466 ValidationContext vc = new ValidationContext();
476 } 467 vc.setActiveElement(PrintValidatable.INSTANCE);
477 468 this.applyChange0(vc, change);
478 public void rename(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean validateOnly) {
479 Entry<?> entry = reference.getNameableEntry();
480 EntryMapping previous = project.getMapper().getDeobfMapping(entry);
481 project.getMapper().mapFromObf(vc, entry, previous != null ? previous.withName(newName) : new EntryMapping(newName), true, validateOnly);
482 gui.showStructure(gui.getActiveEditor()); 469 gui.showStructure(gui.getActiveEditor());
483 470
484 if (validateOnly || !vc.canProceed()) return; 471 return vc.canProceed();
472 }
485 473
486 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass()) 474 public void validateChange(ValidationContext vc, EntryChange<?> change) {
487 this.gui.moveClassTree(reference.entry, newName); 475 if (change.getDeobfName().isSet()) {
476 EntryValidation.validateRename(vc, this.project, change.getTarget(), change.getDeobfName().getNewValue());
477 }
488 478
489 chp.invalidateMapped(); 479 if (change.getJavadoc().isSet()) {
480 EntryValidation.validateJavadoc(vc, change.getJavadoc().getNewValue());
481 }
490 } 482 }
491 483
492 @Override 484 public void applyChange(ValidationContext vc, EntryChange<?> change) {
493 public void removeMapping(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) { 485 this.applyChange0(vc, change);
494 project.getMapper().removeByObf(vc, reference.getNameableEntry());
495 gui.showStructure(gui.getActiveEditor()); 486 gui.showStructure(gui.getActiveEditor());
496
497 if (!vc.canProceed()) return; 487 if (!vc.canProceed()) return;
498 488 this.sendPacket(new EntryChangeC2SPacket(change));
499 if (reference.entry instanceof ClassEntry)
500 this.gui.moveClassTree(reference.entry, false, true);
501
502 chp.invalidateMapped();
503 } 489 }
504 490
505 @Override 491 private void applyChange0(ValidationContext vc, EntryChange<?> change) {
506 public void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) { 492 validateChange(vc, change);
507 changeDocs(vc, reference, updatedDocs, false); 493 if (!vc.canProceed()) return;
508 }
509
510 public void changeDocs(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean validateOnly) {
511 changeDoc(vc, reference.entry, updatedDocs, validateOnly);
512
513 if (validateOnly || !vc.canProceed()) return;
514 494
515 chp.invalidateJavadoc(reference.getLocationClassEntry()); 495 Entry<?> target = change.getTarget();
516 } 496 EntryMapping prev = this.project.getMapper().getDeobfMapping(target);
497 EntryMapping mapping = EntryUtil.applyChange(vc, this.project.getMapper(), change);
517 498
518 private void changeDoc(ValidationContext vc, Entry<?> obfEntry, String newDoc, boolean validateOnly) { 499 boolean renamed = !change.getDeobfName().isUnchanged();
519 EntryRemapper mapper = project.getMapper();
520 500
521 EntryMapping deobfMapping = mapper.getDeobfMapping(obfEntry); 501 if (renamed && target instanceof ClassEntry && !((ClassEntry) target).isInnerClass()) {
522 if (deobfMapping == null) { 502 this.gui.moveClassTree(target, prev.targetName() == null, mapping.targetName() == null);
523 deobfMapping = new EntryMapping(mapper.deobfuscate(obfEntry).getName());
524 } 503 }
525 504
526 mapper.mapFromObf(vc, obfEntry, deobfMapping.withDocs(newDoc), false, validateOnly); 505 if (!Objects.equals(prev.targetName(), mapping.targetName())) {
527 } 506 this.chp.invalidateMapped();
507 }
528 508
529 @Override 509 if (!Objects.equals(prev.javadoc(), mapping.javadoc())) {
530 public void markAsDeobfuscated(ValidationContext vc, EntryReference<Entry<?>, Entry<?>> reference) { 510 this.chp.invalidateJavadoc(target.getTopLevelClass());
531 EntryRemapper mapper = project.getMapper(); 511 }
532 Entry<?> entry = reference.getNameableEntry();
533 mapper.mapFromObf(vc, entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
534 gui.showStructure(gui.getActiveEditor()); 512 gui.showStructure(gui.getActiveEditor());
535
536 if (!vc.canProceed()) return;
537
538 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
539 this.gui.moveClassTree(reference.entry, true, false);
540
541 chp.invalidateMapped();
542 } 513 }
543 514
544 public void openStats(Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) { 515 public void openStats(Set<StatsMember> includedMembers, String topLevelPackage, boolean includeSynthetic) {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
index 2fc67476..9470e11c 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
@@ -29,23 +29,22 @@ import cuchaz.enigma.gui.config.UiConfig;
29import cuchaz.enigma.gui.elements.ValidatableTextArea; 29import cuchaz.enigma.gui.elements.ValidatableTextArea;
30import cuchaz.enigma.gui.util.GuiUtil; 30import cuchaz.enigma.gui.util.GuiUtil;
31import cuchaz.enigma.gui.util.ScaleUtil; 31import cuchaz.enigma.gui.util.ScaleUtil;
32import cuchaz.enigma.network.packet.ChangeDocsC2SPacket; 32import cuchaz.enigma.translation.mapping.EntryChange;
33import cuchaz.enigma.translation.representation.entry.Entry; 33import cuchaz.enigma.translation.representation.entry.Entry;
34import cuchaz.enigma.utils.I18n; 34import cuchaz.enigma.utils.I18n;
35import cuchaz.enigma.utils.validation.Message;
36import cuchaz.enigma.utils.validation.ValidationContext; 35import cuchaz.enigma.utils.validation.ValidationContext;
37 36
38public class JavadocDialog { 37public class JavadocDialog {
39 38
40 private final JDialog ui; 39 private final JDialog ui;
41 private final GuiController controller; 40 private final GuiController controller;
42 private final EntryReference<Entry<?>, Entry<?>> entry; 41 private final Entry<?> entry;
43 42
44 private final ValidatableTextArea text; 43 private final ValidatableTextArea text;
45 44
46 private final ValidationContext vc = new ValidationContext(); 45 private final ValidationContext vc = new ValidationContext();
47 46
48 private JavadocDialog(JFrame parent, GuiController controller, EntryReference<Entry<?>, Entry<?>> entry, String preset) { 47 private JavadocDialog(JFrame parent, GuiController controller, Entry<?> entry, String preset) {
49 this.ui = new JDialog(parent, I18n.translate("javadocs.edit")); 48 this.ui = new JDialog(parent, I18n.translate("javadocs.edit"));
50 this.controller = controller; 49 this.controller = controller;
51 this.entry = entry; 50 this.entry = entry;
@@ -161,28 +160,21 @@ public class JavadocDialog {
161 public void validate() { 160 public void validate() {
162 vc.setActiveElement(text); 161 vc.setActiveElement(text);
163 162
164 if (text.getText().contains("*/")) { 163 controller.validateChange(vc, EntryChange.modify(entry).withJavadoc(text.getText()));
165 vc.raise(Message.ILLEGAL_DOC_COMMENT_END);
166 }
167
168 controller.changeDocs(vc, entry, text.getText(), true);
169 } 164 }
170 165
171 public void save() { 166 public void save() {
172 vc.setActiveElement(text); 167 vc.setActiveElement(text);
173 controller.changeDocs(vc, entry, text.getText().trim().isEmpty() ? null : text.getText());
174
175 if (!vc.canProceed()) return;
176 168
177 controller.sendPacket(new ChangeDocsC2SPacket(entry.getNameableEntry(), text.getText())); 169 controller.applyChange(vc, EntryChange.modify(entry).withJavadoc(text.getText()));
178 } 170 }
179 171
180 public static void show(JFrame parent, GuiController controller, EntryReference<Entry<?>, Entry<?>> entry) { 172 public static void show(JFrame parent, GuiController controller, EntryReference<Entry<?>, Entry<?>> entry) {
181 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(entry); 173 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(entry);
182 String text = Strings.nullToEmpty(translatedReference.entry.getJavadocs()); 174 String text = Strings.nullToEmpty(translatedReference.entry.getJavadocs());
183 175
184 JavadocDialog dialog = new JavadocDialog(parent, controller, entry, text); 176 JavadocDialog dialog = new JavadocDialog(parent, controller, entry.getNameableEntry(), text);
185 dialog.ui.doLayout(); 177 //dialog.ui.doLayout();
186 dialog.ui.setVisible(true); 178 dialog.ui.setVisible(true);
187 dialog.text.grabFocus(); 179 dialog.text.grabFocus();
188 } 180 }
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java
new file mode 100644
index 00000000..898529a4
--- /dev/null
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/newabstraction/EntryValidation.java
@@ -0,0 +1,22 @@
1package cuchaz.enigma.gui.newabstraction;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.translation.representation.entry.Entry;
5import cuchaz.enigma.utils.validation.Message;
6import cuchaz.enigma.utils.validation.ValidationContext;
7
8public class EntryValidation {
9
10 public static boolean validateJavadoc(ValidationContext vc, String javadoc) {
11 if (javadoc.contains("*/")) {
12 vc.raise(Message.ILLEGAL_DOC_COMMENT_END);
13 return false;
14 }
15 return true;
16 }
17
18 public static boolean validateRename(ValidationContext vc, EnigmaProject p, Entry<?> entry, String newName) {
19 return p.getMapper().getValidator().validateRename(vc, entry, newName);
20 }
21
22}
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
index 5b7882b0..cd09c1a1 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/DeobfPanel.java
@@ -22,7 +22,7 @@ public class DeobfPanel extends JPanel {
22 22
23 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true); 23 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true);
24 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo); 24 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo);
25 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename); 25 this.deobfClasses.setRenameSelectionListener(gui::onRenameFromClassTree);
26 26
27 this.setLayout(new BorderLayout()); 27 this.setLayout(new BorderLayout());
28 this.add(this.title, BorderLayout.NORTH); 28 this.add(this.title, BorderLayout.NORTH);
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
index 3bae94c3..4ae0b7be 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/IdentifierPanel.java
@@ -15,7 +15,6 @@ import javax.swing.JLabel;
15import javax.swing.JPanel; 15import javax.swing.JPanel;
16 16
17import cuchaz.enigma.EnigmaProject; 17import cuchaz.enigma.EnigmaProject;
18import cuchaz.enigma.analysis.EntryReference;
19import cuchaz.enigma.gui.EditableType; 18import cuchaz.enigma.gui.EditableType;
20import cuchaz.enigma.gui.Gui; 19import cuchaz.enigma.gui.Gui;
21import cuchaz.enigma.gui.elements.ConvertingTextField; 20import cuchaz.enigma.gui.elements.ConvertingTextField;
@@ -23,8 +22,8 @@ import cuchaz.enigma.gui.events.ConvertingTextFieldListener;
23import cuchaz.enigma.gui.util.GridBagConstraintsBuilder; 22import cuchaz.enigma.gui.util.GridBagConstraintsBuilder;
24import cuchaz.enigma.gui.util.GuiUtil; 23import cuchaz.enigma.gui.util.GuiUtil;
25import cuchaz.enigma.gui.util.ScaleUtil; 24import cuchaz.enigma.gui.util.ScaleUtil;
26import cuchaz.enigma.network.packet.RenameC2SPacket;
27import cuchaz.enigma.translation.mapping.AccessModifier; 25import cuchaz.enigma.translation.mapping.AccessModifier;
26import cuchaz.enigma.translation.mapping.EntryChange;
28import cuchaz.enigma.translation.mapping.EntryMapping; 27import cuchaz.enigma.translation.mapping.EntryMapping;
29import cuchaz.enigma.translation.representation.entry.*; 28import cuchaz.enigma.translation.representation.entry.*;
30import cuchaz.enigma.utils.I18n; 29import cuchaz.enigma.utils.I18n;
@@ -76,7 +75,7 @@ public class IdentifierPanel {
76 } 75 }
77 76
78 private void onModifierChanged(AccessModifier modifier) { 77 private void onModifierChanged(AccessModifier modifier) {
79 gui.validateImmediateAction(vc -> this.gui.getController().onModifierChanged(vc, entry, modifier)); 78 gui.validateImmediateAction(vc -> this.gui.getController().applyChange(vc, EntryChange.modify(entry).withAccess(modifier)));
80 } 79 }
81 80
82 public void refreshReference() { 81 public void refreshReference() {
@@ -176,13 +175,12 @@ public class IdentifierPanel {
176 } 175 }
177 176
178 private void validateRename(String newName) { 177 private void validateRename(String newName) {
179 gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true, true); 178 gui.getController().validateChange(vc, EntryChange.modify(entry).withDeobfName(newName));
180 } 179 }
181 180
182 private void doRename(String newName) { 181 private void doRename(String newName) {
183 gui.getController().rename(vc, new EntryReference<>(entry, deobfEntry.getName()), newName, true); 182 EntryChange<? extends Entry<?>> change = EntryChange.modify(entry).withDeobfName(newName);
184 if (!vc.canProceed()) return; 183 gui.getController().applyChange(vc, change);
185 gui.getController().sendPacket(new RenameC2SPacket(entry, newName, true));
186 } 184 }
187 185
188 public void retranslateUi() { 186 public void retranslateUi() {
@@ -277,12 +275,7 @@ public class IdentifierPanel {
277 275
278 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values()); 276 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
279 EntryMapping mapping = project.getMapper().getDeobfMapping(e); 277 EntryMapping mapping = project.getMapper().getDeobfMapping(e);
280 278 combo.setSelectedIndex(mapping.accessModifier().ordinal());
281 if (mapping != null) {
282 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
283 } else {
284 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
285 }
286 279
287 if (this.gui.isEditable(type)) { 280 if (this.gui.isEditable(type)) {
288 combo.addItemListener(event -> { 281 combo.addItemListener(event -> {
diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
index b384968d..7783843d 100644
--- a/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
+++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/panels/ObfPanel.java
@@ -33,7 +33,7 @@ public class ObfPanel extends JPanel {
33 33
34 this.obfClasses = new ClassSelector(gui, obfClassComparator, false); 34 this.obfClasses = new ClassSelector(gui, obfClassComparator, false);
35 this.obfClasses.setSelectionListener(gui.getController()::navigateTo); 35 this.obfClasses.setSelectionListener(gui.getController()::navigateTo);
36 this.obfClasses.setRenameSelectionListener(gui::onPanelRename); 36 this.obfClasses.setRenameSelectionListener(gui::onRenameFromClassTree);
37 37
38 this.setLayout(new BorderLayout()); 38 this.setLayout(new BorderLayout());
39 this.add(this.title, BorderLayout.NORTH); 39 this.add(this.title, BorderLayout.NORTH);
diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java
index 9ac611f5..5f371a58 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/DecompiledClassSource.java
@@ -79,7 +79,7 @@ public class DecompiledClassSource {
79 return null; 79 return null;
80 } 80 }
81 81
82 private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) { 82 public static Optional<String> proposeName(EnigmaProject project, Entry<?> entry) {
83 EnigmaServices services = project.getEnigma().getServices(); 83 EnigmaServices services = project.getEnigma().getServices();
84 84
85 return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> { 85 return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> {
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
index b85851fa..4e8940ad 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
@@ -148,7 +148,7 @@ public class EnigmaDumper extends StringStreamDumper {
148 continue; 148 continue;
149 } 149 }
150 150
151 String javaDoc = mapping.getJavadoc(); 151 String javaDoc = mapping.javadoc();
152 if (javaDoc != null) { 152 if (javaDoc != null) {
153 recordComponentDocs.add(String.format("@param %s %s", field.getFieldName(), javaDoc)); 153 recordComponentDocs.add(String.format("@param %s %s", field.getFieldName(), javaDoc));
154 } 154 }
@@ -159,7 +159,7 @@ public class EnigmaDumper extends StringStreamDumper {
159 159
160 String javadoc = null; 160 String javadoc = null;
161 if (mapping != null) { 161 if (mapping != null) {
162 javadoc = mapping.getJavadoc(); 162 javadoc = mapping.javadoc();
163 } 163 }
164 164
165 if (javadoc != null || !recordComponentDocs.isEmpty()) { 165 if (javadoc != null || !recordComponentDocs.isEmpty()) {
@@ -191,7 +191,7 @@ public class EnigmaDumper extends StringStreamDumper {
191 MethodEntry methodEntry = getMethodEntry(method); 191 MethodEntry methodEntry = getMethodEntry(method);
192 EntryMapping mapping = mapper.getDeobfMapping(methodEntry); 192 EntryMapping mapping = mapper.getDeobfMapping(methodEntry);
193 if (mapping != null) { 193 if (mapping != null) {
194 String javadoc = mapping.getJavadoc(); 194 String javadoc = mapping.javadoc();
195 if (javadoc != null) { 195 if (javadoc != null) {
196 lines.addAll(Arrays.asList(javadoc.split("\\R"))); 196 lines.addAll(Arrays.asList(javadoc.split("\\R")));
197 } 197 }
@@ -204,9 +204,9 @@ public class EnigmaDumper extends StringStreamDumper {
204 if (each instanceof LocalVariableEntry) { 204 if (each instanceof LocalVariableEntry) {
205 EntryMapping paramMapping = mapper.getDeobfMapping(each); 205 EntryMapping paramMapping = mapper.getDeobfMapping(each);
206 if (paramMapping != null) { 206 if (paramMapping != null) {
207 String javadoc = paramMapping.getJavadoc(); 207 String javadoc = paramMapping.javadoc();
208 if (javadoc != null) { 208 if (javadoc != null) {
209 lines.addAll(Arrays.asList(("@param " + paramMapping.getTargetName() + " " + javadoc).split("\\R"))); 209 lines.addAll(Arrays.asList(("@param " + paramMapping.targetName() + " " + javadoc).split("\\R")));
210 } 210 }
211 } 211 }
212 } 212 }
@@ -230,7 +230,7 @@ public class EnigmaDumper extends StringStreamDumper {
230 if (mapper != null && !recordComponent) { 230 if (mapper != null && !recordComponent) {
231 EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getDescriptor())); 231 EntryMapping mapping = mapper.getDeobfMapping(getFieldEntry(owner, field.getFieldName(), field.getDescriptor()));
232 if (mapping != null) { 232 if (mapping != null) {
233 String javadoc = mapping.getJavadoc(); 233 String javadoc = mapping.javadoc();
234 if (javadoc != null) { 234 if (javadoc != null) {
235 print("/**").newln(); 235 print("/**").newln();
236 for (String line : javadoc.split("\\R")) { 236 for (String line : javadoc.split("\\R")) {
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
index 70fc8c6b..1e5beb1e 100644
--- a/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
+++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
@@ -47,16 +47,17 @@ public final class AddJavadocsAstTransform implements IAstTransform {
47 47
48 private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) { 48 private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) {
49 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); 49 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
50 final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); 50 final String docs = Strings.emptyToNull(mapping.javadoc());
51 return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, 51 return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st,
52 CommentType.Documentation)).toArray(Comment[]::new); 52 CommentType.Documentation)).toArray(Comment[]::new);
53 } 53 }
54 54
55 private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) { 55 private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) {
56 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); 56 Entry<?> entry = retriever.apply(node);
57 final EntryMapping mapping = remapper.getDeobfMapping(entry);
57 final Comment[] ret = getComments(node, retriever); 58 final Comment[] ret = getComments(node, retriever);
58 if (ret != null) { 59 if (ret != null) {
59 final String paramPrefix = "@param " + mapping.getTargetName() + " "; 60 final String paramPrefix = "@param " + (mapping.targetName() != null ? mapping.targetName() : entry.getName()) + " ";
60 final String indent = Strings.repeat(" ", paramPrefix.length()); 61 final String indent = Strings.repeat(" ", paramPrefix.length());
61 ret[0].setContent(paramPrefix + ret[0].getContent()); 62 ret[0].setContent(paramPrefix + ret[0].getContent());
62 for (int i = 1; i < ret.length; i++) { 63 for (int i = 1; i < ret.length; i++) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java
new file mode 100644
index 00000000..b5ec8552
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryChange.java
@@ -0,0 +1,97 @@
1package cuchaz.enigma.translation.mapping;
2
3import java.util.Objects;
4import java.util.Optional;
5
6import javax.annotation.Nullable;
7
8import cuchaz.enigma.EnigmaProject;
9import cuchaz.enigma.source.DecompiledClassSource;
10import cuchaz.enigma.translation.representation.entry.Entry;
11import cuchaz.enigma.utils.TristateChange;
12
13public final class EntryChange<E extends Entry<?>> {
14
15 private final E target;
16 private final TristateChange<String> deobfName;
17 private final TristateChange<String> javadoc;
18 private final TristateChange<AccessModifier> access;
19
20 private EntryChange(E target, TristateChange<String> deobfName, TristateChange<String> javadoc, TristateChange<AccessModifier> access) {
21 this.target = target;
22 this.deobfName = deobfName;
23 this.javadoc = javadoc;
24 this.access = access;
25 }
26
27 public static <E extends Entry<?>> EntryChange<E> modify(E target) {
28 return new EntryChange<>(target, TristateChange.unchanged(), TristateChange.unchanged(), TristateChange.unchanged());
29 }
30
31 public EntryChange<E> withDeobfName(String name) {
32 return new EntryChange<>(this.target, TristateChange.set(name), this.javadoc, this.access);
33 }
34
35 public EntryChange<E> withDefaultDeobfName(@Nullable EnigmaProject project) {
36 Optional<String> proposed = project != null ? DecompiledClassSource.proposeName(project, this.target) : Optional.empty();
37 return this.withDeobfName(proposed.orElse(this.target.getName()));
38 }
39
40 public EntryChange<E> clearDeobfName() {
41 return new EntryChange<>(this.target, TristateChange.reset(), this.javadoc, this.access);
42 }
43
44 public EntryChange<E> withJavadoc(String javadoc) {
45 return new EntryChange<>(this.target, this.deobfName, TristateChange.set(javadoc), this.access);
46 }
47
48 public EntryChange<E> clearJavadoc() {
49 return new EntryChange<>(this.target, this.deobfName, TristateChange.reset(), this.access);
50 }
51
52 public EntryChange<E> withAccess(AccessModifier access) {
53 return new EntryChange<>(this.target, this.deobfName, this.javadoc, TristateChange.set(access));
54 }
55
56 public EntryChange<E> clearAccess() {
57 return new EntryChange<>(this.target, this.deobfName, this.javadoc, TristateChange.reset());
58 }
59
60 public TristateChange<String> getDeobfName() {
61 return this.deobfName;
62 }
63
64 public TristateChange<String> getJavadoc() {
65 return this.javadoc;
66 }
67
68 public TristateChange<AccessModifier> getAccess() {
69 return this.access;
70 }
71
72 public E getTarget() {
73 return this.target;
74 }
75
76 @Override
77 public boolean equals(Object o) {
78 if (this == o) return true;
79 if (!(o instanceof EntryChange)) return false;
80 EntryChange<?> that = (EntryChange<?>) o;
81 return Objects.equals(this.target, that.target) &&
82 Objects.equals(this.deobfName, that.deobfName) &&
83 Objects.equals(this.javadoc, that.javadoc) &&
84 Objects.equals(this.access, that.access);
85 }
86
87 @Override
88 public int hashCode() {
89 return Objects.hash(this.target, this.deobfName, this.javadoc, this.access);
90 }
91
92 @Override
93 public String toString() {
94 return String.format("EntryChange { target: %s, deobfName: %s, javadoc: %s, access: %s }", this.target, this.deobfName, this.javadoc, this.access);
95 }
96
97}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
index c607817c..e916bf35 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
@@ -1,49 +1,37 @@
1package cuchaz.enigma.translation.mapping; 1package cuchaz.enigma.translation.mapping;
2 2
3import java.util.Arrays;
4
3import javax.annotation.Nonnull; 5import javax.annotation.Nonnull;
4import javax.annotation.Nullable; 6import javax.annotation.Nullable;
5 7
6public class EntryMapping { 8public record EntryMapping(
7 private final String targetName; 9 @Nullable String targetName,
8 private final AccessModifier accessModifier; 10 @Nonnull AccessModifier accessModifier,
9 private final @Nullable String javadoc; 11 @Nullable String javadoc
12) {
13 public static final EntryMapping DEFAULT = new EntryMapping(null, AccessModifier.UNCHANGED, null);
14
15 public EntryMapping {
16 if (accessModifier == null) {
17 accessModifier = AccessModifier.UNCHANGED;
18 System.err.println("EntryMapping initialized with 'null' accessModifier, assuming UNCHANGED. Please fix.");
19 Arrays.stream(new Exception().getStackTrace()).skip(1).map("\tat %s"::formatted).forEach(System.err::println);
20 }
21 }
10 22
11 public EntryMapping(@Nonnull String targetName) { 23 public EntryMapping(@Nullable String targetName) {
12 this(targetName, AccessModifier.UNCHANGED); 24 this(targetName, AccessModifier.UNCHANGED);
13 } 25 }
14 26
15 public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) { 27 public EntryMapping(@Nullable String targetName, @Nullable String javadoc) {
16 this(targetName, AccessModifier.UNCHANGED, javadoc); 28 this(targetName, AccessModifier.UNCHANGED, javadoc);
17 } 29 }
18 30
19 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) { 31 public EntryMapping(@Nullable String targetName, AccessModifier accessModifier) {
20 this(targetName, accessModifier, null); 32 this(targetName, accessModifier, null);
21 } 33 }
22 34
23 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) {
24 this.targetName = targetName;
25 this.accessModifier = accessModifier;
26 this.javadoc = javadoc;
27 }
28
29 @Nonnull
30 public String getTargetName() {
31 return targetName;
32 }
33
34 @Nonnull
35 public AccessModifier getAccessModifier() {
36 if (accessModifier == null) {
37 return AccessModifier.UNCHANGED;
38 }
39 return accessModifier;
40 }
41
42 @Nullable
43 public String getJavadoc() {
44 return javadoc;
45 }
46
47 public EntryMapping withName(String newName) { 35 public EntryMapping withName(String newName) {
48 return new EntryMapping(newName, accessModifier, javadoc); 36 return new EntryMapping(newName, accessModifier, javadoc);
49 } 37 }
@@ -55,21 +43,4 @@ public class EntryMapping {
55 public EntryMapping withDocs(String newDocs) { 43 public EntryMapping withDocs(String newDocs) {
56 return new EntryMapping(targetName, accessModifier, newDocs); 44 return new EntryMapping(targetName, accessModifier, newDocs);
57 } 45 }
58
59 @Override
60 public boolean equals(Object obj) {
61 if (obj == this) return true;
62
63 if (obj instanceof EntryMapping) {
64 EntryMapping mapping = (EntryMapping) obj;
65 return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier);
66 }
67
68 return false;
69 }
70
71 @Override
72 public int hashCode() {
73 return targetName.hashCode() + accessModifier.hashCode() * 31;
74 }
75} 46}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
index 8b5490e1..0977b742 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
@@ -1,10 +1,11 @@
1package cuchaz.enigma.translation.mapping; 1package cuchaz.enigma.translation.mapping;
2 2
3import java.util.Collection; 3import java.util.Collection;
4import java.util.Objects;
4import java.util.List; 5import java.util.List;
5import java.util.stream.Stream; 6import java.util.stream.Stream;
6 7
7import javax.annotation.Nullable; 8import javax.annotation.Nonnull;
8 9
9import cuchaz.enigma.analysis.index.JarIndex; 10import cuchaz.enigma.analysis.index.JarIndex;
10import cuchaz.enigma.translation.MappingTranslator; 11import cuchaz.enigma.translation.MappingTranslator;
@@ -50,15 +51,15 @@ public class EntryRemapper {
50 return new EntryRemapper(index, new HashEntryTree<>()); 51 return new EntryRemapper(index, new HashEntryTree<>());
51 } 52 }
52 53
53 public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping) { 54 public void validatePutMapping(ValidationContext vc, Entry<?> obfuscatedEntry, @Nonnull EntryMapping deobfMapping) {
54 mapFromObf(vc, obfuscatedEntry, deobfMapping, true); 55 doPutMapping(vc, obfuscatedEntry, deobfMapping, true);
55 } 56 }
56 57
57 public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) { 58 public void putMapping(ValidationContext vc, Entry<?> obfuscatedEntry, @Nonnull EntryMapping deobfMapping) {
58 mapFromObf(vc, obfuscatedEntry, deobfMapping, renaming, false); 59 doPutMapping(vc, obfuscatedEntry, deobfMapping, false);
59 } 60 }
60 61
61 public <E extends Entry<?>> void mapFromObf(ValidationContext vc, E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming, boolean validateOnly) { 62 private void doPutMapping(ValidationContext vc, Entry<?> obfuscatedEntry, @Nonnull EntryMapping deobfMapping, boolean validateOnly) {
62 if (obfuscatedEntry instanceof FieldEntry) { 63 if (obfuscatedEntry instanceof FieldEntry) {
63 FieldEntry fieldEntry = (FieldEntry) obfuscatedEntry; 64 FieldEntry fieldEntry = (FieldEntry) obfuscatedEntry;
64 ClassEntry classEntry = fieldEntry.getParent(); 65 ClassEntry classEntry = fieldEntry.getParent();
@@ -66,25 +67,27 @@ public class EntryRemapper {
66 mapRecordComponentGetter(vc, classEntry, fieldEntry, deobfMapping); 67 mapRecordComponentGetter(vc, classEntry, fieldEntry, deobfMapping);
67 } 68 }
68 69
69 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST); 70 boolean renaming = !Objects.equals(getDeobfMapping(obfuscatedEntry).targetName(), deobfMapping.targetName());
70 71
71 if (renaming && deobfMapping != null) { 72 Collection<Entry<?>> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST);
72 for (E resolvedEntry : resolvedEntries) { 73
73 validator.validateRename(vc, resolvedEntry, deobfMapping.getTargetName()); 74 if (renaming && deobfMapping.targetName() != null) {
75 for (Entry<?> resolvedEntry : resolvedEntries) {
76 validator.validateRename(vc, resolvedEntry, deobfMapping.targetName());
74 } 77 }
75 } 78 }
76 79
77 if (validateOnly || !vc.canProceed()) return; 80 if (validateOnly || !vc.canProceed()) return;
78 81
79 for (E resolvedEntry : resolvedEntries) { 82 for (Entry<?> resolvedEntry : resolvedEntries) {
80 obfToDeobf.insert(resolvedEntry, deobfMapping); 83 if (deobfMapping.equals(EntryMapping.DEFAULT)) {
84 obfToDeobf.insert(resolvedEntry, null);
85 } else {
86 obfToDeobf.insert(resolvedEntry, deobfMapping);
87 }
81 } 88 }
82 } 89 }
83 90
84 public void removeByObf(ValidationContext vc, Entry<?> obfuscatedEntry) {
85 mapFromObf(vc, obfuscatedEntry, null);
86 }
87
88 // A little bit of a hack to also map the getter method for record fields/components. 91 // A little bit of a hack to also map the getter method for record fields/components.
89 private void mapRecordComponentGetter(ValidationContext vc, ClassEntry classEntry, FieldEntry fieldEntry, EntryMapping fieldMapping) { 92 private void mapRecordComponentGetter(ValidationContext vc, ClassEntry classEntry, FieldEntry fieldEntry, EntryMapping fieldMapping) {
90 if (!jarIndex.getEntryIndex().getClassAccess(classEntry).isRecord() || jarIndex.getEntryIndex().getFieldAccess(fieldEntry).isStatic()) { 93 if (!jarIndex.getEntryIndex().getClassAccess(classEntry).isRecord() || jarIndex.getEntryIndex().getFieldAccess(fieldEntry).isStatic()) {
@@ -107,20 +110,17 @@ public class EntryRemapper {
107 } 110 }
108 111
109 if (methodEntry == null && fieldMapping != null) { 112 if (methodEntry == null && fieldMapping != null) {
110 vc.raise(Message.UNKNOWN_RECORD_GETTER, fieldMapping.getTargetName()); 113 vc.raise(Message.UNKNOWN_RECORD_GETTER, fieldMapping.targetName());
111 return; 114 return;
112 } 115 }
113 116
114 mapFromObf(vc, methodEntry, fieldMapping != null ? new EntryMapping(fieldMapping.getTargetName()) : null); 117 putMapping(vc, methodEntry, fieldMapping != null ? new EntryMapping(fieldMapping.targetName()) : null);
115 } 118 }
116 119
117 @Nullable 120 @Nonnull
118 public EntryMapping getDeobfMapping(Entry<?> entry) { 121 public EntryMapping getDeobfMapping(Entry<?> entry) {
119 return obfToDeobf.get(entry); 122 EntryMapping entryMapping = obfToDeobf.get(entry);
120 } 123 return entryMapping == null ? EntryMapping.DEFAULT : entryMapping;
121
122 public boolean hasDeobfMapping(Entry<?> obfEntry) {
123 return obfToDeobf.contains(obfEntry);
124 } 124 }
125 125
126 public <T extends Translatable> TranslateResult<T> extendedDeobfuscate(T translatable) { 126 public <T extends Translatable> TranslateResult<T> extendedDeobfuscate(T translatable) {
@@ -158,4 +158,9 @@ public class EntryRemapper {
158 public EntryResolver getObfResolver() { 158 public EntryResolver getObfResolver() {
159 return obfResolver; 159 return obfResolver;
160 } 160 }
161
162 public MappingValidator getValidator() {
163 return validator;
164 }
165
161} 166}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java
new file mode 100644
index 00000000..582076c1
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/EntryUtil.java
@@ -0,0 +1,42 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.utils.validation.ValidationContext;
7
8public class EntryUtil {
9
10 public static EntryMapping applyChange(ValidationContext vc, EntryRemapper remapper, EntryChange<?> change) {
11 Entry<?> target = change.getTarget();
12 EntryMapping prev = remapper.getDeobfMapping(target);
13 EntryMapping mapping = EntryUtil.applyChange(prev, change);
14
15 remapper.putMapping(vc, target, mapping);
16
17 return mapping;
18 }
19
20 public static EntryMapping applyChange(@Nonnull EntryMapping self, EntryChange<?> change) {
21 if (change.getDeobfName().isSet()) {
22 self = self.withName(change.getDeobfName().getNewValue());
23 } else if (change.getDeobfName().isReset()) {
24 self = self.withName(null);
25 }
26
27 if (change.getJavadoc().isSet()) {
28 self = self.withDocs(change.getJavadoc().getNewValue());
29 } else if (change.getJavadoc().isReset()) {
30 self = self.withDocs(null);
31 }
32
33 if (change.getAccess().isSet()) {
34 self = self.withModifier(change.getAccess().getNewValue());
35 } else if (change.getAccess().isReset()) {
36 self = self.withModifier(AccessModifier.UNCHANGED);
37 }
38
39 return self;
40 }
41
42}
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
index 1615912b..065e5c3e 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
@@ -25,18 +25,22 @@ public class MappingValidator {
25 this.index = index; 25 this.index = index;
26 } 26 }
27 27
28 public void validateRename(ValidationContext vc, Entry<?> entry, String name) { 28 public boolean validateRename(ValidationContext vc, Entry<?> entry, String name) {
29 Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry); 29 Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry);
30 boolean error = false;
30 for (Entry<?> equivalentEntry : equivalentEntries) { 31 for (Entry<?> equivalentEntry : equivalentEntries) {
31 equivalentEntry.validateName(vc, name); 32 equivalentEntry.validateName(vc, name);
32 validateUnique(vc, equivalentEntry, name); 33 error |= validateUnique(vc, equivalentEntry, name);
33 } 34 }
35 return error;
34 } 36 }
35 37
36 private void validateUnique(ValidationContext vc, Entry<?> entry, String name) { 38 private boolean validateUnique(ValidationContext vc, Entry<?> entry, String name) {
37 ClassEntry containingClass = entry.getContainingClass(); 39 ClassEntry containingClass = entry.getContainingClass();
38 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass); 40 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass);
39 41
42 boolean error = false;
43
40 for (ClassEntry relatedClass : relatedClasses) { 44 for (ClassEntry relatedClass : relatedClasses) {
41 Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass); 45 Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass);
42 Entry<?> translatedEntry = deobfuscator.translate(relatedEntry); 46 Entry<?> translatedEntry = deobfuscator.translate(relatedEntry);
@@ -52,8 +56,11 @@ public class MappingValidator {
52 } else { 56 } else {
53 vc.raise(Message.NONUNIQUE_NAME, name); 57 vc.raise(Message.NONUNIQUE_NAME, name);
54 } 58 }
59 error = true;
55 } 60 }
56 } 61 }
62
63 return error;
57 } 64 }
58 65
59 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) { 66 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
index 141a07cc..392b1a67 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
@@ -75,7 +75,7 @@ public class MappingsChecker {
75 private final Map<Entry<?>, String> droppedMappings = new HashMap<>(); 75 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
76 76
77 public void drop(Entry<?> entry, EntryMapping mapping) { 77 public void drop(Entry<?> entry, EntryMapping mapping) {
78 droppedMappings.put(entry, mapping.getTargetName()); 78 droppedMappings.put(entry, mapping.targetName() != null ? mapping.targetName() : entry.getName());
79 } 79 }
80 80
81 void apply(EntryTree<EntryMapping> mappings) { 81 void apply(EntryTree<EntryMapping> mappings) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
index 79587a0b..6465008b 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
@@ -1,23 +1,23 @@
1package cuchaz.enigma.translation.mapping.serde; 1package cuchaz.enigma.translation.mapping.serde;
2 2
3import cuchaz.enigma.translation.mapping.AccessModifier;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5
6import java.util.ArrayList; 3import java.util.ArrayList;
7import java.util.List; 4import java.util.List;
8 5
6import cuchaz.enigma.translation.mapping.AccessModifier;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8
9public final class RawEntryMapping { 9public final class RawEntryMapping {
10 private final String targetName; 10 private final String targetName;
11 private final AccessModifier access; 11 private final AccessModifier access;
12 private List<String> javadocs = new ArrayList<>(); 12 private final List<String> javadocs = new ArrayList<>();
13 13
14 public RawEntryMapping(String targetName) { 14 public RawEntryMapping(String targetName) {
15 this(targetName, null); 15 this(targetName, AccessModifier.UNCHANGED);
16 } 16 }
17 17
18 public RawEntryMapping(String targetName, AccessModifier access) { 18 public RawEntryMapping(String targetName, AccessModifier access) {
19 this.access = access; 19 this.access = access;
20 this.targetName = targetName; 20 this.targetName = targetName != null && !targetName.equals("-") ? targetName : null;
21 } 21 }
22 22
23 public void addJavadocLine(String line) { 23 public void addJavadocLine(String line) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java
index 27545c03..ccc25dff 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsReader.java
@@ -1,15 +1,21 @@
1package cuchaz.enigma.translation.mapping.serde.enigma; 1package cuchaz.enigma.translation.mapping.serde.enigma;
2 2
3import java.io.IOException;
4import java.nio.file.FileSystem;
5import java.nio.file.FileSystems;
6import java.nio.file.Files;
7import java.nio.file.Path;
8import java.util.*;
9
10import javax.annotation.Nullable;
11
3import com.google.common.base.Charsets; 12import com.google.common.base.Charsets;
13
4import cuchaz.enigma.ProgressListener; 14import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.serde.MappingParseException;
6import cuchaz.enigma.translation.mapping.AccessModifier; 15import cuchaz.enigma.translation.mapping.AccessModifier;
7import cuchaz.enigma.translation.mapping.EntryMapping; 16import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingPair; 17import cuchaz.enigma.translation.mapping.MappingPair;
9import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; 18import cuchaz.enigma.translation.mapping.serde.*;
10import cuchaz.enigma.translation.mapping.serde.MappingHelper;
11import cuchaz.enigma.translation.mapping.serde.MappingsReader;
12import cuchaz.enigma.translation.mapping.serde.RawEntryMapping;
13import cuchaz.enigma.translation.mapping.tree.EntryTree; 19import cuchaz.enigma.translation.mapping.tree.EntryTree;
14import cuchaz.enigma.translation.mapping.tree.HashEntryTree; 20import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
15import cuchaz.enigma.translation.representation.MethodDescriptor; 21import cuchaz.enigma.translation.representation.MethodDescriptor;
@@ -17,18 +23,6 @@ import cuchaz.enigma.translation.representation.TypeDescriptor;
17import cuchaz.enigma.translation.representation.entry.*; 23import cuchaz.enigma.translation.representation.entry.*;
18import cuchaz.enigma.utils.I18n; 24import cuchaz.enigma.utils.I18n;
19 25
20import javax.annotation.Nullable;
21import java.io.IOException;
22import java.nio.file.FileSystem;
23import java.nio.file.FileSystems;
24import java.nio.file.Files;
25import java.nio.file.Path;
26import java.util.ArrayDeque;
27import java.util.Arrays;
28import java.util.Deque;
29import java.util.List;
30import java.util.Locale;
31
32public enum EnigmaMappingsReader implements MappingsReader { 26public enum EnigmaMappingsReader implements MappingsReader {
33 FILE { 27 FILE {
34 @Override 28 @Override
@@ -200,12 +194,12 @@ public enum EnigmaMappingsReader implements MappingsReader {
200 throw new RuntimeException("Unknown token '" + keyToken + "'"); 194 throw new RuntimeException("Unknown token '" + keyToken + "'");
201 } 195 }
202 } 196 }
203 197
204 private static void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) { 198 private static void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) {
205 if (parent == null) 199 if (parent == null)
206 throw new IllegalStateException("Javadoc has no parent!"); 200 throw new IllegalStateException("Javadoc has no parent!");
207 // Empty string to concat 201 // Empty string to concat
208 String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : ""; 202 String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens, 1, tokens.length)) : "";
209 if (parent.getMapping() == null) { 203 if (parent.getMapping() == null) {
210 parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED)); 204 parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED));
211 } 205 }
@@ -237,11 +231,7 @@ public enum EnigmaMappingsReader implements MappingsReader {
237 modifier = parseModifier(tokens[3]); 231 modifier = parseModifier(tokens[3]);
238 } 232 }
239 233
240 if (mapping != null) { 234 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
241 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
242 } else {
243 return new MappingPair<>(obfuscatedEntry);
244 }
245 } 235 }
246 236
247 private static MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) { 237 private static MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
@@ -252,7 +242,7 @@ public enum EnigmaMappingsReader implements MappingsReader {
252 ClassEntry ownerEntry = (ClassEntry) parent; 242 ClassEntry ownerEntry = (ClassEntry) parent;
253 243
254 String obfuscatedName = tokens[1]; 244 String obfuscatedName = tokens[1];
255 String mapping = obfuscatedName; 245 String mapping = null;
256 AccessModifier modifier = AccessModifier.UNCHANGED; 246 AccessModifier modifier = AccessModifier.UNCHANGED;
257 TypeDescriptor descriptor; 247 TypeDescriptor descriptor;
258 248
@@ -269,19 +259,15 @@ public enum EnigmaMappingsReader implements MappingsReader {
269 descriptor = new TypeDescriptor(tokens[3]); 259 descriptor = new TypeDescriptor(tokens[3]);
270 } 260 }
271 } else if (tokens.length == 5) { 261 } else if (tokens.length == 5) {
272 descriptor = new TypeDescriptor(tokens[3]);
273 mapping = tokens[2]; 262 mapping = tokens[2];
274 modifier = parseModifier(tokens[4]); 263 modifier = parseModifier(tokens[3]);
264 descriptor = new TypeDescriptor(tokens[4]);
275 } else { 265 } else {
276 throw new RuntimeException("Invalid field declaration"); 266 throw new RuntimeException("Invalid field declaration");
277 } 267 }
278 268
279 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor); 269 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
280 if (mapping != null) { 270 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
281 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
282 } else {
283 return new MappingPair<>(obfuscatedEntry);
284 }
285 } 271 }
286 272
287 private static MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) { 273 private static MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
@@ -317,11 +303,7 @@ public enum EnigmaMappingsReader implements MappingsReader {
317 } 303 }
318 304
319 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor); 305 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
320 if (mapping != null) { 306 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
321 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
322 } else {
323 return new MappingPair<>(obfuscatedEntry);
324 }
325 } 307 }
326 308
327 private static MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) { 309 private static MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java
index d9f1470d..5d7a7893 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/enigma/EnigmaMappingsWriter.java
@@ -15,12 +15,7 @@ import java.io.IOException;
15import java.io.PrintWriter; 15import java.io.PrintWriter;
16import java.net.URI; 16import java.net.URI;
17import java.net.URISyntaxException; 17import java.net.URISyntaxException;
18import java.nio.file.DirectoryStream; 18import java.nio.file.*;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24import java.util.ArrayList; 19import java.util.ArrayList;
25import java.util.Collection; 20import java.util.Collection;
26import java.util.Collections; 21import java.util.Collections;
@@ -28,25 +23,19 @@ import java.util.Objects;
28import java.util.concurrent.atomic.AtomicInteger; 23import java.util.concurrent.atomic.AtomicInteger;
29import java.util.stream.Stream; 24import java.util.stream.Stream;
30 25
26import javax.annotation.Nonnull;
27
31import cuchaz.enigma.ProgressListener; 28import cuchaz.enigma.ProgressListener;
32import cuchaz.enigma.translation.MappingTranslator; 29import cuchaz.enigma.translation.MappingTranslator;
33import cuchaz.enigma.translation.Translator; 30import cuchaz.enigma.translation.Translator;
34import cuchaz.enigma.translation.mapping.AccessModifier; 31import cuchaz.enigma.translation.mapping.AccessModifier;
35import cuchaz.enigma.translation.mapping.EntryMapping; 32import cuchaz.enigma.translation.mapping.EntryMapping;
36import cuchaz.enigma.translation.mapping.MappingDelta; 33import cuchaz.enigma.translation.mapping.MappingDelta;
37import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
38import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
39import cuchaz.enigma.translation.mapping.VoidEntryResolver; 34import cuchaz.enigma.translation.mapping.VoidEntryResolver;
40import cuchaz.enigma.translation.mapping.serde.LfPrintWriter; 35import cuchaz.enigma.translation.mapping.serde.*;
41import cuchaz.enigma.translation.mapping.serde.MappingHelper;
42import cuchaz.enigma.translation.mapping.serde.MappingsWriter;
43import cuchaz.enigma.translation.mapping.tree.EntryTree; 36import cuchaz.enigma.translation.mapping.tree.EntryTree;
44import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; 37import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
45import cuchaz.enigma.translation.representation.entry.ClassEntry; 38import cuchaz.enigma.translation.representation.entry.*;
46import cuchaz.enigma.translation.representation.entry.Entry;
47import cuchaz.enigma.translation.representation.entry.FieldEntry;
48import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
49import cuchaz.enigma.translation.representation.entry.MethodEntry;
50import cuchaz.enigma.utils.I18n; 39import cuchaz.enigma.utils.I18n;
51 40
52public enum EnigmaMappingsWriter implements MappingsWriter { 41public enum EnigmaMappingsWriter implements MappingsWriter {
@@ -184,19 +173,22 @@ public enum EnigmaMappingsWriter implements MappingsWriter {
184 173
185 EntryMapping classEntryMapping = mappings.get(classEntry); 174 EntryMapping classEntryMapping = mappings.get(classEntry);
186 175
176 if (classEntryMapping == null) {
177 classEntryMapping = EntryMapping.DEFAULT;
178 }
179
187 writer.println(writeClass(classEntry, classEntryMapping).trim()); 180 writer.println(writeClass(classEntry, classEntryMapping).trim());
188 if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) { 181 if (classEntryMapping.javadoc() != null) {
189 writeDocs(writer, classEntryMapping, 0); 182 writeDocs(writer, classEntryMapping, 0);
190 } 183 }
191 184
192 for (Entry<?> child : children) { 185 for (Entry<?> child : children) {
193 writeEntry(writer, mappings, child, 1); 186 writeEntry(writer, mappings, child, 1);
194 } 187 }
195
196 } 188 }
197 189
198 private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) { 190 private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) {
199 String jd = mapping.getJavadoc(); 191 String jd = mapping.javadoc();
200 if (jd != null) { 192 if (jd != null) {
201 for (String line : jd.split("\\R")) { 193 for (String line : jd.split("\\R")) {
202 writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1)); 194 writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1));
@@ -212,20 +204,26 @@ public enum EnigmaMappingsWriter implements MappingsWriter {
212 204
213 EntryMapping mapping = node.getValue(); 205 EntryMapping mapping = node.getValue();
214 206
215 if (entry instanceof ClassEntry) { 207 if (mapping == null) {
216 String line = writeClass((ClassEntry) entry, mapping); 208 mapping = EntryMapping.DEFAULT;
217 writer.println(indent(line, depth)); 209 }
218 } else if (entry instanceof MethodEntry) { 210
219 String line = writeMethod((MethodEntry) entry, mapping); 211 String line = null;
220 writer.println(indent(line, depth)); 212 if (entry instanceof ClassEntry classEntry) {
221 } else if (entry instanceof FieldEntry) { 213 line = writeClass(classEntry, mapping);
222 String line = writeField((FieldEntry) entry, mapping); 214 } else if (entry instanceof MethodEntry methodEntry) {
223 writer.println(indent(line, depth)); 215 line = writeMethod(methodEntry, mapping);
224 } else if (entry instanceof LocalVariableEntry && mapping != null) { 216 } else if (entry instanceof FieldEntry fieldEntry) {
225 String line = writeArgument((LocalVariableEntry) entry, mapping); 217 line = writeField(fieldEntry, mapping);
218 } else if (entry instanceof LocalVariableEntry varEntry && mapping.targetName() != null) {
219 line = writeArgument(varEntry, mapping);
220 }
221
222 if (line != null) {
226 writer.println(indent(line, depth)); 223 writer.println(indent(line, depth));
227 } 224 }
228 if (mapping != null && mapping.getJavadoc() != null) { 225
226 if (mapping.javadoc() != null) {
229 writeDocs(writer, mapping, depth); 227 writeDocs(writer, mapping, depth);
230 } 228 }
231 229
@@ -254,55 +252,53 @@ public enum EnigmaMappingsWriter implements MappingsWriter {
254 .forEach(result::add); 252 .forEach(result::add);
255 253
256 children.stream().filter(e -> e instanceof ClassEntry) 254 children.stream().filter(e -> e instanceof ClassEntry)
257 .map(e -> (ClassEntry) e) 255 .map(e -> (ClassEntry) e)
258 .sorted() 256 .sorted()
259 .forEach(result::add); 257 .forEach(result::add);
260 258
261 return result; 259 return result;
262 } 260 }
263 261
264 protected String writeClass(ClassEntry entry, EntryMapping mapping) { 262 protected String writeClass(ClassEntry entry, @Nonnull EntryMapping mapping) {
265 StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" "); 263 StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS + " ");
266 builder.append(entry.getName()).append(' '); 264 builder.append(entry.getName()).append(' ');
267 writeMapping(builder, mapping); 265 writeMapping(builder, mapping);
268 266
269 return builder.toString(); 267 return builder.toString();
270 } 268 }
271 269
272 protected String writeMethod(MethodEntry entry, EntryMapping mapping) { 270 protected String writeMethod(MethodEntry entry, @Nonnull EntryMapping mapping) {
273 StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " "); 271 StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " ");
274 builder.append(entry.getName()).append(' '); 272 builder.append(entry.getName()).append(' ');
275 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { 273 writeMapping(builder, mapping);
276 writeMapping(builder, mapping);
277 }
278 274
279 builder.append(entry.getDesc().toString()); 275 builder.append(entry.getDesc().toString());
280 276
281 return builder.toString(); 277 return builder.toString();
282 } 278 }
283 279
284 protected String writeField(FieldEntry entry, EntryMapping mapping) { 280 protected String writeField(FieldEntry entry, @Nonnull EntryMapping mapping) {
285 StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " "); 281 StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " ");
286 builder.append(entry.getName()).append(' '); 282 builder.append(entry.getName()).append(' ');
287 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) { 283 writeMapping(builder, mapping);
288 writeMapping(builder, mapping);
289 }
290 284
291 builder.append(entry.getDesc().toString()); 285 builder.append(entry.getDesc().toString());
292 286
293 return builder.toString(); 287 return builder.toString();
294 } 288 }
295 289
296 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) { 290 protected String writeArgument(LocalVariableEntry entry, @Nonnull EntryMapping mapping) {
297 return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName(); 291 return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.targetName();
298 } 292 }
299 293
300 private void writeMapping(StringBuilder builder, EntryMapping mapping) { 294 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
301 if (mapping != null) { 295 if (mapping.targetName() != null) {
302 builder.append(mapping.getTargetName()).append(' '); 296 builder.append(mapping.targetName()).append(' ');
303 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) { 297 if (mapping.accessModifier() != AccessModifier.UNCHANGED) {
304 builder.append(mapping.getAccessModifier().getFormattedName()).append(' '); 298 builder.append(mapping.accessModifier().getFormattedName()).append(' ');
305 } 299 }
300 } else if (mapping.accessModifier() != AccessModifier.UNCHANGED) {
301 builder.append("- ").append(mapping.accessModifier().getFormattedName()).append(' ');
306 } 302 }
307 } 303 }
308 304
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java
index 4f78e6f3..1f785e10 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tiny/TinyMappingsWriter.java
@@ -73,13 +73,16 @@ public class TinyMappingsWriter implements MappingsWriter {
73 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE); 73 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
74 74
75 EntryMapping mapping = mappings.get(entry); 75 EntryMapping mapping = mappings.get(entry);
76 if (mapping != null && !entry.getName().equals(mapping.getTargetName())) { 76
77 // Do not write mappings without deobfuscated name since tiny v1 doesn't
78 // support comments anyway
79 if (mapping != null && mapping.targetName() != null) {
77 if (entry instanceof ClassEntry) { 80 if (entry instanceof ClassEntry) {
78 writeClass(writer, (ClassEntry) entry, translator); 81 writeClass(writer, (ClassEntry) entry, translator);
79 } else if (entry instanceof FieldEntry) { 82 } else if (entry instanceof FieldEntry) {
80 writeLine(writer, serializeEntry(entry, mapping.getTargetName())); 83 writeLine(writer, serializeEntry(entry, mapping.targetName()));
81 } else if (entry instanceof MethodEntry) { 84 } else if (entry instanceof MethodEntry) {
82 writeLine(writer, serializeEntry(entry, mapping.getTargetName())); 85 writeLine(writer, serializeEntry(entry, mapping.targetName()));
83 } 86 }
84 } 87 }
85 88
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java
index 5160eda3..c4005688 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/serde/tinyv2/TinyV2Writer.java
@@ -62,8 +62,8 @@ public final class TinyV2Writer implements MappingsWriter {
62 Deque<String> parts = new LinkedList<>(); 62 Deque<String> parts = new LinkedList<>();
63 do { 63 do {
64 EntryMapping mapping = tree.get(classEntry); 64 EntryMapping mapping = tree.get(classEntry);
65 if (mapping != null) { 65 if (mapping != null && mapping.targetName() != null) {
66 parts.addFirst(mapping.getTargetName()); 66 parts.addFirst(mapping.targetName());
67 } else { 67 } else {
68 parts.addFirst(classEntry.getName()); 68 parts.addFirst(classEntry.getName());
69 } 69 }
@@ -81,7 +81,7 @@ public final class TinyV2Writer implements MappingsWriter {
81 writeComment(writer, node.getValue(), 1); 81 writeComment(writer, node.getValue(), 1);
82 82
83 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) { 83 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
84 Entry entry = child.getEntry(); 84 Entry<?> entry = child.getEntry();
85 if (entry instanceof FieldEntry) { 85 if (entry instanceof FieldEntry) {
86 writeField(writer, child); 86 writeField(writer, child);
87 } else if (entry instanceof MethodEntry) { 87 } else if (entry instanceof MethodEntry) {
@@ -98,16 +98,21 @@ public final class TinyV2Writer implements MappingsWriter {
98 writer.print(node.getEntry().getName()); 98 writer.print(node.getEntry().getName());
99 writer.print("\t"); 99 writer.print("\t");
100 EntryMapping mapping = node.getValue(); 100 EntryMapping mapping = node.getValue();
101
101 if (mapping == null) { 102 if (mapping == null) {
102 writer.println(node.getEntry().getName()); // todo fix v2 name inference 103 mapping = EntryMapping.DEFAULT;
103 } else { 104 }
104 writer.println(mapping.getTargetName());
105 105
106 writeComment(writer, mapping, 2); 106 if (mapping.targetName() != null) {
107 writer.println(mapping.targetName());
108 } else {
109 writer.println(node.getEntry().getName()); // todo fix v2 name inference
107 } 110 }
108 111
112 writeComment(writer, mapping, 2);
113
109 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) { 114 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
110 Entry entry = child.getEntry(); 115 Entry<?> entry = child.getEntry();
111 if (entry instanceof LocalVariableEntry) { 116 if (entry instanceof LocalVariableEntry) {
112 writeParameter(writer, child); 117 writeParameter(writer, child);
113 } 118 }
@@ -116,7 +121,7 @@ public final class TinyV2Writer implements MappingsWriter {
116 } 121 }
117 122
118 private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) { 123 private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
119 if (node.getValue() == null) 124 if (node.getValue() == null || node.getValue().equals(EntryMapping.DEFAULT))
120 return; // Shortcut 125 return; // Shortcut
121 126
122 writer.print(indent(1)); 127 writer.print(indent(1));
@@ -126,17 +131,22 @@ public final class TinyV2Writer implements MappingsWriter {
126 writer.print(node.getEntry().getName()); 131 writer.print(node.getEntry().getName());
127 writer.print("\t"); 132 writer.print("\t");
128 EntryMapping mapping = node.getValue(); 133 EntryMapping mapping = node.getValue();
134
129 if (mapping == null) { 135 if (mapping == null) {
130 writer.println(node.getEntry().getName()); // todo fix v2 name inference 136 mapping = EntryMapping.DEFAULT;
131 } else { 137 }
132 writer.println(mapping.getTargetName());
133 138
134 writeComment(writer, mapping, 2); 139 if (mapping.targetName() != null) {
140 writer.println(mapping.targetName());
141 } else {
142 writer.println(node.getEntry().getName()); // todo fix v2 name inference
135 } 143 }
144
145 writeComment(writer, mapping, 2);
136 } 146 }
137 147
138 private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) { 148 private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
139 if (node.getValue() == null) 149 if (node.getValue() == null || node.getValue().equals(EntryMapping.DEFAULT))
140 return; // Shortcut 150 return; // Shortcut
141 151
142 writer.print(indent(2)); 152 writer.print(indent(2));
@@ -146,20 +156,20 @@ public final class TinyV2Writer implements MappingsWriter {
146 writer.print(node.getEntry().getName()); 156 writer.print(node.getEntry().getName());
147 writer.print("\t"); 157 writer.print("\t");
148 EntryMapping mapping = node.getValue(); 158 EntryMapping mapping = node.getValue();
149 if (mapping == null) { 159 if (mapping == null || mapping.targetName() == null) {
150 writer.println(); // todo ??? 160 writer.println(); // todo ???
151 } else { 161 } else {
152 writer.println(mapping.getTargetName()); 162 writer.println(mapping.targetName());
153 163
154 writeComment(writer, mapping, 3); 164 writeComment(writer, mapping, 3);
155 } 165 }
156 } 166 }
157 167
158 private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) { 168 private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) {
159 if (mapping != null && mapping.getJavadoc() != null) { 169 if (mapping != null && mapping.javadoc() != null) {
160 writer.print(indent(indent)); 170 writer.print(indent(indent));
161 writer.print("c\t"); 171 writer.print("c\t");
162 writer.print(MappingHelper.escape(mapping.getJavadoc())); 172 writer.print(MappingHelper.escape(mapping.javadoc()));
163 writer.println(); 173 writer.println();
164 } 174 }
165 } 175 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
index 570941cd..0992d342 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
@@ -6,6 +6,7 @@ import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver; 6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.representation.entry.Entry; 7import cuchaz.enigma.translation.representation.entry.Entry;
8 8
9import javax.annotation.Nonnull;
9import javax.annotation.Nullable; 10import javax.annotation.Nullable;
10import java.util.*; 11import java.util.*;
11import java.util.function.Function; 12import java.util.function.Function;
@@ -152,6 +153,7 @@ public class HashEntryTree<T> implements EntryTree<T> {
152 } 153 }
153 154
154 @Override 155 @Override
156 @Nonnull
155 public Iterator<EntryTreeNode<T>> iterator() { 157 public Iterator<EntryTreeNode<T>> iterator() {
156 Collection<EntryTreeNode<T>> nodes = new ArrayList<>(); 158 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
157 for (EntryTreeNode<T> node : root.values()) { 159 for (EntryTreeNode<T> node : root.values()) {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
index a6aed731..13c7cd48 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
@@ -35,9 +35,9 @@ public class Lambda implements Translatable {
35 EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); 35 EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod);
36 36
37 return TranslateResult.of( 37 return TranslateResult.of(
38 samMethodMapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 38 samMethodMapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
39 new Lambda( 39 new Lambda(
40 samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, 40 samMethodMapping.targetName() != null ? samMethodMapping.targetName() : invokedName,
41 invokedType.extendedTranslate(translator, resolver, mappings).getValue(), 41 invokedType.extendedTranslate(translator, resolver, mappings).getValue(),
42 samMethodType.extendedTranslate(translator, resolver, mappings).getValue(), 42 samMethodType.extendedTranslate(translator, resolver, mappings).getValue(),
43 implMethod.extendedTranslate(translator, resolver, mappings).getValue(), 43 implMethod.extendedTranslate(translator, resolver, mappings).getValue(),
@@ -53,7 +53,7 @@ public class Lambda implements Translatable {
53 return mapping; 53 return mapping;
54 } 54 }
55 } 55 }
56 return null; 56 return EntryMapping.DEFAULT;
57 } 57 }
58 58
59 public ClassEntry getInterface() { 59 public ClassEntry getInterface() {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
index b0fd2865..237c93dc 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
@@ -13,6 +13,7 @@ package cuchaz.enigma.translation.representation.entry;
13 13
14import java.util.Arrays; 14import java.util.Arrays;
15 15
16import javax.annotation.Nonnull;
16import javax.annotation.Nullable; 17import javax.annotation.Nullable;
17 18
18import com.google.common.base.Preconditions; 19import com.google.common.base.Preconditions;
@@ -75,15 +76,15 @@ public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> {
75 } 76 }
76 77
77 @Override 78 @Override
78 public TranslateResult<ClassDefEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 79 public TranslateResult<ClassDefEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
79 Signature translatedSignature = translator.translate(signature); 80 Signature translatedSignature = translator.translate(signature);
80 String translatedName = mapping != null ? mapping.getTargetName() : name; 81 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
81 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; 82 AccessFlags translatedAccess = mapping.accessModifier().transform(access);
82 ClassEntry translatedSuper = translator.translate(superClass); 83 ClassEntry translatedSuper = translator.translate(superClass);
83 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new); 84 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new);
84 String docs = mapping != null ? mapping.getJavadoc() : null; 85 String docs = mapping.javadoc();
85 return TranslateResult.of( 86 return TranslateResult.of(
86 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 87 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
87 new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs) 88 new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs)
88 ); 89 );
89 } 90 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
index fe56611b..ec5294ed 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
@@ -82,16 +82,16 @@ public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<
82 } 82 }
83 83
84 @Override 84 @Override
85 public TranslateResult<? extends ClassEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 85 public TranslateResult<? extends ClassEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
86 if (name.charAt(0) == '[') { 86 if (name.charAt(0) == '[') {
87 TranslateResult<TypeDescriptor> translatedName = translator.extendedTranslate(new TypeDescriptor(name)); 87 TranslateResult<TypeDescriptor> translatedName = translator.extendedTranslate(new TypeDescriptor(name));
88 return translatedName.map(desc -> new ClassEntry(parent, desc.toString())); 88 return translatedName.map(desc -> new ClassEntry(parent, desc.toString()));
89 } 89 }
90 90
91 String translatedName = mapping != null ? mapping.getTargetName() : name; 91 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
92 String docs = mapping != null ? mapping.getJavadoc() : null; 92 String docs = mapping.javadoc();
93 return TranslateResult.of( 93 return TranslateResult.of(
94 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 94 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
95 new ClassEntry(parent, translatedName, docs) 95 new ClassEntry(parent, translatedName, docs)
96 ); 96 );
97 } 97 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
index 8a81c54a..956f32ca 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
@@ -13,6 +13,7 @@ package cuchaz.enigma.translation.representation.entry;
13 13
14import java.util.ArrayList; 14import java.util.ArrayList;
15import java.util.List; 15import java.util.List;
16import java.util.Objects;
16 17
17import javax.annotation.Nullable; 18import javax.annotation.Nullable;
18 19
@@ -107,16 +108,29 @@ public interface Entry<P extends Entry<?>> extends Translatable {
107 108
108 boolean canConflictWith(Entry<?> entry); 109 boolean canConflictWith(Entry<?> entry);
109 110
110 @Nullable
111 default ClassEntry getContainingClass() { 111 default ClassEntry getContainingClass() {
112 P parent = getParent(); 112 ClassEntry last = null;
113 if (parent == null) { 113 Entry<?> current = this;
114 return null; 114 while (current != null) {
115 if (current instanceof ClassEntry) {
116 last = (ClassEntry) current;
117 break;
118 }
119 current = current.getParent();
115 } 120 }
116 if (parent instanceof ClassEntry) { 121 return Objects.requireNonNull(last, () -> String.format("%s has no containing class?", this));
117 return (ClassEntry) parent; 122 }
123
124 default ClassEntry getTopLevelClass() {
125 ClassEntry last = null;
126 Entry<?> current = this;
127 while (current != null) {
128 if (current instanceof ClassEntry) {
129 last = (ClassEntry) current;
130 }
131 current = current.getParent();
118 } 132 }
119 return parent.getContainingClass(); 133 return Objects.requireNonNull(last, () -> String.format("%s has no top level class?", this));
120 } 134 }
121 135
122 default List<Entry<?>> getAncestry() { 136 default List<Entry<?>> getAncestry() {
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
index b9da6cca..0efb6a99 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
@@ -11,8 +11,6 @@
11 11
12package cuchaz.enigma.translation.representation.entry; 12package cuchaz.enigma.translation.representation.entry;
13 13
14import javax.annotation.Nullable;
15
16import com.google.common.base.Preconditions; 14import com.google.common.base.Preconditions;
17 15
18import cuchaz.enigma.source.RenamableTokenType; 16import cuchaz.enigma.source.RenamableTokenType;
@@ -23,6 +21,8 @@ import cuchaz.enigma.translation.representation.AccessFlags;
23import cuchaz.enigma.translation.representation.Signature; 21import cuchaz.enigma.translation.representation.Signature;
24import cuchaz.enigma.translation.representation.TypeDescriptor; 22import cuchaz.enigma.translation.representation.TypeDescriptor;
25 23
24import javax.annotation.Nonnull;
25
26public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> { 26public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
27 private final AccessFlags access; 27 private final AccessFlags access;
28 private final Signature signature; 28 private final Signature signature;
@@ -53,14 +53,14 @@ public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
53 } 53 }
54 54
55 @Override 55 @Override
56 protected TranslateResult<FieldEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 56 protected TranslateResult<FieldEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
57 TypeDescriptor translatedDesc = translator.translate(desc); 57 TypeDescriptor translatedDesc = translator.translate(desc);
58 Signature translatedSignature = translator.translate(signature); 58 Signature translatedSignature = translator.translate(signature);
59 String translatedName = mapping != null ? mapping.getTargetName() : name; 59 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
60 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; 60 AccessFlags translatedAccess = mapping.accessModifier().transform(access);
61 String docs = mapping != null ? mapping.getJavadoc() : null; 61 String docs = mapping.javadoc();
62 return TranslateResult.of( 62 return TranslateResult.of(
63 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 63 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
64 new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs) 64 new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs)
65 ); 65 );
66 } 66 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
index 5ddd33de..db940118 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
@@ -13,7 +13,7 @@ package cuchaz.enigma.translation.representation.entry;
13 13
14import java.util.Objects; 14import java.util.Objects;
15 15
16import javax.annotation.Nullable; 16import javax.annotation.Nonnull;
17 17
18import com.google.common.base.Preconditions; 18import com.google.common.base.Preconditions;
19 19
@@ -63,11 +63,11 @@ public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<
63 } 63 }
64 64
65 @Override 65 @Override
66 protected TranslateResult<FieldEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 66 protected TranslateResult<FieldEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
67 String translatedName = mapping != null ? mapping.getTargetName() : name; 67 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
68 String docs = mapping != null ? mapping.getJavadoc() : null; 68 String docs = mapping.javadoc();
69 return TranslateResult.of( 69 return TranslateResult.of(
70 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 70 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
71 new FieldEntry(parent, translatedName, translator.translate(desc), docs) 71 new FieldEntry(parent, translatedName, translator.translate(desc), docs)
72 ); 72 );
73 } 73 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
index 2712c65a..c151de4e 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
@@ -1,6 +1,6 @@
1package cuchaz.enigma.translation.representation.entry; 1package cuchaz.enigma.translation.representation.entry;
2 2
3import javax.annotation.Nullable; 3import javax.annotation.Nonnull;
4 4
5import com.google.common.base.Preconditions; 5import com.google.common.base.Preconditions;
6 6
@@ -30,12 +30,12 @@ public class LocalVariableDefEntry extends LocalVariableEntry {
30 } 30 }
31 31
32 @Override 32 @Override
33 protected TranslateResult<LocalVariableEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 33 protected TranslateResult<LocalVariableEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
34 TypeDescriptor translatedDesc = translator.translate(desc); 34 TypeDescriptor translatedDesc = translator.translate(desc);
35 String translatedName = mapping != null ? mapping.getTargetName() : name; 35 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
36 String javadoc = mapping != null ? mapping.getJavadoc() : javadocs; 36 String javadoc = mapping.javadoc();
37 return TranslateResult.of( 37 return TranslateResult.of(
38 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 38 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
39 new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc) 39 new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc)
40 ); 40 );
41 } 41 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
index 154f11fd..1cf1a832 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
@@ -2,7 +2,7 @@ package cuchaz.enigma.translation.representation.entry;
2 2
3import java.util.Objects; 3import java.util.Objects;
4 4
5import javax.annotation.Nullable; 5import javax.annotation.Nonnull;
6 6
7import com.google.common.base.Preconditions; 7import com.google.common.base.Preconditions;
8 8
@@ -50,11 +50,11 @@ public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Co
50 } 50 }
51 51
52 @Override 52 @Override
53 protected TranslateResult<LocalVariableEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 53 protected TranslateResult<LocalVariableEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
54 String translatedName = mapping != null ? mapping.getTargetName() : name; 54 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
55 String javadoc = mapping != null ? mapping.getJavadoc() : null; 55 String javadoc = mapping.javadoc();
56 return TranslateResult.of( 56 return TranslateResult.of(
57 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 57 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
58 new LocalVariableEntry(parent, index, translatedName, parameter, javadoc) 58 new LocalVariableEntry(parent, index, translatedName, parameter, javadoc)
59 ); 59 );
60 } 60 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
index cc326d6a..30ef706e 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
@@ -11,7 +11,7 @@
11 11
12package cuchaz.enigma.translation.representation.entry; 12package cuchaz.enigma.translation.representation.entry;
13 13
14import javax.annotation.Nullable; 14import javax.annotation.Nonnull;
15 15
16import com.google.common.base.Preconditions; 16import com.google.common.base.Preconditions;
17 17
@@ -53,14 +53,14 @@ public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry>
53 } 53 }
54 54
55 @Override 55 @Override
56 protected TranslateResult<MethodDefEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 56 protected TranslateResult<MethodDefEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
57 MethodDescriptor translatedDesc = translator.translate(descriptor); 57 MethodDescriptor translatedDesc = translator.translate(descriptor);
58 Signature translatedSignature = translator.translate(signature); 58 Signature translatedSignature = translator.translate(signature);
59 String translatedName = mapping != null ? mapping.getTargetName() : name; 59 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
60 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access; 60 AccessFlags translatedAccess = mapping.accessModifier().transform(access);
61 String docs = mapping != null ? mapping.getJavadoc() : null; 61 String docs = mapping.javadoc();
62 return TranslateResult.of( 62 return TranslateResult.of(
63 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 63 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
64 new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs) 64 new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs)
65 ); 65 );
66 } 66 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
index 864a5802..ab9c2d17 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
@@ -13,7 +13,7 @@ package cuchaz.enigma.translation.representation.entry;
13 13
14import java.util.Objects; 14import java.util.Objects;
15 15
16import javax.annotation.Nullable; 16import javax.annotation.Nonnull;
17 17
18import com.google.common.base.Preconditions; 18import com.google.common.base.Preconditions;
19 19
@@ -58,11 +58,11 @@ public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable
58 } 58 }
59 59
60 @Override 60 @Override
61 protected TranslateResult<? extends MethodEntry> extendedTranslate(Translator translator, @Nullable EntryMapping mapping) { 61 protected TranslateResult<? extends MethodEntry> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping) {
62 String translatedName = mapping != null ? mapping.getTargetName() : name; 62 String translatedName = mapping.targetName() != null ? mapping.targetName() : name;
63 String docs = mapping != null ? mapping.getJavadoc() : null; 63 String docs = mapping.javadoc();
64 return TranslateResult.of( 64 return TranslateResult.of(
65 mapping == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED, 65 mapping.targetName() == null ? RenamableTokenType.OBFUSCATED : RenamableTokenType.DEOBFUSCATED,
66 new MethodEntry(parent, translatedName, translator.translate(descriptor), docs) 66 new MethodEntry(parent, translatedName, translator.translate(descriptor), docs)
67 ); 67 );
68 } 68 }
diff --git a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
index 56348915..267bc11a 100644
--- a/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
+++ b/enigma/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
@@ -11,6 +11,7 @@
11 11
12package cuchaz.enigma.translation.representation.entry; 12package cuchaz.enigma.translation.representation.entry;
13 13
14import javax.annotation.Nonnull;
14import javax.annotation.Nullable; 15import javax.annotation.Nullable;
15 16
16import com.google.common.base.Preconditions; 17import com.google.common.base.Preconditions;
@@ -41,7 +42,7 @@ public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
41 @Override 42 @Override
42 public abstract ParentedEntry<P> withName(String name); 43 public abstract ParentedEntry<P> withName(String name);
43 44
44 protected abstract TranslateResult<? extends ParentedEntry<P>> extendedTranslate(Translator translator, @Nullable EntryMapping mapping); 45 protected abstract TranslateResult<? extends ParentedEntry<P>> extendedTranslate(Translator translator, @Nonnull EntryMapping mapping);
45 46
46 @Override 47 @Override
47 public String getName() { 48 public String getName() {
@@ -93,6 +94,6 @@ public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
93 return mapping; 94 return mapping;
94 } 95 }
95 } 96 }
96 return null; 97 return EntryMapping.DEFAULT;
97 } 98 }
98} 99}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java b/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java
new file mode 100644
index 00000000..864154cc
--- /dev/null
+++ b/enigma/src/main/java/cuchaz/enigma/utils/TristateChange.java
@@ -0,0 +1,78 @@
1package cuchaz.enigma.utils;
2
3import java.util.Objects;
4
5public final class TristateChange<T> {
6
7 private static final TristateChange<?> UNCHANGED = new TristateChange<>(Type.UNCHANGED, null);
8 private static final TristateChange<?> RESET = new TristateChange<>(Type.RESET, null);
9
10 private final Type type;
11 private final T val;
12
13 @SuppressWarnings("unchecked")
14 public static <T> TristateChange<T> unchanged() {
15 return (TristateChange<T>) TristateChange.UNCHANGED;
16 }
17
18 @SuppressWarnings("unchecked")
19 public static <T> TristateChange<T> reset() {
20 return (TristateChange<T>) TristateChange.RESET;
21 }
22
23 public static <T> TristateChange<T> set(T value) {
24 return new TristateChange<>(Type.SET, value);
25 }
26
27 private TristateChange(Type type, T val) {
28 this.type = type;
29 this.val = val;
30 }
31
32 public Type getType() {
33 return this.type;
34 }
35
36 public boolean isUnchanged() {
37 return this.type == Type.UNCHANGED;
38 }
39
40 public boolean isReset() {
41 return this.type == Type.RESET;
42 }
43
44 public boolean isSet() {
45 return this.type == Type.SET;
46 }
47
48 public T getNewValue() {
49 if (this.type != Type.SET) throw new IllegalStateException(String.format("No concrete value in %s", this));
50 return this.val;
51 }
52
53 @Override
54 public boolean equals(Object o) {
55 if (this == o) return true;
56 if (o == null || getClass() != o.getClass()) return false;
57 TristateChange<?> that = (TristateChange<?>) o;
58 return type == that.type &&
59 Objects.equals(val, that.val);
60 }
61
62 @Override
63 public int hashCode() {
64 return Objects.hash(type, val);
65 }
66
67 @Override
68 public String toString() {
69 return String.format("TristateChange { type: %s, val: %s }", type, val);
70 }
71
72 public enum Type {
73 UNCHANGED,
74 RESET,
75 SET,
76 }
77
78}
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java
index 81b6c43f..5067d7e3 100644
--- a/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java
+++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/PrintValidatable.java
@@ -1,5 +1,6 @@
1package cuchaz.enigma.utils.validation; 1package cuchaz.enigma.utils.validation;
2 2
3import java.io.PrintStream;
3import java.util.Arrays; 4import java.util.Arrays;
4 5
5public class PrintValidatable implements Validatable { 6public class PrintValidatable implements Validatable {
@@ -8,6 +9,10 @@ public class PrintValidatable implements Validatable {
8 9
9 @Override 10 @Override
10 public void addMessage(ParameterizedMessage message) { 11 public void addMessage(ParameterizedMessage message) {
12 formatMessage(System.out, message);
13 }
14
15 public static void formatMessage(PrintStream w, ParameterizedMessage message) {
11 String text = message.getText(); 16 String text = message.getText();
12 String longText = message.getLongText(); 17 String longText = message.getLongText();
13 String type = switch (message.message.type) { 18 String type = switch (message.message.type) {
@@ -15,9 +20,10 @@ public class PrintValidatable implements Validatable {
15 case WARNING -> "warning"; 20 case WARNING -> "warning";
16 case ERROR -> "error"; 21 case ERROR -> "error";
17 }; 22 };
18 System.out.printf("%s: %s\n", type, text); 23 w.printf("%s: %s\n", type, text);
24
19 if (!longText.isEmpty()) { 25 if (!longText.isEmpty()) {
20 Arrays.stream(longText.split("\n")).forEach(s -> System.out.printf(" %s\n", s)); 26 Arrays.stream(longText.split("\n")).forEach(s -> w.printf(" %s\n", s));
21 } 27 }
22 } 28 }
23 29
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java
index ee2b5956..0ecb9fb3 100644
--- a/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java
+++ b/enigma/src/main/java/cuchaz/enigma/utils/validation/ValidationContext.java
@@ -62,6 +62,20 @@ public class ValidationContext {
62 return messages.stream().noneMatch(m -> m.message.type == Type.ERROR); 62 return messages.stream().noneMatch(m -> m.message.type == Type.ERROR);
63 } 63 }
64 64
65 /**
66 * If this validation context has at least one error, throw an exception.
67 *
68 * @throws IllegalStateException if errors are present
69 */
70 public void throwOnError() {
71 if (!this.canProceed()) {
72 for (ParameterizedMessage message : this.messages) {
73 PrintValidatable.formatMessage(System.err, message);
74 }
75 throw new IllegalStateException("Errors encountered; cannot continue! Check error log for details.");
76 }
77 }
78
65 public List<ParameterizedMessage> getMessages() { 79 public List<ParameterizedMessage> getMessages() {
66 return Collections.unmodifiableList(messages); 80 return Collections.unmodifiableList(messages);
67 } 81 }
diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java
index 4f6654a4..510dd3cd 100644
--- a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java
+++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestReadWriteCycle.java
@@ -85,18 +85,18 @@ public class TestReadWriteCycle {
85 Assert.assertTrue("Loaded mappings don't contain testMethod1", loadedMappings.contains(testMethod1.a)); 85 Assert.assertTrue("Loaded mappings don't contain testMethod1", loadedMappings.contains(testMethod1.a));
86 Assert.assertTrue("Loaded mappings don't contain testMethod2", loadedMappings.contains(testMethod2.a)); 86 Assert.assertTrue("Loaded mappings don't contain testMethod2", loadedMappings.contains(testMethod2.a));
87 87
88 Assert.assertEquals("Incorrect mapping: testClazz", testClazz.b.getTargetName(), loadedMappings.get(testClazz.a).getTargetName()); 88 Assert.assertEquals("Incorrect mapping: testClazz", testClazz.b.targetName(), loadedMappings.get(testClazz.a).targetName());
89 Assert.assertEquals("Incorrect mapping: testField1", testField1.b.getTargetName(), loadedMappings.get(testField1.a).getTargetName()); 89 Assert.assertEquals("Incorrect mapping: testField1", testField1.b.targetName(), loadedMappings.get(testField1.a).targetName());
90 Assert.assertEquals("Incorrect mapping: testField2", testField2.b.getTargetName(), loadedMappings.get(testField2.a).getTargetName()); 90 Assert.assertEquals("Incorrect mapping: testField2", testField2.b.targetName(), loadedMappings.get(testField2.a).targetName());
91 Assert.assertEquals("Incorrect mapping: testMethod1", testMethod1.b.getTargetName(), loadedMappings.get(testMethod1.a).getTargetName()); 91 Assert.assertEquals("Incorrect mapping: testMethod1", testMethod1.b.targetName(), loadedMappings.get(testMethod1.a).targetName());
92 Assert.assertEquals("Incorrect mapping: testMethod2", testMethod2.b.getTargetName(), loadedMappings.get(testMethod2.a).getTargetName()); 92 Assert.assertEquals("Incorrect mapping: testMethod2", testMethod2.b.targetName(), loadedMappings.get(testMethod2.a).targetName());
93 93
94 if (supportsJavadoc) { 94 if (supportsJavadoc) {
95 Assert.assertEquals("Incorrect javadoc: testClazz", testClazz.b.getJavadoc(), loadedMappings.get(testClazz.a).getJavadoc()); 95 Assert.assertEquals("Incorrect javadoc: testClazz", testClazz.b.javadoc(), loadedMappings.get(testClazz.a).javadoc());
96 Assert.assertEquals("Incorrect javadoc: testField1", testField1.b.getJavadoc(), loadedMappings.get(testField1.a).getJavadoc()); 96 Assert.assertEquals("Incorrect javadoc: testField1", testField1.b.javadoc(), loadedMappings.get(testField1.a).javadoc());
97 Assert.assertEquals("Incorrect javadoc: testField2", testField2.b.getJavadoc(), loadedMappings.get(testField2.a).getJavadoc()); 97 Assert.assertEquals("Incorrect javadoc: testField2", testField2.b.javadoc(), loadedMappings.get(testField2.a).javadoc());
98 Assert.assertEquals("Incorrect javadoc: testMethod1", testMethod1.b.getJavadoc(), loadedMappings.get(testMethod1.a).getJavadoc()); 98 Assert.assertEquals("Incorrect javadoc: testMethod1", testMethod1.b.javadoc(), loadedMappings.get(testMethod1.a).javadoc());
99 Assert.assertEquals("Incorrect javadoc: testMethod2", testMethod2.b.getJavadoc(), loadedMappings.get(testMethod2.a).getJavadoc()); 99 Assert.assertEquals("Incorrect javadoc: testMethod2", testMethod2.b.javadoc(), loadedMappings.get(testMethod2.a).javadoc());
100 } 100 }
101 101
102 tempFile.delete(); 102 tempFile.delete();