summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorGravatar Runemoro2020-06-03 13:39:42 -0400
committerGravatar GitHub2020-06-03 18:39:42 +0100
commit0f47403d0220757fed189b76e2071e25b1025cb8 (patch)
tree879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /src/main/java
parentFix search dialog hanging for a short time sometimes (#250) (diff)
downloadenigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip
Split GUI code to separate module (#242)
* Split into modules * Post merge compile fixes Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/cuchaz/enigma/ClassProvider.java10
-rw-r--r--src/main/java/cuchaz/enigma/CommandMain.java105
-rw-r--r--src/main/java/cuchaz/enigma/Constants.java20
-rw-r--r--src/main/java/cuchaz/enigma/Enigma.java121
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaProfile.java131
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaProject.java276
-rw-r--r--src/main/java/cuchaz/enigma/EnigmaServices.java20
-rw-r--r--src/main/java/cuchaz/enigma/ExceptionIgnorer.java35
-rw-r--r--src/main/java/cuchaz/enigma/Main.java116
-rw-r--r--src/main/java/cuchaz/enigma/ProgressListener.java19
-rw-r--r--src/main/java/cuchaz/enigma/ProposingTranslator.java53
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Access.java43
-rw-r--r--src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java157
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassCache.java127
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java94
-rw-r--r--src/main/java/cuchaz/enigma/analysis/EntryReference.java140
-rw-r--r--src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java83
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java154
-rw-r--r--src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java74
-rw-r--r--src/main/java/cuchaz/enigma/analysis/InterpreterPair.java131
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java85
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java95
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java19
-rw-r--r--src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java113
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java74
-rw-r--r--src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java20
-rw-r--r--src/main/java/cuchaz/enigma/analysis/Token.java72
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java156
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java102
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java40
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java180
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java127
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndex.java171
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java28
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java147
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java148
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPlugin.java5
-rw-r--r--src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java9
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaService.java4
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java11
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java5
-rw-r--r--src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java29
-rw-r--r--src/main/java/cuchaz/enigma/api/service/JarIndexerService.java10
-rw-r--r--src/main/java/cuchaz/enigma/api/service/NameProposalService.java12
-rw-r--r--src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java9
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java46
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java126
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java39
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java51
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java102
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java33
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java145
-rw-r--r--src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java129
-rw-r--r--src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java77
-rw-r--r--src/main/java/cuchaz/enigma/command/Command.java154
-rw-r--r--src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java41
-rw-r--r--src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java39
-rw-r--r--src/main/java/cuchaz/enigma/command/DecompileCommand.java54
-rw-r--r--src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java37
-rw-r--r--src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java40
-rw-r--r--src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java69
-rw-r--r--src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java148
-rw-r--r--src/main/java/cuchaz/enigma/config/Config.java261
-rw-r--r--src/main/java/cuchaz/enigma/config/Themes.java48
-rw-r--r--src/main/java/cuchaz/enigma/gui/BrowserCaret.java28
-rw-r--r--src/main/java/cuchaz/enigma/gui/ClassSelector.java532
-rw-r--r--src/main/java/cuchaz/enigma/gui/CodeReader.java73
-rw-r--r--src/main/java/cuchaz/enigma/gui/ConnectionState.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java159
-rw-r--r--src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java90
-rw-r--r--src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java44
-rw-r--r--src/main/java/cuchaz/enigma/gui/Gui.java1058
-rw-r--r--src/main/java/cuchaz/enigma/gui/GuiController.java729
-rw-r--r--src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java24
-rw-r--r--src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java42
-rw-r--r--src/main/java/cuchaz/enigma/gui/QuickFindAction.java45
-rw-r--r--src/main/java/cuchaz/enigma/gui/RefreshMode.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/SourceRemapper.java64
-rw-r--r--src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java35
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java69
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java50
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java82
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java105
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java65
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java159
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java109
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java261
-rw-r--r--src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java82
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java40
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/MenuBar.java386
-rw-r--r--src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java125
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java10
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java11
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java69
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java31
-rw-r--r--src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java7
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java72
-rw-r--r--src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java58
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java26
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java171
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java32
-rw-r--r--src/main/java/cuchaz/enigma/gui/panels/PanelObf.java37
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java197
-rw-r--r--src/main/java/cuchaz/enigma/gui/stats/StatsMember.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java77
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/History.java49
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java8
-rw-r--r--src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java110
-rw-r--r--src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java164
-rw-r--r--src/main/java/cuchaz/enigma/network/EnigmaClient.java85
-rw-r--r--src/main/java/cuchaz/enigma/network/EnigmaServer.java292
-rw-r--r--src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java16
-rw-r--r--src/main/java/cuchaz/enigma/network/ServerPacketHandler.java22
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java59
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java44
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java33
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java33
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java75
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java48
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java40
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java39
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java36
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/Packet.java15
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/PacketHelper.java135
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java64
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java55
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java40
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java64
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java48
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java88
-rw-r--r--src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java44
-rw-r--r--src/main/java/cuchaz/enigma/source/Decompiler.java5
-rw-r--r--src/main/java/cuchaz/enigma/source/DecompilerService.java11
-rw-r--r--src/main/java/cuchaz/enigma/source/Decompilers.java9
-rw-r--r--src/main/java/cuchaz/enigma/source/Source.java11
-rw-r--r--src/main/java/cuchaz/enigma/source/SourceIndex.java174
-rw-r--r--src/main/java/cuchaz/enigma/source/SourceSettings.java11
-rw-r--r--src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java108
-rw-r--r--src/main/java/cuchaz/enigma/source/cfr/CfrSource.java38
-rw-r--r--src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java433
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/EntryParser.java49
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java81
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java49
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java95
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java218
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java40
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java46
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java134
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java33
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java37
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java29
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java107
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java414
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java39
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java197
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java33
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java38
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java140
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java38
-rw-r--r--src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java20
-rw-r--r--src/main/java/cuchaz/enigma/throwables/IllegalNameException.java39
-rw-r--r--src/main/java/cuchaz/enigma/throwables/MappingConflict.java7
-rw-r--r--src/main/java/cuchaz/enigma/throwables/MappingParseException.java39
-rw-r--r--src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java44
-rw-r--r--src/main/java/cuchaz/enigma/translation/MappingTranslator.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/SignatureUpdater.java92
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translatable.java9
-rw-r--r--src/main/java/cuchaz/enigma/translation/TranslationDirection.java36
-rw-r--r--src/main/java/cuchaz/enigma/translation/Translator.java61
-rw-r--r--src/main/java/cuchaz/enigma/translation/VoidTranslator.java10
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java25
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java24
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java75
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java105
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java41
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java227
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java54
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java10
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java32
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java16
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java76
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java99
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java53
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java6
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java27
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java9
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java319
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java316
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java59
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java51
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java14
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java17
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java134
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java30
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java118
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java115
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java148
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java295
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java169
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java110
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java26
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java40
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java188
-rw-r--r--src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java75
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java116
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/Lambda.java105
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java132
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/Signature.java98
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java268
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java93
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java214
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java7
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java107
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java71
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java96
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java51
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java93
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java71
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java105
-rw-r--r--src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java82
-rw-r--r--src/main/java/cuchaz/enigma/utils/I18n.java102
-rw-r--r--src/main/java/cuchaz/enigma/utils/LFPrintWriter.java16
-rw-r--r--src/main/java/cuchaz/enigma/utils/Message.java392
-rw-r--r--src/main/java/cuchaz/enigma/utils/Pair.java26
-rw-r--r--src/main/java/cuchaz/enigma/utils/ReadableToken.java30
-rw-r--r--src/main/java/cuchaz/enigma/utils/Utils.java179
-rw-r--r--src/main/java/cuchaz/enigma/utils/search/SearchEntry.java17
-rw-r--r--src/main/java/cuchaz/enigma/utils/search/SearchUtil.java268
231 files changed, 0 insertions, 21193 deletions
diff --git a/src/main/java/cuchaz/enigma/ClassProvider.java b/src/main/java/cuchaz/enigma/ClassProvider.java
deleted file mode 100644
index 2b91379..0000000
--- a/src/main/java/cuchaz/enigma/ClassProvider.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma;
2
3import org.objectweb.asm.tree.ClassNode;
4
5import javax.annotation.Nullable;
6
7public interface ClassProvider {
8 @Nullable
9 ClassNode getClassNode(String name);
10}
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java
deleted file mode 100644
index 93b013d..0000000
--- a/src/main/java/cuchaz/enigma/CommandMain.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.command.*;
15
16import java.util.LinkedHashMap;
17import java.util.Locale;
18import java.util.Map;
19
20public class CommandMain {
21
22 private static final Map<String, Command> COMMANDS = new LinkedHashMap<>();
23
24 public static void main(String... args) throws Exception {
25 try {
26 // process the command
27 if (args.length < 1)
28 throw new IllegalArgumentException("Requires a command");
29 String command = args[0].toLowerCase(Locale.ROOT);
30
31 Command cmd = COMMANDS.get(command);
32 if (cmd == null)
33 throw new IllegalArgumentException("Command not recognized: " + command);
34
35 if (!cmd.isValidArgument(args.length - 1)) {
36 throw new CommandHelpException(cmd);
37 }
38
39 String[] cmdArgs = new String[args.length - 1];
40 System.arraycopy(args, 1, cmdArgs, 0, args.length - 1);
41
42 try {
43 cmd.run(cmdArgs);
44 } catch (Exception ex) {
45 throw new CommandHelpException(cmd, ex);
46 }
47 } catch (CommandHelpException ex) {
48 System.err.println(ex.getMessage());
49 System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION));
50 System.out.println("Command " + ex.command.name + " has encountered an error! Usage:");
51 printHelp(ex.command);
52 System.exit(1);
53 } catch (IllegalArgumentException ex) {
54 System.err.println(ex.getMessage());
55 printHelp();
56 System.exit(1);
57 }
58 }
59
60 private static void printHelp() {
61 System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION));
62 System.out.println("Usage:");
63 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>");
64 System.out.println("\twhere <command> is one of:");
65
66 for (Command command : COMMANDS.values()) {
67 printHelp(command);
68 }
69 }
70
71 private static void printHelp(Command command) {
72 System.out.println("\t\t" + command.name + " " + command.getUsage());
73 }
74
75 private static void register(Command command) {
76 Command old = COMMANDS.put(command.name, command);
77 if (old != null) {
78 System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command);
79 }
80 }
81
82 static {
83 register(new DeobfuscateCommand());
84 register(new DecompileCommand());
85 register(new ConvertMappingsCommand());
86 register(new ComposeMappingsCommand());
87 register(new InvertMappingsCommand());
88 register(new CheckMappingsCommand());
89 register(new MapSpecializedMethodsCommand());
90 }
91
92 private static final class CommandHelpException extends IllegalArgumentException {
93
94 final Command command;
95
96 CommandHelpException(Command command) {
97 this.command = command;
98 }
99
100 CommandHelpException(Command command, Throwable cause) {
101 super(cause);
102 this.command = command;
103 }
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/Constants.java b/src/main/java/cuchaz/enigma/Constants.java
deleted file mode 100644
index 577315f..0000000
--- a/src/main/java/cuchaz/enigma/Constants.java
+++ /dev/null
@@ -1,20 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14public class Constants {
15 public static final String NAME = "Enigma";
16 public static final String VERSION = "@VERSION@/Fabric";
17 public static final String URL = "https://fabricmc.net";
18 public static final int MiB = 1024 * 1024; // 1 mebibyte
19 public static final int KiB = 1024; // 1 kebibyte
20}
diff --git a/src/main/java/cuchaz/enigma/Enigma.java b/src/main/java/cuchaz/enigma/Enigma.java
deleted file mode 100644
index f5f0649..0000000
--- a/src/main/java/cuchaz/enigma/Enigma.java
+++ /dev/null
@@ -1,121 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.ImmutableListMultimap;
16import cuchaz.enigma.analysis.ClassCache;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.api.EnigmaPlugin;
19import cuchaz.enigma.api.EnigmaPluginContext;
20import cuchaz.enigma.api.service.EnigmaService;
21import cuchaz.enigma.api.service.EnigmaServiceFactory;
22import cuchaz.enigma.api.service.EnigmaServiceType;
23import cuchaz.enigma.api.service.JarIndexerService;
24import cuchaz.enigma.utils.Utils;
25
26import java.io.IOException;
27import java.nio.file.Path;
28import java.util.List;
29import java.util.ServiceLoader;
30
31public class Enigma {
32 private final EnigmaProfile profile;
33 private final EnigmaServices services;
34
35 private Enigma(EnigmaProfile profile, EnigmaServices services) {
36 this.profile = profile;
37 this.services = services;
38 }
39
40 public static Enigma create() {
41 return new Builder().build();
42 }
43
44 public static Builder builder() {
45 return new Builder();
46 }
47
48 public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException {
49 ClassCache classCache = ClassCache.of(path);
50 JarIndex jarIndex = classCache.index(progress);
51
52 services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex));
53
54 return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path));
55 }
56
57 public EnigmaProfile getProfile() {
58 return profile;
59 }
60
61 public EnigmaServices getServices() {
62 return services;
63 }
64
65 public static class Builder {
66 private EnigmaProfile profile = EnigmaProfile.EMPTY;
67 private Iterable<EnigmaPlugin> plugins = ServiceLoader.load(EnigmaPlugin.class);
68
69 private Builder() {
70 }
71
72 public Builder setProfile(EnigmaProfile profile) {
73 Preconditions.checkNotNull(profile, "profile cannot be null");
74 this.profile = profile;
75 return this;
76 }
77
78 public Builder setPlugins(Iterable<EnigmaPlugin> plugins) {
79 Preconditions.checkNotNull(plugins, "plugins cannot be null");
80 this.plugins = plugins;
81 return this;
82 }
83
84 public Enigma build() {
85 PluginContext pluginContext = new PluginContext(profile);
86 for (EnigmaPlugin plugin : plugins) {
87 plugin.init(pluginContext);
88 }
89
90 EnigmaServices services = pluginContext.buildServices();
91 return new Enigma(profile, services);
92 }
93 }
94
95 private static class PluginContext implements EnigmaPluginContext {
96 private final EnigmaProfile profile;
97
98 private final ImmutableListMultimap.Builder<EnigmaServiceType<?>, EnigmaService> services = ImmutableListMultimap.builder();
99
100 PluginContext(EnigmaProfile profile) {
101 this.profile = profile;
102 }
103
104 @Override
105 public <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory) {
106 List<EnigmaProfile.Service> serviceProfiles = profile.getServiceProfiles(serviceType);
107
108 for (EnigmaProfile.Service serviceProfile : serviceProfiles) {
109 if (serviceProfile.matches(id)) {
110 T service = factory.create(serviceProfile::getArgument);
111 services.put(serviceType, service);
112 break;
113 }
114 }
115 }
116
117 EnigmaServices buildServices() {
118 return new EnigmaServices(services.build());
119 }
120 }
121}
diff --git a/src/main/java/cuchaz/enigma/EnigmaProfile.java b/src/main/java/cuchaz/enigma/EnigmaProfile.java
deleted file mode 100644
index 09b90f5..0000000
--- a/src/main/java/cuchaz/enigma/EnigmaProfile.java
+++ /dev/null
@@ -1,131 +0,0 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableMap;
4import com.google.gson.Gson;
5import com.google.gson.GsonBuilder;
6import com.google.gson.JsonDeserializationContext;
7import com.google.gson.JsonDeserializer;
8import com.google.gson.JsonElement;
9import com.google.gson.JsonObject;
10import com.google.gson.JsonParseException;
11import com.google.gson.annotations.SerializedName;
12import com.google.gson.reflect.TypeToken;
13import cuchaz.enigma.api.service.EnigmaServiceType;
14import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
15import cuchaz.enigma.translation.mapping.MappingSaveParameters;
16
17import javax.annotation.Nullable;
18import java.io.BufferedReader;
19import java.io.IOException;
20import java.io.InputStreamReader;
21import java.io.Reader;
22import java.lang.reflect.Type;
23import java.nio.charset.StandardCharsets;
24import java.nio.file.Files;
25import java.nio.file.Path;
26import java.util.Collections;
27import java.util.List;
28import java.util.Map;
29import java.util.Optional;
30
31public final class EnigmaProfile {
32 public static final EnigmaProfile EMPTY = new EnigmaProfile(new ServiceContainer(ImmutableMap.of()));
33
34 private static final MappingSaveParameters DEFAULT_MAPPING_SAVE_PARAMETERS = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
35 private static final Gson GSON = new GsonBuilder()
36 .registerTypeAdapter(ServiceContainer.class, (JsonDeserializer<ServiceContainer>) EnigmaProfile::loadServiceContainer)
37 .create();
38 private static final Type SERVICE_LIST_TYPE = new TypeToken<List<Service>>() {
39 }.getType();
40
41 @SerializedName("services")
42 private final ServiceContainer serviceProfiles;
43
44 @SerializedName("mapping_save_parameters")
45 private final MappingSaveParameters mappingSaveParameters = null;
46
47 private EnigmaProfile(ServiceContainer serviceProfiles) {
48 this.serviceProfiles = serviceProfiles;
49 }
50
51 public static EnigmaProfile read(@Nullable Path file) throws IOException {
52 if (file != null) {
53 try (BufferedReader reader = Files.newBufferedReader(file)) {
54 return EnigmaProfile.parse(reader);
55 }
56 } else {
57 try (BufferedReader reader = new BufferedReader(new InputStreamReader(Main.class.getResourceAsStream("/profile.json"), StandardCharsets.UTF_8))) {
58 return EnigmaProfile.parse(reader);
59 } catch (IOException ex) {
60 System.err.println("Failed to load default profile, will use empty profile: " + ex.getMessage());
61 return EnigmaProfile.EMPTY;
62 }
63 }
64 }
65
66 public static EnigmaProfile parse(Reader reader) {
67 return GSON.fromJson(reader, EnigmaProfile.class);
68 }
69
70 private static ServiceContainer loadServiceContainer(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
71 if (!json.isJsonObject()) {
72 throw new JsonParseException("services must be an Object!");
73 }
74
75 JsonObject object = json.getAsJsonObject();
76
77 ImmutableMap.Builder<String, List<Service>> builder = ImmutableMap.builder();
78
79 for (Map.Entry<String, JsonElement> entry : object.entrySet()) {
80 JsonElement value = entry.getValue();
81 if (value.isJsonObject()) {
82 builder.put(entry.getKey(), Collections.singletonList(GSON.fromJson(value, Service.class)));
83 } else if (value.isJsonArray()) {
84 builder.put(entry.getKey(), GSON.fromJson(value, SERVICE_LIST_TYPE));
85 } else {
86 throw new JsonParseException(String.format("Don't know how to convert %s to a list of service!", value));
87 }
88 }
89
90 return new ServiceContainer(builder.build());
91 }
92
93 public List<Service> getServiceProfiles(EnigmaServiceType<?> serviceType) {
94 return serviceProfiles.get(serviceType.key);
95 }
96
97 public MappingSaveParameters getMappingSaveParameters() {
98 //noinspection ConstantConditions
99 return mappingSaveParameters == null ? EnigmaProfile.DEFAULT_MAPPING_SAVE_PARAMETERS : mappingSaveParameters;
100 }
101
102 public static class Service {
103 private final String id;
104 private final Map<String, String> args;
105
106 Service(String id, Map<String, String> args) {
107 this.id = id;
108 this.args = args;
109 }
110
111 public boolean matches(String id) {
112 return this.id.equals(id);
113 }
114
115 public Optional<String> getArgument(String key) {
116 return args != null ? Optional.ofNullable(args.get(key)) : Optional.empty();
117 }
118 }
119
120 static final class ServiceContainer {
121 private final Map<String, List<Service>> services;
122
123 ServiceContainer(Map<String, List<Service>> services) {
124 this.services = services;
125 }
126
127 List<Service> get(String key) {
128 return services.getOrDefault(key, Collections.emptyList());
129 }
130 }
131}
diff --git a/src/main/java/cuchaz/enigma/EnigmaProject.java b/src/main/java/cuchaz/enigma/EnigmaProject.java
deleted file mode 100644
index b345fb3..0000000
--- a/src/main/java/cuchaz/enigma/EnigmaProject.java
+++ /dev/null
@@ -1,276 +0,0 @@
1package cuchaz.enigma;
2
3import com.google.common.base.Functions;
4import com.google.common.base.Preconditions;
5import cuchaz.enigma.analysis.ClassCache;
6import cuchaz.enigma.analysis.EntryReference;
7import cuchaz.enigma.analysis.index.JarIndex;
8import cuchaz.enigma.api.service.NameProposalService;
9import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
10import cuchaz.enigma.bytecode.translators.TranslationClassVisitor;
11import cuchaz.enigma.network.EnigmaServer;
12import cuchaz.enigma.source.*;
13import cuchaz.enigma.translation.Translator;
14import cuchaz.enigma.translation.mapping.*;
15import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18import cuchaz.enigma.translation.representation.entry.Entry;
19import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21import cuchaz.enigma.utils.I18n;
22
23import cuchaz.enigma.utils.Utils;
24import org.objectweb.asm.ClassWriter;
25import org.objectweb.asm.Opcodes;
26import org.objectweb.asm.tree.ClassNode;
27
28import java.io.*;
29import java.nio.file.Files;
30import java.nio.file.Path;
31import java.util.Collection;
32import java.util.Map;
33import java.util.Objects;
34import java.util.concurrent.atomic.AtomicInteger;
35import java.util.jar.JarEntry;
36import java.util.jar.JarOutputStream;
37import java.util.stream.Collectors;
38
39public class EnigmaProject {
40 private final Enigma enigma;
41
42 private final ClassCache classCache;
43 private final JarIndex jarIndex;
44 private final byte[] jarChecksum;
45
46 private EntryRemapper mapper;
47
48 public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) {
49 Preconditions.checkArgument(jarChecksum.length == EnigmaServer.CHECKSUM_SIZE);
50 this.enigma = enigma;
51 this.classCache = classCache;
52 this.jarIndex = jarIndex;
53 this.jarChecksum = jarChecksum;
54
55 this.mapper = EntryRemapper.empty(jarIndex);
56 }
57
58 public void setMappings(EntryTree<EntryMapping> mappings) {
59 if (mappings != null) {
60 mapper = EntryRemapper.mapped(jarIndex, mappings);
61 } else {
62 mapper = EntryRemapper.empty(jarIndex);
63 }
64 }
65
66 public Enigma getEnigma() {
67 return enigma;
68 }
69
70 public ClassCache getClassCache() {
71 return classCache;
72 }
73
74 public JarIndex getJarIndex() {
75 return jarIndex;
76 }
77
78 public byte[] getJarChecksum() {
79 return jarChecksum;
80 }
81
82 public EntryRemapper getMapper() {
83 return mapper;
84 }
85
86 public void dropMappings(ProgressListener progress) {
87 DeltaTrackingTree<EntryMapping> mappings = mapper.getObfToDeobf();
88
89 Collection<Entry<?>> dropped = dropMappings(mappings, progress);
90 for (Entry<?> entry : dropped) {
91 mappings.trackChange(entry);
92 }
93 }
94
95 private Collection<Entry<?>> dropMappings(EntryTree<EntryMapping> mappings, ProgressListener progress) {
96 // drop mappings that don't match the jar
97 MappingsChecker checker = new MappingsChecker(jarIndex, mappings);
98 MappingsChecker.Dropped dropped = checker.dropBrokenMappings(progress);
99
100 Map<Entry<?>, String> droppedMappings = dropped.getDroppedMappings();
101 for (Map.Entry<Entry<?>, String> mapping : droppedMappings.entrySet()) {
102 System.out.println("WARNING: Couldn't find " + mapping.getKey() + " (" + mapping.getValue() + ") in jar. Mapping was dropped.");
103 }
104
105 return droppedMappings.keySet();
106 }
107
108 public boolean isRenamable(Entry<?> obfEntry) {
109 if (obfEntry instanceof MethodEntry) {
110 // HACKHACK: Object methods are not obfuscated identifiers
111 MethodEntry obfMethodEntry = (MethodEntry) obfEntry;
112 String name = obfMethodEntry.getName();
113 String sig = obfMethodEntry.getDesc().toString();
114 if (name.equals("clone") && sig.equals("()Ljava/lang/Object;")) {
115 return false;
116 } else if (name.equals("equals") && sig.equals("(Ljava/lang/Object;)Z")) {
117 return false;
118 } else if (name.equals("finalize") && sig.equals("()V")) {
119 return false;
120 } else if (name.equals("getClass") && sig.equals("()Ljava/lang/Class;")) {
121 return false;
122 } else if (name.equals("hashCode") && sig.equals("()I")) {
123 return false;
124 } else if (name.equals("notify") && sig.equals("()V")) {
125 return false;
126 } else if (name.equals("notifyAll") && sig.equals("()V")) {
127 return false;
128 } else if (name.equals("toString") && sig.equals("()Ljava/lang/String;")) {
129 return false;
130 } else if (name.equals("wait") && sig.equals("()V")) {
131 return false;
132 } else if (name.equals("wait") && sig.equals("(J)V")) {
133 return false;
134 } else if (name.equals("wait") && sig.equals("(JI)V")) {
135 return false;
136 }
137 } else if (obfEntry instanceof LocalVariableEntry && !((LocalVariableEntry) obfEntry).isArgument()) {
138 return false;
139 }
140
141 return this.jarIndex.getEntryIndex().hasEntry(obfEntry);
142 }
143
144 public boolean isRenamable(EntryReference<Entry<?>, Entry<?>> obfReference) {
145 return obfReference.isNamed() && isRenamable(obfReference.getNameableEntry());
146 }
147
148 public JarExport exportRemappedJar(ProgressListener progress) {
149 Collection<ClassEntry> classEntries = jarIndex.getEntryIndex().getClasses();
150
151 NameProposalService[] nameProposalServices = getEnigma().getServices().get(NameProposalService.TYPE).toArray(new NameProposalService[0]);
152 Translator deobfuscator = nameProposalServices.length == 0 ? mapper.getDeobfuscator() : new ProposingTranslator(mapper, nameProposalServices);
153
154 AtomicInteger count = new AtomicInteger();
155 progress.init(classEntries.size(), I18n.translate("progress.classes.deobfuscating"));
156
157 Map<String, ClassNode> compiled = classEntries.parallelStream()
158 .map(entry -> {
159 ClassEntry translatedEntry = deobfuscator.translate(entry);
160 progress.step(count.getAndIncrement(), translatedEntry.toString());
161
162 ClassNode node = classCache.getClassNode(entry.getFullName());
163 if (node != null) {
164 ClassNode translatedNode = new ClassNode();
165 node.accept(new TranslationClassVisitor(deobfuscator, Utils.ASM_VERSION, new SourceFixVisitor(Utils.ASM_VERSION, translatedNode, jarIndex)));
166 return translatedNode;
167 }
168
169 return null;
170 })
171 .filter(Objects::nonNull)
172 .collect(Collectors.toMap(n -> n.name, Functions.identity()));
173
174 return new JarExport(jarIndex, compiled);
175 }
176
177 public static final class JarExport {
178 private final JarIndex jarIndex;
179 private final Map<String, ClassNode> compiled;
180
181 JarExport(JarIndex jarIndex, Map<String, ClassNode> compiled) {
182 this.jarIndex = jarIndex;
183 this.compiled = compiled;
184 }
185
186 public void write(Path path, ProgressListener progress) throws IOException {
187 progress.init(this.compiled.size(), I18n.translate("progress.jar.writing"));
188
189 try (JarOutputStream out = new JarOutputStream(Files.newOutputStream(path))) {
190 AtomicInteger count = new AtomicInteger();
191
192 for (ClassNode node : this.compiled.values()) {
193 progress.step(count.getAndIncrement(), node.name);
194
195 String entryName = node.name.replace('.', '/') + ".class";
196
197 ClassWriter writer = new ClassWriter(0);
198 node.accept(writer);
199
200 out.putNextEntry(new JarEntry(entryName));
201 out.write(writer.toByteArray());
202 out.closeEntry();
203 }
204 }
205 }
206
207 public SourceExport decompile(ProgressListener progress, DecompilerService decompilerService) {
208 Collection<ClassNode> classes = this.compiled.values().stream()
209 .filter(classNode -> classNode.name.indexOf('$') == -1)
210 .collect(Collectors.toList());
211
212 progress.init(classes.size(), I18n.translate("progress.classes.decompiling"));
213
214 //create a common instance outside the loop as mappings shouldn't be changing while this is happening
215 Decompiler decompiler = decompilerService.create(compiled::get, new SourceSettings(false, false));
216
217 AtomicInteger count = new AtomicInteger();
218
219 Collection<ClassSource> decompiled = classes.parallelStream()
220 .map(translatedNode -> {
221 progress.step(count.getAndIncrement(), translatedNode.name);
222
223 String source = decompileClass(translatedNode, decompiler);
224 return new ClassSource(translatedNode.name, source);
225 })
226 .collect(Collectors.toList());
227
228 return new SourceExport(decompiled);
229 }
230
231 private String decompileClass(ClassNode translatedNode, Decompiler decompiler) {
232 return decompiler.getSource(translatedNode.name).asString();
233 }
234 }
235
236 public static final class SourceExport {
237 private final Collection<ClassSource> decompiled;
238
239 SourceExport(Collection<ClassSource> decompiled) {
240 this.decompiled = decompiled;
241 }
242
243 public void write(Path path, ProgressListener progress) throws IOException {
244 progress.init(decompiled.size(), I18n.translate("progress.sources.writing"));
245
246 int count = 0;
247 for (ClassSource source : decompiled) {
248 progress.step(count++, source.name);
249
250 Path sourcePath = source.resolvePath(path);
251 source.writeTo(sourcePath);
252 }
253 }
254 }
255
256 private static class ClassSource {
257 private final String name;
258 private final String source;
259
260 ClassSource(String name, String source) {
261 this.name = name;
262 this.source = source;
263 }
264
265 void writeTo(Path path) throws IOException {
266 Files.createDirectories(path.getParent());
267 try (BufferedWriter writer = Files.newBufferedWriter(path)) {
268 writer.write(source);
269 }
270 }
271
272 Path resolvePath(Path root) {
273 return root.resolve(name.replace('.', '/') + ".java");
274 }
275 }
276}
diff --git a/src/main/java/cuchaz/enigma/EnigmaServices.java b/src/main/java/cuchaz/enigma/EnigmaServices.java
deleted file mode 100644
index df3b7bb..0000000
--- a/src/main/java/cuchaz/enigma/EnigmaServices.java
+++ /dev/null
@@ -1,20 +0,0 @@
1package cuchaz.enigma;
2
3import com.google.common.collect.ImmutableListMultimap;
4import cuchaz.enigma.api.service.EnigmaService;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7import java.util.List;
8
9public final class EnigmaServices {
10 private final ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services;
11
12 EnigmaServices(ImmutableListMultimap<EnigmaServiceType<?>, EnigmaService> services) {
13 this.services = services;
14 }
15
16 @SuppressWarnings("unchecked")
17 public <T extends EnigmaService> List<T> get(EnigmaServiceType<T> type) {
18 return (List<T>) services.get(type);
19 }
20}
diff --git a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java b/src/main/java/cuchaz/enigma/ExceptionIgnorer.java
deleted file mode 100644
index 84331cc..0000000
--- a/src/main/java/cuchaz/enigma/ExceptionIgnorer.java
+++ /dev/null
@@ -1,35 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14public class ExceptionIgnorer {
15
16 public static boolean shouldIgnore(Throwable t) {
17
18 // is this that pesky concurrent access bug in the highlight painter system?
19 // (ancient ui code is ancient)
20 if (t instanceof ArrayIndexOutOfBoundsException) {
21 StackTraceElement[] stackTrace = t.getStackTrace();
22 if (stackTrace.length > 1) {
23
24 // does this stack frame match javax.swing.text.DefaultHighlighter.paint*() ?
25 StackTraceElement frame = stackTrace[1];
26 if (frame.getClassName().equals("javax.swing.text.DefaultHighlighter") && frame.getMethodName().startsWith("paint")) {
27 return true;
28 }
29 }
30 }
31
32 return false;
33 }
34
35}
diff --git a/src/main/java/cuchaz/enigma/Main.java b/src/main/java/cuchaz/enigma/Main.java
deleted file mode 100644
index 7c87669..0000000
--- a/src/main/java/cuchaz/enigma/Main.java
+++ /dev/null
@@ -1,116 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma;
13
14import cuchaz.enigma.gui.Gui;
15import cuchaz.enigma.gui.GuiController;
16import cuchaz.enigma.translation.mapping.serde.MappingFormat;
17
18import joptsimple.*;
19
20import java.io.IOException;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24
25import com.google.common.io.MoreFiles;
26
27public class Main {
28
29 public static void main(String[] args) throws IOException {
30 OptionParser parser = new OptionParser();
31
32 OptionSpec<Path> jar = parser.accepts("jar", "Jar file to open at startup")
33 .withRequiredArg()
34 .withValuesConvertedBy(PathConverter.INSTANCE);
35
36 OptionSpec<Path> mappings = parser.accepts("mappings", "Mappings file to open at startup")
37 .withRequiredArg()
38 .withValuesConvertedBy(PathConverter.INSTANCE);
39
40 OptionSpec<Path> profile = parser.accepts("profile", "Profile json to apply at startup")
41 .withRequiredArg()
42 .withValuesConvertedBy(PathConverter.INSTANCE);
43
44 parser.accepts("help", "Displays help information");
45
46 try {
47 OptionSet options = parser.parse(args);
48
49 if (options.has("help")) {
50 parser.printHelpOn(System.out);
51 return;
52 }
53
54 EnigmaProfile parsedProfile = EnigmaProfile.read(options.valueOf(profile));
55
56 Gui gui = new Gui(parsedProfile);
57 GuiController controller = gui.getController();
58
59 if (options.has(jar)) {
60 Path jarPath = options.valueOf(jar);
61 controller.openJar(jarPath)
62 .whenComplete((v, t) -> {
63 if (options.has(mappings)) {
64 Path mappingsPath = options.valueOf(mappings);
65 if (Files.isDirectory(mappingsPath)) {
66 controller.openMappings(MappingFormat.ENIGMA_DIRECTORY, mappingsPath);
67 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsPath))) {
68 controller.openMappings(MappingFormat.ENIGMA_ZIP, mappingsPath);
69 } else {
70 controller.openMappings(MappingFormat.ENIGMA_FILE, mappingsPath);
71 }
72 }
73 });
74 }
75 } catch (OptionException e) {
76 System.out.println("Invalid arguments: " + e.getMessage());
77 System.out.println();
78 parser.printHelpOn(System.out);
79 }
80 }
81
82 public static class PathConverter implements ValueConverter<Path> {
83 public static final ValueConverter<Path> INSTANCE = new PathConverter();
84
85 PathConverter() {
86 }
87
88 @Override
89 public Path convert(String path) {
90 // expand ~ to the home dir
91 if (path.startsWith("~")) {
92 // get the home dir
93 Path dirHome = Paths.get(System.getProperty("user.home"));
94
95 // is the path just ~/ or is it ~user/ ?
96 if (path.startsWith("~/")) {
97 return dirHome.resolve(path.substring(2));
98 } else {
99 return dirHome.getParent().resolve(path.substring(1));
100 }
101 }
102
103 return Paths.get(path);
104 }
105
106 @Override
107 public Class<? extends Path> valueType() {
108 return Path.class;
109 }
110
111 @Override
112 public String valuePattern() {
113 return "path";
114 }
115 }
116}
diff --git a/src/main/java/cuchaz/enigma/ProgressListener.java b/src/main/java/cuchaz/enigma/ProgressListener.java
deleted file mode 100644
index 6da3b81..0000000
--- a/src/main/java/cuchaz/enigma/ProgressListener.java
+++ /dev/null
@@ -1,19 +0,0 @@
1package cuchaz.enigma;
2
3public interface ProgressListener {
4 static ProgressListener none() {
5 return new ProgressListener() {
6 @Override
7 public void init(int totalWork, String title) {
8 }
9
10 @Override
11 public void step(int numDone, String message) {
12 }
13 };
14 }
15
16 void init(int totalWork, String title);
17
18 void step(int numDone, String message);
19}
diff --git a/src/main/java/cuchaz/enigma/ProposingTranslator.java b/src/main/java/cuchaz/enigma/ProposingTranslator.java
deleted file mode 100644
index 018fbfd..0000000
--- a/src/main/java/cuchaz/enigma/ProposingTranslator.java
+++ /dev/null
@@ -1,53 +0,0 @@
1package cuchaz.enigma;
2
3import cuchaz.enigma.api.service.NameProposalService;
4import cuchaz.enigma.translation.Translatable;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryRemapper;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import java.util.Arrays;
11import java.util.Optional;
12
13public class ProposingTranslator implements Translator {
14 private final EntryRemapper mapper;
15 private final NameProposalService[] nameProposalServices;
16
17 public ProposingTranslator(EntryRemapper mapper, NameProposalService[] nameProposalServices) {
18 this.mapper = mapper;
19 this.nameProposalServices = nameProposalServices;
20 }
21
22 @Override
23 @SuppressWarnings("unchecked")
24 public <T extends Translatable> T translate(T translatable) {
25 if (translatable == null) {
26 return null;
27 }
28
29 T deobfuscated = mapper.deobfuscate(translatable);
30
31 if (translatable instanceof Entry && ((Entry) deobfuscated).getName().equals(((Entry<?>) translatable).getName())) {
32 return mapper.getObfResolver()
33 .resolveEntry((Entry<?>) translatable, ResolutionStrategy.RESOLVE_ROOT)
34 .stream()
35 .map(this::proposeName)
36 .filter(Optional::isPresent)
37 .map(Optional::get)
38 .findFirst()
39 .map(newName -> (T) ((Entry) deobfuscated).withName(newName))
40 .orElse(deobfuscated);
41 }
42
43 return deobfuscated;
44 }
45
46 private Optional<String> proposeName(Entry<?> entry) {
47 return Arrays.stream(nameProposalServices)
48 .map(service -> service.proposeName(entry, mapper))
49 .filter(Optional::isPresent)
50 .map(Optional::get)
51 .findFirst();
52 }
53}
diff --git a/src/main/java/cuchaz/enigma/analysis/Access.java b/src/main/java/cuchaz/enigma/analysis/Access.java
deleted file mode 100644
index 82ca669..0000000
--- a/src/main/java/cuchaz/enigma/analysis/Access.java
+++ /dev/null
@@ -1,43 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.representation.AccessFlags;
15
16import java.lang.reflect.Modifier;
17
18public enum Access {
19
20 PUBLIC, PROTECTED, PACKAGE, PRIVATE;
21
22 public static Access get(AccessFlags flags) {
23 return get(flags.getFlags());
24 }
25
26 public static Access get(int modifiers) {
27 boolean isPublic = Modifier.isPublic(modifiers);
28 boolean isProtected = Modifier.isProtected(modifiers);
29 boolean isPrivate = Modifier.isPrivate(modifiers);
30
31 if (isPublic && !isProtected && !isPrivate) {
32 return PUBLIC;
33 } else if (!isPublic && isProtected && !isPrivate) {
34 return PROTECTED;
35 } else if (!isPublic && !isProtected && isPrivate) {
36 return PRIVATE;
37 } else if (!isPublic && !isProtected && !isPrivate) {
38 return PACKAGE;
39 }
40 // assume public by default
41 return PUBLIC;
42 }
43}
diff --git a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
deleted file mode 100644
index dc3f553..0000000
--- a/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java
+++ /dev/null
@@ -1,157 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.api.EnigmaPlugin;
4import cuchaz.enigma.api.EnigmaPluginContext;
5import cuchaz.enigma.api.service.JarIndexerService;
6import cuchaz.enigma.api.service.NameProposalService;
7import cuchaz.enigma.source.DecompilerService;
8import cuchaz.enigma.source.Decompilers;
9import cuchaz.enigma.source.procyon.ProcyonDecompiler;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.Entry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.utils.Pair;
15import cuchaz.enigma.utils.Utils;
16import org.objectweb.asm.ClassReader;
17import org.objectweb.asm.ClassVisitor;
18import org.objectweb.asm.FieldVisitor;
19import org.objectweb.asm.MethodVisitor;
20import org.objectweb.asm.Opcodes;
21import org.objectweb.asm.tree.AbstractInsnNode;
22import org.objectweb.asm.tree.FieldInsnNode;
23import org.objectweb.asm.tree.InsnList;
24import org.objectweb.asm.tree.LdcInsnNode;
25import org.objectweb.asm.tree.MethodInsnNode;
26import org.objectweb.asm.tree.MethodNode;
27import org.objectweb.asm.tree.analysis.Analyzer;
28import org.objectweb.asm.tree.analysis.Frame;
29import org.objectweb.asm.tree.analysis.SourceInterpreter;
30import org.objectweb.asm.tree.analysis.SourceValue;
31
32import java.util.ArrayList;
33import java.util.HashMap;
34import java.util.HashSet;
35import java.util.List;
36import java.util.Map;
37import java.util.Optional;
38import java.util.Set;
39
40public final class BuiltinPlugin implements EnigmaPlugin {
41
42 public BuiltinPlugin() {
43 }
44
45 @Override
46 public void init(EnigmaPluginContext ctx) {
47 registerEnumNamingService(ctx);
48 registerDecompilerServices(ctx);
49 }
50
51 private void registerEnumNamingService(EnigmaPluginContext ctx) {
52 final Map<Entry<?>, String> names = new HashMap<>();
53 final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names);
54
55 ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES));
56 ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry)));
57 }
58
59 private void registerDecompilerServices(EnigmaPluginContext ctx) {
60 ctx.registerService("enigma:procyon", DecompilerService.TYPE, ctx1 -> Decompilers.PROCYON);
61 ctx.registerService("enigma:cfr", DecompilerService.TYPE, ctx1 -> Decompilers.CFR);
62 }
63
64 private static final class EnumFieldNameFindingVisitor extends ClassVisitor {
65
66 private ClassEntry clazz;
67 private String className;
68 private final Map<Entry<?>, String> mappings;
69 private final Set<Pair<String, String>> enumFields = new HashSet<>();
70 private final List<MethodNode> classInits = new ArrayList<>();
71
72 EnumFieldNameFindingVisitor(Map<Entry<?>, String> mappings) {
73 super(Utils.ASM_VERSION);
74 this.mappings = mappings;
75 }
76
77 @Override
78 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
79 super.visit(version, access, name, signature, superName, interfaces);
80 this.className = name;
81 this.clazz = new ClassEntry(name);
82 this.enumFields.clear();
83 this.classInits.clear();
84 }
85
86 @Override
87 public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) {
88 if ((access & Opcodes.ACC_ENUM) != 0) {
89 if (!enumFields.add(new Pair<>(name, descriptor))) {
90 throw new IllegalArgumentException("Found two enum fields with the same name \"" + name + "\" and desc \"" + descriptor + "\"!");
91 }
92 }
93 return super.visitField(access, name, descriptor, signature, value);
94 }
95
96 @Override
97 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
98 if ("<clinit>".equals(name)) {
99 MethodNode node = new MethodNode(api, access, name, descriptor, signature, exceptions);
100 classInits.add(node);
101 return node;
102 }
103 return super.visitMethod(access, name, descriptor, signature, exceptions);
104 }
105
106 @Override
107 public void visitEnd() {
108 super.visitEnd();
109 try {
110 collectResults();
111 } catch (Exception ex) {
112 throw new RuntimeException(ex);
113 }
114 }
115
116 private void collectResults() throws Exception {
117 String owner = className;
118 Analyzer<SourceValue> analyzer = new Analyzer<>(new SourceInterpreter());
119
120 for (MethodNode mn : classInits) {
121 Frame<SourceValue>[] frames = analyzer.analyze(className, mn);
122
123 InsnList instrs = mn.instructions;
124 for (int i = 1; i < instrs.size(); i++) {
125 AbstractInsnNode instr1 = instrs.get(i - 1);
126 AbstractInsnNode instr2 = instrs.get(i);
127 String s = null;
128
129 if (instr2.getOpcode() == Opcodes.PUTSTATIC
130 && ((FieldInsnNode) instr2).owner.equals(owner)
131 && enumFields.contains(new Pair<>(((FieldInsnNode) instr2).name, ((FieldInsnNode) instr2).desc))
132 && instr1.getOpcode() == Opcodes.INVOKESPECIAL
133 && "<init>".equals(((MethodInsnNode) instr1).name)) {
134
135 for (int j = 0; j < frames[i - 1].getStackSize(); j++) {
136 SourceValue sv = frames[i - 1].getStack(j);
137 for (AbstractInsnNode ci : sv.insns) {
138 if (ci instanceof LdcInsnNode && ((LdcInsnNode) ci).cst instanceof String) {
139 //if (s == null || !s.equals(((LdcInsnNode) ci).cst)) {
140 if (s == null) {
141 s = (String) (((LdcInsnNode) ci).cst);
142 // stringsFound++;
143 }
144 }
145 }
146 }
147 }
148
149 if (s != null) {
150 mappings.put(new FieldEntry(clazz, ((FieldInsnNode) instr2).name, new TypeDescriptor(((FieldInsnNode) instr2).desc)), s);
151 }
152 // report otherwise?
153 }
154 }
155 }
156 }
157}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/src/main/java/cuchaz/enigma/analysis/ClassCache.java
deleted file mode 100644
index f694bf3..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ClassCache.java
+++ /dev/null
@@ -1,127 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.cache.Cache;
4import com.google.common.cache.CacheBuilder;
5import com.google.common.collect.ImmutableSet;
6import cuchaz.enigma.ClassProvider;
7import cuchaz.enigma.ProgressListener;
8import cuchaz.enigma.analysis.index.JarIndex;
9import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor;
10import cuchaz.enigma.utils.Utils;
11import org.objectweb.asm.ClassReader;
12import org.objectweb.asm.ClassVisitor;
13import org.objectweb.asm.Opcodes;
14import org.objectweb.asm.tree.ClassNode;
15
16import javax.annotation.Nullable;
17import java.io.IOException;
18import java.nio.file.FileSystem;
19import java.nio.file.FileSystems;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.concurrent.ExecutionException;
23import java.util.concurrent.TimeUnit;
24import java.util.function.Supplier;
25
26public final class ClassCache implements AutoCloseable, ClassProvider {
27 private final FileSystem fileSystem;
28 private final ImmutableSet<String> classNames;
29
30 private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder()
31 .maximumSize(128)
32 .expireAfterAccess(1, TimeUnit.MINUTES)
33 .build();
34
35 private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) {
36 this.fileSystem = fileSystem;
37 this.classNames = classNames;
38 }
39
40 public static ClassCache of(Path jarPath) throws IOException {
41 FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null);
42 ImmutableSet<String> classNames = collectClassNames(fileSystem);
43
44 return new ClassCache(fileSystem, classNames);
45 }
46
47 private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException {
48 ImmutableSet.Builder<String> classNames = ImmutableSet.builder();
49 for (Path root : fileSystem.getRootDirectories()) {
50 Files.walk(root).map(Path::toString)
51 .forEach(path -> {
52 if (path.endsWith(".class")) {
53 String name = path.substring(1, path.length() - ".class".length());
54 classNames.add(name);
55 }
56 });
57 }
58
59 return classNames.build();
60 }
61
62 @Nullable
63 @Override
64 public ClassNode getClassNode(String name) {
65 if (!classNames.contains(name)) {
66 return null;
67 }
68
69 try {
70 return nodeCache.get(name, () -> parseNode(name));
71 } catch (ExecutionException e) {
72 throw new RuntimeException(e);
73 }
74 }
75
76 private ClassNode parseNode(String name) throws IOException {
77 ClassReader reader = getReader(name);
78
79 ClassNode node = new ClassNode();
80
81 LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Utils.ASM_VERSION, node);
82 reader.accept(visitor, 0);
83
84 return node;
85 }
86
87 private ClassReader getReader(String name) throws IOException {
88 Path path = fileSystem.getPath(name + ".class");
89 byte[] bytes = Files.readAllBytes(path);
90 return new ClassReader(bytes);
91 }
92
93 public int getClassCount() {
94 return classNames.size();
95 }
96
97 public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) {
98 for (String className : classNames) {
99 ClassVisitor visitor = visitorSupplier.get();
100
101 ClassNode cached = nodeCache.getIfPresent(className);
102 if (cached != null) {
103 cached.accept(visitor);
104 continue;
105 }
106
107 try {
108 ClassReader reader = getReader(className);
109 reader.accept(visitor, readFlags);
110 } catch (IOException e) {
111 System.out.println("Failed to visit class " + className);
112 e.printStackTrace();
113 }
114 }
115 }
116
117 @Override
118 public void close() throws IOException {
119 this.fileSystem.close();
120 }
121
122 public JarIndex index(ProgressListener progress) {
123 JarIndex index = JarIndex.empty();
124 index.indexJar(this, progress);
125 return index;
126 }
127}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
deleted file mode 100644
index 0fc44ca..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ClassImplementationsTreeNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22import java.util.Collection;
23import java.util.List;
24
25public class ClassImplementationsTreeNode extends DefaultMutableTreeNode {
26 private final Translator translator;
27 private final ClassEntry entry;
28
29 public ClassImplementationsTreeNode(Translator translator, ClassEntry entry) {
30 this.translator = translator;
31 this.entry = entry;
32 }
33
34 public static ClassImplementationsTreeNode findNode(ClassImplementationsTreeNode node, MethodEntry entry) {
35 // is this the node?
36 if (node.entry.equals(entry.getParent())) {
37 return node;
38 }
39
40 // recurse
41 for (int i = 0; i < node.getChildCount(); i++) {
42 ClassImplementationsTreeNode foundNode = findNode((ClassImplementationsTreeNode) node.getChildAt(i), entry);
43 if (foundNode != null) {
44 return foundNode;
45 }
46 }
47 return null;
48 }
49
50 public ClassEntry getClassEntry() {
51 return this.entry;
52 }
53
54 @Override
55 public String toString() {
56 return translator.translate(entry).toString();
57 }
58
59 public void load(JarIndex index) {
60 // get all method implementations
61 List<ClassImplementationsTreeNode> nodes = Lists.newArrayList();
62 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
63
64 Collection<ClassEntry> inheritors = inheritanceIndex.getChildren(entry);
65 for (ClassEntry inheritor : inheritors) {
66 nodes.add(new ClassImplementationsTreeNode(translator, inheritor));
67 }
68
69 // add them to this node
70 nodes.forEach(this::add);
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
deleted file mode 100644
index 7904c5f..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ClassInheritanceTreeNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.representation.entry.ClassEntry;
18
19import javax.swing.tree.DefaultMutableTreeNode;
20import java.util.List;
21
22public class ClassInheritanceTreeNode extends DefaultMutableTreeNode {
23 private final Translator translator;
24 private final ClassEntry obfClassEntry;
25
26 public ClassInheritanceTreeNode(Translator translator, String obfClassName) {
27 this.translator = translator;
28 this.obfClassEntry = new ClassEntry(obfClassName);
29 }
30
31 public static ClassInheritanceTreeNode findNode(ClassInheritanceTreeNode node, ClassEntry entry) {
32 // is this the node?
33 if (node.getObfClassName().equals(entry.getFullName())) {
34 return node;
35 }
36
37 // recurse
38 for (int i = 0; i < node.getChildCount(); i++) {
39 ClassInheritanceTreeNode foundNode = findNode((ClassInheritanceTreeNode) node.getChildAt(i), entry);
40 if (foundNode != null) {
41 return foundNode;
42 }
43 }
44 return null;
45 }
46
47 public String getObfClassName() {
48 return this.obfClassEntry.getFullName();
49 }
50
51 @Override
52 public String toString() {
53 return translator.translate(obfClassEntry).getFullName();
54 }
55
56 public void load(InheritanceIndex ancestries, boolean recurse) {
57 // get all the child nodes
58 List<ClassInheritanceTreeNode> nodes = Lists.newArrayList();
59 for (ClassEntry inheritor : ancestries.getChildren(this.obfClassEntry)) {
60 nodes.add(new ClassInheritanceTreeNode(translator, inheritor.getFullName()));
61 }
62
63 // add them to this node
64 nodes.forEach(this::add);
65
66 if (recurse) {
67 for (ClassInheritanceTreeNode node : nodes) {
68 node.load(ancestries, true);
69 }
70 }
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java
deleted file mode 100644
index 90d8a6c..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ClassReferenceTreeNode.java
+++ /dev/null
@@ -1,94 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.TreeNode;
24import java.util.Set;
25
26public class ClassReferenceTreeNode extends DefaultMutableTreeNode
27 implements ReferenceTreeNode<ClassEntry, MethodDefEntry> {
28
29 private Translator deobfuscatingTranslator;
30 private ClassEntry entry;
31 private EntryReference<ClassEntry, MethodDefEntry> reference;
32
33 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, ClassEntry entry) {
34 this.deobfuscatingTranslator = deobfuscatingTranslator;
35 this.entry = entry;
36 this.reference = null;
37 }
38
39 public ClassReferenceTreeNode(Translator deobfuscatingTranslator, EntryReference<ClassEntry, MethodDefEntry> reference) {
40 this.deobfuscatingTranslator = deobfuscatingTranslator;
41 this.entry = reference.entry;
42 this.reference = reference;
43 }
44
45 @Override
46 public ClassEntry getEntry() {
47 return this.entry;
48 }
49
50 @Override
51 public EntryReference<ClassEntry, MethodDefEntry> getReference() {
52 return this.reference;
53 }
54
55 @Override
56 public String toString() {
57 if (this.reference != null) {
58 return String.format("%s", this.deobfuscatingTranslator.translate(this.reference.context));
59 }
60 return this.deobfuscatingTranslator.translate(this.entry).getFullName();
61 }
62
63 public void load(JarIndex index, boolean recurse) {
64 ReferenceIndex referenceIndex = index.getReferenceIndex();
65
66 // get all the child nodes
67 for (EntryReference<ClassEntry, MethodDefEntry> reference : referenceIndex.getReferencesToClass(this.entry)) {
68 add(new ClassReferenceTreeNode(this.deobfuscatingTranslator, reference));
69 }
70
71 if (recurse && this.children != null) {
72 for (Object child : this.children) {
73 if (child instanceof ClassReferenceTreeNode) {
74 ClassReferenceTreeNode node = (ClassReferenceTreeNode) child;
75
76 // don't recurse into ancestor
77 Set<Entry<?>> ancestors = Sets.newHashSet();
78 TreeNode n = node;
79 while (n.getParent() != null) {
80 n = n.getParent();
81 if (n instanceof ClassReferenceTreeNode) {
82 ancestors.add(((ClassReferenceTreeNode) n).getEntry());
83 }
84 }
85 if (ancestors.contains(node.getEntry())) {
86 continue;
87 }
88
89 node.load(index, true);
90 }
91 }
92 }
93 }
94}
diff --git a/src/main/java/cuchaz/enigma/analysis/EntryReference.java b/src/main/java/cuchaz/enigma/analysis/EntryReference.java
deleted file mode 100644
index 2e738c0..0000000
--- a/src/main/java/cuchaz/enigma/analysis/EntryReference.java
+++ /dev/null
@@ -1,140 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.Translatable;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.mapping.EntryResolver;
18import cuchaz.enigma.translation.mapping.EntryMap;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.Entry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
22import cuchaz.enigma.utils.Utils;
23
24import java.util.Arrays;
25import java.util.List;
26
27public class EntryReference<E extends Entry<?>, C extends Entry<?>> implements Translatable {
28
29 private static final List<String> CONSTRUCTOR_NON_NAMES = Arrays.asList("this", "super", "static");
30 public E entry;
31 public C context;
32 public ReferenceTargetType targetType;
33
34 private boolean sourceName;
35
36 public EntryReference(E entry, String sourceName) {
37 this(entry, sourceName, null);
38 }
39
40 public EntryReference(E entry, String sourceName, C context) {
41 this(entry, sourceName, context, ReferenceTargetType.none());
42 }
43
44 public EntryReference(E entry, String sourceName, C context, ReferenceTargetType targetType) {
45 if (entry == null) {
46 throw new IllegalArgumentException("Entry cannot be null!");
47 }
48
49 this.entry = entry;
50 this.context = context;
51 this.targetType = targetType;
52
53 this.sourceName = sourceName != null && !sourceName.isEmpty();
54 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor() && CONSTRUCTOR_NON_NAMES.contains(sourceName)) {
55 this.sourceName = false;
56 }
57 }
58
59 public EntryReference(E entry, C context, EntryReference<E, C> other) {
60 this.entry = entry;
61 this.context = context;
62 this.sourceName = other.sourceName;
63 this.targetType = other.targetType;
64 }
65
66 public ClassEntry getLocationClassEntry() {
67 if (context != null) {
68 return context.getContainingClass();
69 }
70 return entry.getContainingClass();
71 }
72
73 public boolean isNamed() {
74 return this.sourceName;
75 }
76
77 public Entry<?> getNameableEntry() {
78 if (entry instanceof MethodEntry && ((MethodEntry) entry).isConstructor()) {
79 // renaming a constructor really means renaming the class
80 return entry.getContainingClass();
81 }
82 return entry;
83 }
84
85 public String getNameableName() {
86 return getNameableEntry().getName();
87 }
88
89 @Override
90 public int hashCode() {
91 if (context != null) {
92 return Utils.combineHashesOrdered(entry.hashCode(), context.hashCode());
93 }
94 return entry.hashCode();
95 }
96
97 @Override
98 public boolean equals(Object other) {
99 return other instanceof EntryReference && equals((EntryReference<?, ?>) other);
100 }
101
102 public boolean equals(EntryReference<?, ?> other) {
103 // check entry first
104 boolean isEntrySame = entry.equals(other.entry);
105 if (!isEntrySame) {
106 return false;
107 }
108
109 // check caller
110 if (context == null && other.context == null) {
111 return true;
112 } else if (context != null && other.context != null) {
113 return context.equals(other.context);
114 }
115 return false;
116 }
117
118 @Override
119 public String toString() {
120 StringBuilder buf = new StringBuilder();
121 buf.append(entry);
122
123 if (context != null) {
124 buf.append(" called from ");
125 buf.append(context);
126 }
127
128 if (targetType != null && targetType.getKind() != ReferenceTargetType.Kind.NONE) {
129 buf.append(" on target of type ");
130 buf.append(targetType);
131 }
132
133 return buf.toString();
134 }
135
136 @Override
137 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
138 return new EntryReference<>(translator.translate(entry), translator.translate(context), this);
139 }
140}
diff --git a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
deleted file mode 100644
index 4beab7f..0000000
--- a/src/main/java/cuchaz/enigma/analysis/FieldReferenceTreeNode.java
+++ /dev/null
@@ -1,83 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.analysis.index.JarIndex;
15import cuchaz.enigma.analysis.index.ReferenceIndex;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.representation.entry.FieldEntry;
18import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22
23public class FieldReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<FieldEntry, MethodDefEntry> {
24
25 private final Translator translator;
26 private FieldEntry entry;
27 private EntryReference<FieldEntry, MethodDefEntry> reference;
28
29 public FieldReferenceTreeNode(Translator translator, FieldEntry entry) {
30 this.translator = translator;
31 this.entry = entry;
32 this.reference = null;
33 }
34
35 private FieldReferenceTreeNode(Translator translator, EntryReference<FieldEntry, MethodDefEntry> reference) {
36 this.translator = translator;
37 this.entry = reference.entry;
38 this.reference = reference;
39 }
40
41 @Override
42 public FieldEntry getEntry() {
43 return this.entry;
44 }
45
46 @Override
47 public EntryReference<FieldEntry, MethodDefEntry> getReference() {
48 return this.reference;
49 }
50
51 @Override
52 public String toString() {
53 if (this.reference != null) {
54 return String.format("%s", translator.translate(this.reference.context));
55 }
56 return translator.translate(entry).toString();
57 }
58
59 public void load(JarIndex index, boolean recurse) {
60 ReferenceIndex referenceIndex = index.getReferenceIndex();
61
62 // get all the child nodes
63 if (this.reference == null) {
64 for (EntryReference<FieldEntry, MethodDefEntry> reference : referenceIndex.getReferencesToField(this.entry)) {
65 add(new FieldReferenceTreeNode(translator, reference));
66 }
67 } else {
68 for (EntryReference<MethodEntry, MethodDefEntry> reference : referenceIndex.getReferencesToMethod(this.reference.context)) {
69 add(new MethodReferenceTreeNode(translator, reference));
70 }
71 }
72
73 if (recurse && children != null) {
74 for (Object node : children) {
75 if (node instanceof MethodReferenceTreeNode) {
76 ((MethodReferenceTreeNode) node).load(index, true, false);
77 } else if (node instanceof FieldReferenceTreeNode) {
78 ((FieldReferenceTreeNode) node).load(index, true);
79 }
80 }
81 }
82 }
83}
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java b/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java
deleted file mode 100644
index 80a7154..0000000
--- a/src/main/java/cuchaz/enigma/analysis/IndexSimpleVerifier.java
+++ /dev/null
@@ -1,154 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.analysis.index.EntryIndex;
4import cuchaz.enigma.analysis.index.InheritanceIndex;
5import cuchaz.enigma.translation.representation.AccessFlags;
6import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
7import cuchaz.enigma.translation.representation.entry.ClassEntry;
8import cuchaz.enigma.utils.Utils;
9import org.objectweb.asm.Type;
10import org.objectweb.asm.tree.analysis.BasicValue;
11import org.objectweb.asm.tree.analysis.SimpleVerifier;
12
13import java.util.Set;
14
15public class IndexSimpleVerifier extends SimpleVerifier {
16 private static final Type OBJECT_TYPE = Type.getType("Ljava/lang/Object;");
17 private final EntryIndex entryIndex;
18 private final InheritanceIndex inheritanceIndex;
19
20 public IndexSimpleVerifier(EntryIndex entryIndex, InheritanceIndex inheritanceIndex) {
21 super(Utils.ASM_VERSION, null, null, null, false);
22 this.entryIndex = entryIndex;
23 this.inheritanceIndex = inheritanceIndex;
24 }
25
26 @Override
27 protected boolean isSubTypeOf(BasicValue value, BasicValue expected) {
28 Type expectedType = expected.getType();
29 Type type = value.getType();
30 switch (expectedType.getSort()) {
31 case Type.INT:
32 case Type.FLOAT:
33 case Type.LONG:
34 case Type.DOUBLE:
35 return type.equals(expectedType);
36 case Type.ARRAY:
37 case Type.OBJECT:
38 if (type.equals(NULL_TYPE)) {
39 return true;
40 } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
41 if (isAssignableFrom(expectedType, type)) {
42 return true;
43 } else if (isInterface(expectedType)) {
44 return isAssignableFrom(OBJECT_TYPE, type);
45 } else {
46 return false;
47 }
48 } else {
49 return false;
50 }
51 default:
52 throw new AssertionError();
53 }
54 }
55
56 @Override
57 protected boolean isInterface(Type type) {
58 AccessFlags classAccess = entryIndex.getClassAccess(new ClassEntry(type.getInternalName()));
59 if (classAccess != null) {
60 return classAccess.isInterface();
61 }
62
63 Class<?> clazz = getClass(type);
64 if (clazz != null) {
65 return clazz.isInterface();
66 }
67
68 return false;
69 }
70
71 @Override
72 protected Type getSuperClass(Type type) {
73 ClassDefEntry definition = entryIndex.getDefinition(new ClassEntry(type.getInternalName()));
74 if (definition != null) {
75 return Type.getType('L' + definition.getSuperClass().getFullName() + ';');
76 }
77
78 Class<?> clazz = getClass(type);
79 if (clazz != null) {
80 return Type.getType(clazz.getSuperclass());
81 }
82
83 return OBJECT_TYPE;
84 }
85
86 @Override
87 protected boolean isAssignableFrom(Type type1, Type type2) {
88 if (type1.equals(type2)) {
89 return true;
90 }
91
92 if (type2.equals(NULL_TYPE)) {
93 return true;
94 }
95
96 if (type1.getSort() == Type.ARRAY) {
97 return type2.getSort() == Type.ARRAY && isAssignableFrom(Type.getType(type1.getDescriptor().substring(1)), Type.getType(type2.getDescriptor().substring(1)));
98 }
99
100 if (type2.getSort() == Type.ARRAY) {
101 return type1.equals(OBJECT_TYPE);
102 }
103
104 if (type1.getSort() == Type.OBJECT && type2.getSort() == Type.OBJECT) {
105 if (type1.equals(OBJECT_TYPE)) {
106 return true;
107 }
108
109 ClassEntry class1 = new ClassEntry(type1.getInternalName());
110 ClassEntry class2 = new ClassEntry(type2.getInternalName());
111
112 if (entryIndex.hasClass(class1) && entryIndex.hasClass(class2)) {
113 return inheritanceIndex.getAncestors(class2).contains(class1);
114 }
115
116 Class<?> class1Class = getClass(Type.getType('L' + class1.getFullName() + ';'));
117 Class<?> class2Class = getClass(Type.getType('L' + class2.getFullName() + ';'));
118
119 if (class1Class == null) {
120 return true; // missing classes to find out
121 }
122
123 if (class2Class != null) {
124 return class1Class.isAssignableFrom(class2Class);
125 }
126
127 if (entryIndex.hasClass(class2)) {
128 Set<ClassEntry> ancestors = inheritanceIndex.getAncestors(class2);
129
130 for (ClassEntry ancestorEntry : ancestors) {
131 Class<?> ancestor = getClass(Type.getType('L' + ancestorEntry.getFullName() + ';'));
132 if (ancestor == null || class1Class.isAssignableFrom(ancestor)) {
133 return true; // assignable, or missing classes to find out
134 }
135 }
136
137 return false;
138 }
139
140 return true; // missing classes to find out
141 }
142
143 return false;
144 }
145
146 @Override
147 protected final Class<?> getClass(Type type) {
148 try {
149 return Class.forName(type.getSort() == Type.ARRAY ? type.getDescriptor().replace('/', '.') : type.getClassName(), false, null);
150 } catch (ClassNotFoundException e) {
151 return null;
152 }
153 }
154}
diff --git a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java b/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java
deleted file mode 100644
index 0c2dfd7..0000000
--- a/src/main/java/cuchaz/enigma/analysis/IndexTreeBuilder.java
+++ /dev/null
@@ -1,74 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import java.util.Collection;
12import java.util.List;
13
14public class IndexTreeBuilder {
15 private final JarIndex index;
16
17 public IndexTreeBuilder(JarIndex index) {
18 this.index = index;
19 }
20
21 public ClassInheritanceTreeNode buildClassInheritance(Translator translator, ClassEntry obfClassEntry) {
22 // get the root node
23 List<String> ancestry = Lists.newArrayList();
24 ancestry.add(obfClassEntry.getFullName());
25 for (ClassEntry classEntry : index.getInheritanceIndex().getAncestors(obfClassEntry)) {
26 ancestry.add(classEntry.getFullName());
27 }
28
29 ClassInheritanceTreeNode rootNode = new ClassInheritanceTreeNode(translator, ancestry.get(ancestry.size() - 1));
30
31 // expand all children recursively
32 rootNode.load(index.getInheritanceIndex(), true);
33
34 return rootNode;
35 }
36
37 public ClassImplementationsTreeNode buildClassImplementations(Translator translator, ClassEntry obfClassEntry) {
38 if (index.getInheritanceIndex().isParent(obfClassEntry)) {
39 ClassImplementationsTreeNode node = new ClassImplementationsTreeNode(translator, obfClassEntry);
40 node.load(index);
41 return node;
42 }
43 return null;
44 }
45
46 public MethodInheritanceTreeNode buildMethodInheritance(Translator translator, MethodEntry obfMethodEntry) {
47 MethodEntry resolvedEntry = index.getEntryResolver().resolveFirstEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT);
48
49 // make a root node at the base
50 MethodInheritanceTreeNode rootNode = new MethodInheritanceTreeNode(
51 translator, resolvedEntry,
52 index.getEntryIndex().hasMethod(resolvedEntry)
53 );
54
55 // expand the full tree
56 rootNode.load(index);
57
58 return rootNode;
59 }
60
61 public List<MethodImplementationsTreeNode> buildMethodImplementations(Translator translator, MethodEntry obfMethodEntry) {
62 EntryResolver resolver = index.getEntryResolver();
63 Collection<MethodEntry> resolvedEntries = resolver.resolveEntry(obfMethodEntry, ResolutionStrategy.RESOLVE_ROOT);
64
65 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
66 for (MethodEntry resolvedEntry : resolvedEntries) {
67 MethodImplementationsTreeNode node = new MethodImplementationsTreeNode(translator, resolvedEntry);
68 node.load(index);
69 nodes.add(node);
70 }
71
72 return nodes;
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java b/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java
deleted file mode 100644
index 8a1c238..0000000
--- a/src/main/java/cuchaz/enigma/analysis/InterpreterPair.java
+++ /dev/null
@@ -1,131 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.utils.Utils;
4import org.objectweb.asm.Opcodes;
5import org.objectweb.asm.Type;
6import org.objectweb.asm.tree.AbstractInsnNode;
7import org.objectweb.asm.tree.analysis.AnalyzerException;
8import org.objectweb.asm.tree.analysis.Interpreter;
9import org.objectweb.asm.tree.analysis.Value;
10
11import java.util.List;
12import java.util.Objects;
13import java.util.stream.Collectors;
14
15public class InterpreterPair<V extends Value, W extends Value> extends Interpreter<InterpreterPair.PairValue<V, W>> {
16 private final Interpreter<V> left;
17 private final Interpreter<W> right;
18
19 public InterpreterPair(Interpreter<V> left, Interpreter<W> right) {
20 super(Utils.ASM_VERSION);
21 this.left = left;
22 this.right = right;
23 }
24
25 @Override
26 public PairValue<V, W> newValue(Type type) {
27 return pair(
28 left.newValue(type),
29 right.newValue(type)
30 );
31 }
32
33 @Override
34 public PairValue<V, W> newOperation(AbstractInsnNode insn) throws AnalyzerException {
35 return pair(
36 left.newOperation(insn),
37 right.newOperation(insn)
38 );
39 }
40
41 @Override
42 public PairValue<V, W> copyOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
43 return pair(
44 left.copyOperation(insn, value.left),
45 right.copyOperation(insn, value.right)
46 );
47 }
48
49 @Override
50 public PairValue<V, W> unaryOperation(AbstractInsnNode insn, PairValue<V, W> value) throws AnalyzerException {
51 return pair(
52 left.unaryOperation(insn, value.left),
53 right.unaryOperation(insn, value.right)
54 );
55 }
56
57 @Override
58 public PairValue<V, W> binaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2) throws AnalyzerException {
59 return pair(
60 left.binaryOperation(insn, value1.left, value2.left),
61 right.binaryOperation(insn, value1.right, value2.right)
62 );
63 }
64
65 @Override
66 public PairValue<V, W> ternaryOperation(AbstractInsnNode insn, PairValue<V, W> value1, PairValue<V, W> value2, PairValue<V, W> value3) throws AnalyzerException {
67 return pair(
68 left.ternaryOperation(insn, value1.left, value2.left, value3.left),
69 right.ternaryOperation(insn, value1.right, value2.right, value3.right)
70 );
71 }
72
73 @Override
74 public PairValue<V, W> naryOperation(AbstractInsnNode insn, List<? extends PairValue<V, W>> values) throws AnalyzerException {
75 return pair(
76 left.naryOperation(insn, values.stream().map(v -> v.left).collect(Collectors.toList())),
77 right.naryOperation(insn, values.stream().map(v -> v.right).collect(Collectors.toList()))
78 );
79 }
80
81 @Override
82 public void returnOperation(AbstractInsnNode insn, PairValue<V, W> value, PairValue<V, W> expected) throws AnalyzerException {
83 left.returnOperation(insn, value.left, expected.left);
84 right.returnOperation(insn, value.right, expected.right);
85 }
86
87 @Override
88 public PairValue<V, W> merge(PairValue<V, W> value1, PairValue<V, W> value2) {
89 return pair(
90 left.merge(value1.left, value2.left),
91 right.merge(value1.right, value2.right)
92 );
93 }
94
95 private PairValue<V, W> pair(V left, W right) {
96 if (left == null && right == null) {
97 return null;
98 }
99
100 return new PairValue<>(left, right);
101 }
102
103 public static final class PairValue<V extends Value, W extends Value> implements Value {
104 public final V left;
105 public final W right;
106
107 public PairValue(V left, W right) {
108 if (left == null && right == null) {
109 throw new IllegalArgumentException("should use null rather than pair of nulls");
110 }
111
112 this.left = left;
113 this.right = right;
114 }
115
116 @Override
117 public boolean equals(Object o) {
118 return o instanceof InterpreterPair.PairValue && Objects.equals(left, ((PairValue) o).left) && Objects.equals(right, ((PairValue) o).right);
119 }
120
121 @Override
122 public int hashCode() {
123 return left.hashCode() * 31 + right.hashCode();
124 }
125
126 @Override
127 public int getSize() {
128 return (left == null ? right : left).getSize();
129 }
130 }
131}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
deleted file mode 100644
index b09f7ac..0000000
--- a/src/main/java/cuchaz/enigma/analysis/MethodImplementationsTreeNode.java
+++ /dev/null
@@ -1,85 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.analysis.index.EntryIndex;
16import cuchaz.enigma.analysis.index.InheritanceIndex;
17import cuchaz.enigma.analysis.index.JarIndex;
18import cuchaz.enigma.translation.Translator;
19import cuchaz.enigma.translation.representation.entry.ClassEntry;
20import cuchaz.enigma.translation.representation.entry.MethodEntry;
21
22import javax.swing.tree.DefaultMutableTreeNode;
23import java.util.Collection;
24import java.util.List;
25
26public class MethodImplementationsTreeNode extends DefaultMutableTreeNode {
27
28 private final Translator translator;
29 private MethodEntry entry;
30
31 public MethodImplementationsTreeNode(Translator translator, MethodEntry entry) {
32 this.translator = translator;
33 if (entry == null) {
34 throw new IllegalArgumentException("Entry cannot be null!");
35 }
36
37 this.entry = entry;
38 }
39
40 public static MethodImplementationsTreeNode findNode(MethodImplementationsTreeNode node, MethodEntry entry) {
41 // is this the node?
42 if (node.getMethodEntry().equals(entry)) {
43 return node;
44 }
45
46 // recurse
47 for (int i = 0; i < node.getChildCount(); i++) {
48 MethodImplementationsTreeNode foundNode = findNode((MethodImplementationsTreeNode) node.getChildAt(i), entry);
49 if (foundNode != null) {
50 return foundNode;
51 }
52 }
53 return null;
54 }
55
56 public MethodEntry getMethodEntry() {
57 return this.entry;
58 }
59
60 @Override
61 public String toString() {
62 MethodEntry translatedEntry = translator.translate(entry);
63 String className = translatedEntry.getParent().getFullName();
64 String methodName = translatedEntry.getName();
65 return className + "." + methodName + "()";
66 }
67
68 public void load(JarIndex index) {
69 // get all method implementations
70 List<MethodImplementationsTreeNode> nodes = Lists.newArrayList();
71 EntryIndex entryIndex = index.getEntryIndex();
72 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
73
74 Collection<ClassEntry> descendants = inheritanceIndex.getDescendants(entry.getParent());
75 for (ClassEntry inheritor : descendants) {
76 MethodEntry methodEntry = entry.withParent(inheritor);
77 if (entryIndex.hasMethod(methodEntry)) {
78 nodes.add(new MethodImplementationsTreeNode(translator, methodEntry));
79 }
80 }
81
82 // add them to this node
83 nodes.forEach(this::add);
84 }
85}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
deleted file mode 100644
index e77b5cc..0000000
--- a/src/main/java/cuchaz/enigma/analysis/MethodInheritanceTreeNode.java
+++ /dev/null
@@ -1,95 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.analysis.index.EntryIndex;
15import cuchaz.enigma.analysis.index.InheritanceIndex;
16import cuchaz.enigma.analysis.index.JarIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.MethodEntry;
20
21import javax.swing.tree.DefaultMutableTreeNode;
22
23public class MethodInheritanceTreeNode extends DefaultMutableTreeNode {
24
25 private final Translator translator;
26 private MethodEntry entry;
27 private boolean implemented;
28
29 public MethodInheritanceTreeNode(Translator translator, MethodEntry entry, boolean implemented) {
30 this.translator = translator;
31 this.entry = entry;
32 this.implemented = implemented;
33 }
34
35 public static MethodInheritanceTreeNode findNode(MethodInheritanceTreeNode node, MethodEntry entry) {
36 // is this the node?
37 if (node.getMethodEntry().equals(entry)) {
38 return node;
39 }
40
41 // recurse
42 for (int i = 0; i < node.getChildCount(); i++) {
43 MethodInheritanceTreeNode foundNode = findNode((MethodInheritanceTreeNode) node.getChildAt(i), entry);
44 if (foundNode != null) {
45 return foundNode;
46 }
47 }
48 return null;
49 }
50
51 public MethodEntry getMethodEntry() {
52 return this.entry;
53 }
54
55 public boolean isImplemented() {
56 return this.implemented;
57 }
58
59 @Override
60 public String toString() {
61 MethodEntry translatedEntry = translator.translate(entry);
62 String className = translatedEntry.getContainingClass().getFullName();
63
64 if (!this.implemented) {
65 return className;
66 } else {
67 String methodName = translatedEntry.getName();
68 return className + "." + methodName + "()";
69 }
70 }
71
72 /**
73 * Returns true if there is sub-node worthy to display.
74 */
75 public boolean load(JarIndex index) {
76 // get all the child nodes
77 EntryIndex entryIndex = index.getEntryIndex();
78 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
79
80 boolean ret = false;
81 for (ClassEntry inheritorEntry : inheritanceIndex.getChildren(this.entry.getParent())) {
82 MethodEntry methodEntry = new MethodEntry(inheritorEntry, this.entry.getName(), this.entry.getDesc());
83
84 MethodInheritanceTreeNode node = new MethodInheritanceTreeNode(translator, methodEntry, entryIndex.hasMethod(methodEntry));
85 boolean childOverride = node.load(index);
86
87 if (childOverride || node.implemented) {
88 this.add(node);
89 ret = true;
90 }
91 }
92
93 return ret;
94 }
95}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java b/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java
deleted file mode 100644
index 8117103..0000000
--- a/src/main/java/cuchaz/enigma/analysis/MethodNodeWithAction.java
+++ /dev/null
@@ -1,19 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import org.objectweb.asm.tree.MethodNode;
4
5import java.util.function.Consumer;
6
7public class MethodNodeWithAction extends MethodNode {
8 private final Consumer<MethodNode> action;
9
10 public MethodNodeWithAction(int api, int access, String name, String descriptor, String signature, String[] exceptions, Consumer<MethodNode> action) {
11 super(api, access, name, descriptor, signature, exceptions);
12 this.action = action;
13 }
14
15 @Override
16 public void visitEnd() {
17 action.accept(this);
18 }
19}
diff --git a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
deleted file mode 100644
index 8995eb5..0000000
--- a/src/main/java/cuchaz/enigma/analysis/MethodReferenceTreeNode.java
+++ /dev/null
@@ -1,113 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import com.google.common.collect.Sets;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.analysis.index.ReferenceIndex;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
21import cuchaz.enigma.translation.representation.entry.MethodEntry;
22
23import javax.swing.tree.DefaultMutableTreeNode;
24import javax.swing.tree.TreeNode;
25import java.util.ArrayList;
26import java.util.Collection;
27import java.util.Set;
28
29public class MethodReferenceTreeNode extends DefaultMutableTreeNode implements ReferenceTreeNode<MethodEntry, MethodDefEntry> {
30
31 private final Translator translator;
32 private MethodEntry entry;
33 private EntryReference<MethodEntry, MethodDefEntry> reference;
34
35 public MethodReferenceTreeNode(Translator translator, MethodEntry entry) {
36 this.translator = translator;
37 this.entry = entry;
38 this.reference = null;
39 }
40
41 public MethodReferenceTreeNode(Translator translator, EntryReference<MethodEntry, MethodDefEntry> reference) {
42 this.translator = translator;
43 this.entry = reference.entry;
44 this.reference = reference;
45 }
46
47 @Override
48 public MethodEntry getEntry() {
49 return this.entry;
50 }
51
52 @Override
53 public EntryReference<MethodEntry, MethodDefEntry> getReference() {
54 return this.reference;
55 }
56
57 @Override
58 public String toString() {
59 if (this.reference != null) {
60 return String.format("%s", translator.translate(this.reference.context));
61 }
62 return translator.translate(this.entry).getName();
63 }
64
65 public void load(JarIndex index, boolean recurse, boolean recurseMethod) {
66 // get all the child nodes
67 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = getReferences(index, recurseMethod);
68
69 for (EntryReference<MethodEntry, MethodDefEntry> reference : references) {
70 add(new MethodReferenceTreeNode(translator, reference));
71 }
72
73 if (recurse && this.children != null) {
74 for (Object child : this.children) {
75 if (child instanceof MethodReferenceTreeNode) {
76 MethodReferenceTreeNode node = (MethodReferenceTreeNode) child;
77
78 // don't recurse into ancestor
79 Set<Entry<?>> ancestors = Sets.newHashSet();
80 TreeNode n = node;
81 while (n.getParent() != null) {
82 n = n.getParent();
83 if (n instanceof MethodReferenceTreeNode) {
84 ancestors.add(((MethodReferenceTreeNode) n).getEntry());
85 }
86 }
87 if (ancestors.contains(node.getEntry())) {
88 continue;
89 }
90
91 node.load(index, true, false);
92 }
93 }
94 }
95 }
96
97 private Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferences(JarIndex index, boolean recurseMethod) {
98 ReferenceIndex referenceIndex = index.getReferenceIndex();
99
100 if (recurseMethod) {
101 Collection<EntryReference<MethodEntry, MethodDefEntry>> references = new ArrayList<>();
102
103 EntryResolver entryResolver = index.getEntryResolver();
104 for (MethodEntry methodEntry : entryResolver.resolveEquivalentMethods(entry)) {
105 references.addAll(referenceIndex.getReferencesToMethod(methodEntry));
106 }
107
108 return references;
109 } else {
110 return referenceIndex.getReferencesToMethod(entry);
111 }
112 }
113}
diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java
deleted file mode 100644
index 5b19d18..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ReferenceTargetType.java
+++ /dev/null
@@ -1,74 +0,0 @@
1package cuchaz.enigma.analysis;
2
3import cuchaz.enigma.translation.representation.entry.ClassEntry;
4
5public abstract class ReferenceTargetType {
6 private static final None NONE = new None();
7 private static final Uninitialized UNINITIALIZED = new Uninitialized();
8
9 public abstract Kind getKind();
10
11 public static None none() {
12 return NONE;
13 }
14
15 public static Uninitialized uninitialized() {
16 return UNINITIALIZED;
17 }
18
19 public static ClassType classType(ClassEntry name) {
20 return new ClassType(name);
21 }
22
23 public enum Kind {
24 NONE,
25 UNINITIALIZED,
26 CLASS_TYPE
27 }
28
29 public static class None extends ReferenceTargetType {
30 @Override
31 public Kind getKind() {
32 return Kind.NONE;
33 }
34
35 @Override
36 public String toString() {
37 return "(none)";
38 }
39 }
40
41 public static class Uninitialized extends ReferenceTargetType {
42 @Override
43 public Kind getKind() {
44 return Kind.UNINITIALIZED;
45 }
46
47 @Override
48 public String toString() {
49 return "(uninitialized)";
50 }
51 }
52
53 public static class ClassType extends ReferenceTargetType {
54 private final ClassEntry entry;
55
56 private ClassType(ClassEntry entry) {
57 this.entry = entry;
58 }
59
60 public ClassEntry getEntry() {
61 return entry;
62 }
63
64 @Override
65 public Kind getKind() {
66 return Kind.CLASS_TYPE;
67 }
68
69 @Override
70 public String toString() {
71 return entry.toString();
72 }
73 }
74}
diff --git a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java b/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
deleted file mode 100644
index c0a3a75..0000000
--- a/src/main/java/cuchaz/enigma/analysis/ReferenceTreeNode.java
+++ /dev/null
@@ -1,20 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14import cuchaz.enigma.translation.representation.entry.Entry;
15
16public interface ReferenceTreeNode<E extends Entry<?>, C extends Entry<?>> {
17 E getEntry();
18
19 EntryReference<E, C> getReference();
20}
diff --git a/src/main/java/cuchaz/enigma/analysis/Token.java b/src/main/java/cuchaz/enigma/analysis/Token.java
deleted file mode 100644
index f0155e5..0000000
--- a/src/main/java/cuchaz/enigma/analysis/Token.java
+++ /dev/null
@@ -1,72 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis;
13
14public class Token implements Comparable<Token> {
15
16 public int start;
17 public int end;
18 public String text;
19
20 public Token(int start, int end, String text) {
21 this.start = start;
22 this.end = end;
23 this.text = text;
24 }
25
26 public int getRenameOffset(String to) {
27 int length = this.end - this.start;
28 return to.length() - length;
29 }
30
31 public void rename(StringBuffer source, String to) {
32 int oldEnd = this.end;
33 this.text = to;
34 this.end = this.start + to.length();
35
36 source.replace(start, oldEnd, to);
37 }
38
39 public Token move(int offset) {
40 Token token = new Token(this.start + offset, this.end + offset, null);
41 token.text = text;
42 return token;
43 }
44
45 public boolean contains(int pos) {
46 return pos >= start && pos <= end;
47 }
48
49 @Override
50 public int compareTo(Token other) {
51 return start - other.start;
52 }
53
54 @Override
55 public boolean equals(Object other) {
56 return other instanceof Token && equals((Token) other);
57 }
58
59 @Override
60 public int hashCode() {
61 return start * 37 + end;
62 }
63
64 public boolean equals(Token other) {
65 return start == other.start && end == other.end && text.equals(other.text);
66 }
67
68 @Override
69 public String toString() {
70 return String.format("[%d,%d]", start, end);
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java b/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
deleted file mode 100644
index a4b1aac..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/BridgeMethodIndex.java
+++ /dev/null
@@ -1,156 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.Maps;
4import cuchaz.enigma.translation.representation.AccessFlags;
5import cuchaz.enigma.translation.representation.MethodDescriptor;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.ClassEntry;
8import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
9import cuchaz.enigma.translation.representation.entry.MethodEntry;
10
11import javax.annotation.Nullable;
12import java.util.*;
13
14public class BridgeMethodIndex implements JarIndexer {
15 private final EntryIndex entryIndex;
16 private final InheritanceIndex inheritanceIndex;
17 private final ReferenceIndex referenceIndex;
18
19 private final Map<MethodEntry, MethodEntry> bridgeToSpecialized = Maps.newHashMap();
20 private final Map<MethodEntry, MethodEntry> specializedToBridge = Maps.newHashMap();
21
22 public BridgeMethodIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex) {
23 this.entryIndex = entryIndex;
24 this.inheritanceIndex = inheritanceIndex;
25 this.referenceIndex = referenceIndex;
26 }
27
28 public void findBridgeMethods() {
29 // look for access and bridged methods
30 for (MethodEntry methodEntry : entryIndex.getMethods()) {
31 MethodDefEntry methodDefEntry = (MethodDefEntry) methodEntry;
32
33 AccessFlags access = methodDefEntry.getAccess();
34 if (access == null || !access.isSynthetic()) {
35 continue;
36 }
37
38 indexSyntheticMethod(methodDefEntry, access);
39 }
40 }
41
42 @Override
43 public void processIndex(JarIndex index) {
44 Map<MethodEntry, MethodEntry> copiedAccessToBridge = new HashMap<>(specializedToBridge);
45
46 for (Map.Entry<MethodEntry, MethodEntry> entry : copiedAccessToBridge.entrySet()) {
47 MethodEntry specializedEntry = entry.getKey();
48 MethodEntry bridgeEntry = entry.getValue();
49 if (bridgeEntry.getName().equals(specializedEntry.getName())) {
50 continue;
51 }
52
53 MethodEntry renamedSpecializedEntry = specializedEntry.withName(bridgeEntry.getName());
54 specializedToBridge.put(renamedSpecializedEntry, specializedToBridge.get(specializedEntry));
55 }
56 }
57
58 private void indexSyntheticMethod(MethodDefEntry syntheticMethod, AccessFlags access) {
59 MethodEntry specializedMethod = findSpecializedMethod(syntheticMethod);
60 if (specializedMethod == null) {
61 return;
62 }
63
64 if (access.isBridge() || isPotentialBridge(syntheticMethod, specializedMethod)) {
65 bridgeToSpecialized.put(syntheticMethod, specializedMethod);
66 specializedToBridge.put(specializedMethod, syntheticMethod);
67 }
68 }
69
70 private MethodEntry findSpecializedMethod(MethodEntry method) {
71 // we want to find all compiler-added methods that directly call another with no processing
72
73 // get all the methods that we call
74 final Collection<MethodEntry> referencedMethods = referenceIndex.getMethodsReferencedBy(method);
75
76 // is there just one?
77 if (referencedMethods.size() != 1) {
78 return null;
79 }
80
81 return referencedMethods.stream().findFirst().orElse(null);
82 }
83
84 private boolean isPotentialBridge(MethodDefEntry bridgeMethod, MethodEntry specializedMethod) {
85 // Bridge methods only exist for inheritance purposes, if we're private, final, or static, we cannot be inherited
86 AccessFlags bridgeAccess = bridgeMethod.getAccess();
87 if (bridgeAccess.isPrivate() || bridgeAccess.isFinal() || bridgeAccess.isStatic()) {
88 return false;
89 }
90
91 MethodDescriptor bridgeDesc = bridgeMethod.getDesc();
92 MethodDescriptor specializedDesc = specializedMethod.getDesc();
93 List<TypeDescriptor> bridgeArguments = bridgeDesc.getArgumentDescs();
94 List<TypeDescriptor> specializedArguments = specializedDesc.getArgumentDescs();
95
96 // A bridge method will always have the same number of arguments
97 if (bridgeArguments.size() != specializedArguments.size()) {
98 return false;
99 }
100
101 // Check that all argument types are bridge-compatible
102 for (int i = 0; i < bridgeArguments.size(); i++) {
103 if (!areTypesBridgeCompatible(bridgeArguments.get(i), specializedArguments.get(i))) {
104 return false;
105 }
106 }
107
108 // Check that the return type is bridge-compatible
109 return areTypesBridgeCompatible(bridgeDesc.getReturnDesc(), specializedDesc.getReturnDesc());
110 }
111
112 private boolean areTypesBridgeCompatible(TypeDescriptor bridgeDesc, TypeDescriptor specializedDesc) {
113 if (bridgeDesc.equals(specializedDesc)) {
114 return true;
115 }
116
117 // Either the descs will be equal, or they are both types and different through a generic
118 if (bridgeDesc.isType() && specializedDesc.isType()) {
119 ClassEntry bridgeType = bridgeDesc.getTypeEntry();
120 ClassEntry accessedType = specializedDesc.getTypeEntry();
121
122 // If the given types are completely unrelated to each other, this can't be bridge compatible
123 InheritanceIndex.Relation relation = inheritanceIndex.computeClassRelation(accessedType, bridgeType);
124 return relation != InheritanceIndex.Relation.UNRELATED;
125 }
126
127 return false;
128 }
129
130 public boolean isBridgeMethod(MethodEntry entry) {
131 return bridgeToSpecialized.containsKey(entry);
132 }
133
134 public boolean isSpecializedMethod(MethodEntry entry) {
135 return specializedToBridge.containsKey(entry);
136 }
137
138 @Nullable
139 public MethodEntry getBridgeFromSpecialized(MethodEntry specialized) {
140 return specializedToBridge.get(specialized);
141 }
142
143 public MethodEntry getSpecializedFromBridge(MethodEntry bridge) {
144 return bridgeToSpecialized.get(bridge);
145 }
146
147 /** Includes "renamed specialized -> bridge" entries. */
148 public Map<MethodEntry, MethodEntry> getSpecializedToBridge() {
149 return Collections.unmodifiableMap(specializedToBridge);
150 }
151
152 /** Only "bridge -> original name" entries. **/
153 public Map<MethodEntry, MethodEntry> getBridgeToSpecialized() {
154 return Collections.unmodifiableMap(bridgeToSpecialized);
155 }
156}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
deleted file mode 100644
index 9a2726e..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java
+++ /dev/null
@@ -1,102 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4import cuchaz.enigma.translation.representation.entry.*;
5
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.Map;
10
11public class EntryIndex implements JarIndexer {
12 private Map<ClassEntry, AccessFlags> classes = new HashMap<>();
13 private Map<FieldEntry, AccessFlags> fields = new HashMap<>();
14 private Map<MethodEntry, AccessFlags> methods = new HashMap<>();
15 private Map<ClassEntry, ClassDefEntry> definitions = new HashMap<>();
16
17 @Override
18 public void indexClass(ClassDefEntry classEntry) {
19 definitions.put(classEntry, classEntry);
20 classes.put(classEntry, classEntry.getAccess());
21 }
22
23 @Override
24 public void indexMethod(MethodDefEntry methodEntry) {
25 methods.put(methodEntry, methodEntry.getAccess());
26 }
27
28 @Override
29 public void indexField(FieldDefEntry fieldEntry) {
30 fields.put(fieldEntry, fieldEntry.getAccess());
31 }
32
33 public boolean hasClass(ClassEntry entry) {
34 return classes.containsKey(entry);
35 }
36
37 public boolean hasMethod(MethodEntry entry) {
38 return methods.containsKey(entry);
39 }
40
41 public boolean hasField(FieldEntry entry) {
42 return fields.containsKey(entry);
43 }
44
45 public boolean hasEntry(Entry<?> entry) {
46 if (entry instanceof ClassEntry) {
47 return hasClass((ClassEntry) entry);
48 } else if (entry instanceof MethodEntry) {
49 return hasMethod((MethodEntry) entry);
50 } else if (entry instanceof FieldEntry) {
51 return hasField((FieldEntry) entry);
52 } else if (entry instanceof LocalVariableEntry) {
53 return hasMethod(((LocalVariableEntry) entry).getParent());
54 }
55
56 return false;
57 }
58
59 @Nullable
60 public AccessFlags getMethodAccess(MethodEntry entry) {
61 return methods.get(entry);
62 }
63
64 @Nullable
65 public AccessFlags getFieldAccess(FieldEntry entry) {
66 return fields.get(entry);
67 }
68
69 @Nullable
70 public AccessFlags getClassAccess(ClassEntry entry) {
71 return classes.get(entry);
72 }
73
74 @Nullable
75 public AccessFlags getEntryAccess(Entry<?> entry) {
76 if (entry instanceof MethodEntry) {
77 return getMethodAccess((MethodEntry) entry);
78 } else if (entry instanceof FieldEntry) {
79 return getFieldAccess((FieldEntry) entry);
80 } else if (entry instanceof LocalVariableEntry) {
81 return getMethodAccess(((LocalVariableEntry) entry).getParent());
82 }
83
84 return null;
85 }
86
87 public ClassDefEntry getDefinition(ClassEntry entry) {
88 return definitions.get(entry);
89 }
90
91 public Collection<ClassEntry> getClasses() {
92 return classes.keySet();
93 }
94
95 public Collection<MethodEntry> getMethods() {
96 return methods.keySet();
97 }
98
99 public Collection<FieldEntry> getFields() {
100 return fields.keySet();
101 }
102}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
deleted file mode 100644
index f9cb23c..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/IndexClassVisitor.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
4import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
5import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
6import org.objectweb.asm.ClassVisitor;
7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.MethodVisitor;
9
10public class IndexClassVisitor extends ClassVisitor {
11 private final JarIndexer indexer;
12 private ClassDefEntry classEntry;
13
14 public IndexClassVisitor(JarIndex indexer, int api) {
15 super(api);
16 this.indexer = indexer;
17 }
18
19 @Override
20 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
21 classEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
22 indexer.indexClass(classEntry);
23
24 super.visit(version, access, name, signature, superName, interfaces);
25 }
26
27 @Override
28 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
29 indexer.indexField(FieldDefEntry.parse(classEntry, access, name, desc, signature));
30
31 return super.visitField(access, name, desc, signature, value);
32 }
33
34 @Override
35 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
36 indexer.indexMethod(MethodDefEntry.parse(classEntry, access, name, desc, signature));
37
38 return super.visitMethod(access, name, desc, signature, exceptions);
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java
deleted file mode 100644
index f3d419e..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java
+++ /dev/null
@@ -1,180 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.analysis.IndexSimpleVerifier;
4import cuchaz.enigma.analysis.InterpreterPair;
5import cuchaz.enigma.analysis.MethodNodeWithAction;
6import cuchaz.enigma.analysis.ReferenceTargetType;
7import cuchaz.enigma.translation.representation.AccessFlags;
8import cuchaz.enigma.translation.representation.Lambda;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.Signature;
11import cuchaz.enigma.translation.representation.entry.*;
12import org.objectweb.asm.*;
13import org.objectweb.asm.tree.AbstractInsnNode;
14import org.objectweb.asm.tree.FieldInsnNode;
15import org.objectweb.asm.tree.InvokeDynamicInsnNode;
16import org.objectweb.asm.tree.MethodInsnNode;
17import org.objectweb.asm.tree.analysis.*;
18
19import java.util.List;
20import java.util.stream.Collectors;
21
22public class IndexReferenceVisitor extends ClassVisitor {
23 private final JarIndexer indexer;
24 private final EntryIndex entryIndex;
25 private final InheritanceIndex inheritanceIndex;
26 private ClassEntry classEntry;
27 private String className;
28
29 public IndexReferenceVisitor(JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex, int api) {
30 super(api);
31 this.indexer = indexer;
32 this.entryIndex = entryIndex;
33 this.inheritanceIndex = inheritanceIndex;
34 }
35
36 @Override
37 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
38 classEntry = new ClassEntry(name);
39 className = name;
40 }
41
42 @Override
43 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
44 MethodDefEntry entry = new MethodDefEntry(classEntry, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access));
45 return new MethodNodeWithAction(api, access, name, desc, signature, exceptions, methodNode -> {
46 try {
47 new Analyzer<>(new MethodInterpreter(entry, indexer, entryIndex, inheritanceIndex)).analyze(className, methodNode);
48 } catch (AnalyzerException e) {
49 throw new RuntimeException(e);
50 }
51 });
52 }
53
54 private static class MethodInterpreter extends InterpreterPair<BasicValue, SourceValue> {
55 private final MethodDefEntry callerEntry;
56 private JarIndexer indexer;
57
58 public MethodInterpreter(MethodDefEntry callerEntry, JarIndexer indexer, EntryIndex entryIndex, InheritanceIndex inheritanceIndex) {
59 super(new IndexSimpleVerifier(entryIndex, inheritanceIndex), new SourceInterpreter());
60 this.callerEntry = callerEntry;
61 this.indexer = indexer;
62 }
63
64 @Override
65 public PairValue<BasicValue, SourceValue> newOperation(AbstractInsnNode insn) throws AnalyzerException {
66 if (insn.getOpcode() == Opcodes.GETSTATIC) {
67 FieldInsnNode field = (FieldInsnNode) insn;
68 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none());
69 }
70
71 return super.newOperation(insn);
72 }
73
74 @Override
75 public PairValue<BasicValue, SourceValue> unaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value) throws AnalyzerException {
76 if (insn.getOpcode() == Opcodes.PUTSTATIC) {
77 FieldInsnNode field = (FieldInsnNode) insn;
78 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), ReferenceTargetType.none());
79 }
80
81 if (insn.getOpcode() == Opcodes.GETFIELD) {
82 FieldInsnNode field = (FieldInsnNode) insn;
83 indexer.indexFieldReference(callerEntry, FieldEntry.parse(field.owner, field.name, field.desc), getReferenceTargetType(value, insn));
84 }
85
86 return super.unaryOperation(insn, value);
87 }
88
89
90 @Override
91 public PairValue<BasicValue, SourceValue> binaryOperation(AbstractInsnNode insn, PairValue<BasicValue, SourceValue> value1, PairValue<BasicValue, SourceValue> value2) throws AnalyzerException {
92 if (insn.getOpcode() == Opcodes.PUTFIELD) {
93 FieldInsnNode field = (FieldInsnNode) insn;
94 FieldEntry fieldEntry = FieldEntry.parse(field.owner, field.name, field.desc);
95 indexer.indexFieldReference(callerEntry, fieldEntry, ReferenceTargetType.none());
96 }
97
98 return super.binaryOperation(insn, value1, value2);
99 }
100
101 @Override
102 public PairValue<BasicValue, SourceValue> naryOperation(AbstractInsnNode insn, List<? extends PairValue<BasicValue, SourceValue>> values) throws AnalyzerException {
103 if (insn.getOpcode() == Opcodes.INVOKEINTERFACE || insn.getOpcode() == Opcodes.INVOKESPECIAL || insn.getOpcode() == Opcodes.INVOKEVIRTUAL) {
104 MethodInsnNode methodInsn = (MethodInsnNode) insn;
105 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), getReferenceTargetType(values.get(0), insn));
106 }
107
108 if (insn.getOpcode() == Opcodes.INVOKESTATIC) {
109 MethodInsnNode methodInsn = (MethodInsnNode) insn;
110 indexer.indexMethodReference(callerEntry, MethodEntry.parse(methodInsn.owner, methodInsn.name, methodInsn.desc), ReferenceTargetType.none());
111 }
112
113 if (insn.getOpcode() == Opcodes.INVOKEDYNAMIC) {
114 InvokeDynamicInsnNode invokeDynamicInsn = (InvokeDynamicInsnNode) insn;
115 List<AbstractInsnNode> args = values.stream().map(v -> v.right.insns.stream().findFirst().orElseThrow(AssertionError::new)).collect(Collectors.toList());
116
117 if ("java/lang/invoke/LambdaMetafactory".equals(invokeDynamicInsn.bsm.getOwner()) && "metafactory".equals(invokeDynamicInsn.bsm.getName())) {
118 Type samMethodType = (Type) invokeDynamicInsn.bsmArgs[0];
119 Handle implMethod = (Handle) invokeDynamicInsn.bsmArgs[1];
120 Type instantiatedMethodType = (Type) invokeDynamicInsn.bsmArgs[2];
121
122 ReferenceTargetType targetType;
123 if (implMethod.getTag() != Opcodes.H_GETSTATIC && implMethod.getTag() != Opcodes.H_PUTFIELD && implMethod.getTag() != Opcodes.H_INVOKESTATIC) {
124 if (instantiatedMethodType.getArgumentTypes().length < Type.getArgumentTypes(implMethod.getDesc()).length) {
125 targetType = getReferenceTargetType(values.get(0), insn);
126 } else {
127 targetType = ReferenceTargetType.none(); // no "this" argument
128 }
129 } else {
130 targetType = ReferenceTargetType.none();
131 }
132
133 indexer.indexLambda(callerEntry, new Lambda(
134 invokeDynamicInsn.name,
135 new MethodDescriptor(invokeDynamicInsn.desc),
136 new MethodDescriptor(samMethodType.getDescriptor()),
137 getHandleEntry(implMethod),
138 new MethodDescriptor(instantiatedMethodType.getDescriptor())
139 ), targetType);
140 }
141 }
142
143 return super.naryOperation(insn, values);
144 }
145
146 private ReferenceTargetType getReferenceTargetType(PairValue<BasicValue, SourceValue> target, AbstractInsnNode insn) throws AnalyzerException {
147 if (target.left == BasicValue.UNINITIALIZED_VALUE) {
148 return ReferenceTargetType.uninitialized();
149 }
150
151 if (target.left.getType().getSort() == Type.OBJECT) {
152 return ReferenceTargetType.classType(new ClassEntry(target.left.getType().getInternalName()));
153 }
154
155 if (target.left.getType().getSort() == Type.ARRAY) {
156 return ReferenceTargetType.classType(new ClassEntry("java/lang/Object"));
157 }
158
159 throw new AnalyzerException(insn, "called method on or accessed field of non-object type");
160 }
161
162 private static ParentedEntry<?> getHandleEntry(Handle handle) {
163 switch (handle.getTag()) {
164 case Opcodes.H_GETFIELD:
165 case Opcodes.H_GETSTATIC:
166 case Opcodes.H_PUTFIELD:
167 case Opcodes.H_PUTSTATIC:
168 return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
169 case Opcodes.H_INVOKEINTERFACE:
170 case Opcodes.H_INVOKESPECIAL:
171 case Opcodes.H_INVOKESTATIC:
172 case Opcodes.H_INVOKEVIRTUAL:
173 case Opcodes.H_NEWINVOKESPECIAL:
174 return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc());
175 }
176
177 throw new RuntimeException("Invalid handle tag " + handle.getTag());
178 }
179 }
180}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
deleted file mode 100644
index 1ab2abd..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/InheritanceIndex.java
+++ /dev/null
@@ -1,127 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.google.common.collect.Sets;
17import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19
20import java.util.Collection;
21import java.util.HashSet;
22import java.util.LinkedList;
23import java.util.Set;
24
25public class InheritanceIndex implements JarIndexer {
26 private final EntryIndex entryIndex;
27
28 private Multimap<ClassEntry, ClassEntry> classParents = HashMultimap.create();
29 private Multimap<ClassEntry, ClassEntry> classChildren = HashMultimap.create();
30
31 public InheritanceIndex(EntryIndex entryIndex) {
32 this.entryIndex = entryIndex;
33 }
34
35 @Override
36 public void indexClass(ClassDefEntry classEntry) {
37 if (classEntry.isJre()) {
38 return;
39 }
40
41 ClassEntry superClass = classEntry.getSuperClass();
42 if (superClass != null && !superClass.getName().equals("java/lang/Object")) {
43 indexParent(classEntry, superClass);
44 }
45
46 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
47 indexParent(classEntry, interfaceEntry);
48 }
49 }
50
51 private void indexParent(ClassEntry childEntry, ClassEntry parentEntry) {
52 classParents.put(childEntry, parentEntry);
53 classChildren.put(parentEntry, childEntry);
54 }
55
56 public Collection<ClassEntry> getParents(ClassEntry classEntry) {
57 return classParents.get(classEntry);
58 }
59
60 public Collection<ClassEntry> getChildren(ClassEntry classEntry) {
61 return classChildren.get(classEntry);
62 }
63
64 public Collection<ClassEntry> getDescendants(ClassEntry classEntry) {
65 Collection<ClassEntry> descendants = new HashSet<>();
66
67 LinkedList<ClassEntry> descendantQueue = new LinkedList<>();
68 descendantQueue.push(classEntry);
69
70 while (!descendantQueue.isEmpty()) {
71 ClassEntry descendant = descendantQueue.pop();
72 Collection<ClassEntry> children = getChildren(descendant);
73
74 children.forEach(descendantQueue::push);
75 descendants.addAll(children);
76 }
77
78 return descendants;
79 }
80
81 public Set<ClassEntry> getAncestors(ClassEntry classEntry) {
82 Set<ClassEntry> ancestors = Sets.newHashSet();
83
84 LinkedList<ClassEntry> ancestorQueue = new LinkedList<>();
85 ancestorQueue.push(classEntry);
86
87 while (!ancestorQueue.isEmpty()) {
88 ClassEntry ancestor = ancestorQueue.pop();
89 Collection<ClassEntry> parents = getParents(ancestor);
90
91 parents.forEach(ancestorQueue::push);
92 ancestors.addAll(parents);
93 }
94
95 return ancestors;
96 }
97
98 public Relation computeClassRelation(ClassEntry classEntry, ClassEntry potentialAncestor) {
99 if (potentialAncestor.getName().equals("java/lang/Object")) return Relation.RELATED;
100 if (!entryIndex.hasClass(classEntry)) return Relation.UNKNOWN;
101
102 for (ClassEntry ancestor : getAncestors(classEntry)) {
103 if (potentialAncestor.equals(ancestor)) {
104 return Relation.RELATED;
105 } else if (!entryIndex.hasClass(ancestor)) {
106 return Relation.UNKNOWN;
107 }
108 }
109
110 return Relation.UNRELATED;
111 }
112
113 public boolean isParent(ClassEntry classEntry) {
114 return classChildren.containsKey(classEntry);
115 }
116
117 public boolean hasParents(ClassEntry classEntry) {
118 Collection<ClassEntry> parents = classParents.get(classEntry);
119 return parents != null && !parents.isEmpty();
120 }
121
122 public enum Relation {
123 RELATED,
124 UNRELATED,
125 UNKNOWN
126 }
127}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
deleted file mode 100644
index e401c2f..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java
+++ /dev/null
@@ -1,171 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.analysis.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import cuchaz.enigma.ProgressListener;
17import cuchaz.enigma.analysis.ClassCache;
18import cuchaz.enigma.analysis.ReferenceTargetType;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.IndexEntryResolver;
21import cuchaz.enigma.translation.representation.Lambda;
22import cuchaz.enigma.translation.representation.entry.*;
23import cuchaz.enigma.utils.I18n;
24
25import cuchaz.enigma.utils.Utils;
26import org.objectweb.asm.ClassReader;
27import org.objectweb.asm.Opcodes;
28
29import java.util.Arrays;
30import java.util.Collection;
31
32public class JarIndex implements JarIndexer {
33 private final EntryIndex entryIndex;
34 private final InheritanceIndex inheritanceIndex;
35 private final ReferenceIndex referenceIndex;
36 private final BridgeMethodIndex bridgeMethodIndex;
37 private final PackageVisibilityIndex packageVisibilityIndex;
38 private final EntryResolver entryResolver;
39
40 private final Collection<JarIndexer> indexers;
41
42 private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create();
43
44 public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) {
45 this.entryIndex = entryIndex;
46 this.inheritanceIndex = inheritanceIndex;
47 this.referenceIndex = referenceIndex;
48 this.bridgeMethodIndex = bridgeMethodIndex;
49 this.packageVisibilityIndex = packageVisibilityIndex;
50 this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
51 this.entryResolver = new IndexEntryResolver(this);
52 }
53
54 public static JarIndex empty() {
55 EntryIndex entryIndex = new EntryIndex();
56 InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex);
57 ReferenceIndex referenceIndex = new ReferenceIndex();
58 BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex);
59 PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex();
60 return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex);
61 }
62
63 public void indexJar(ClassCache classCache, ProgressListener progress) {
64 progress.init(4, I18n.translate("progress.jar.indexing"));
65
66 progress.step(1, I18n.translate("progress.jar.indexing.entries"));
67 classCache.visit(() -> new IndexClassVisitor(this, Utils.ASM_VERSION), ClassReader.SKIP_CODE);
68
69 progress.step(2, I18n.translate("progress.jar.indexing.references"));
70 classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Utils.ASM_VERSION), 0);
71
72 progress.step(3, I18n.translate("progress.jar.indexing.methods"));
73 bridgeMethodIndex.findBridgeMethods();
74
75 progress.step(4, I18n.translate("progress.jar.indexing.process"));
76 processIndex(this);
77 }
78
79 @Override
80 public void processIndex(JarIndex index) {
81 indexers.forEach(indexer -> indexer.processIndex(index));
82 }
83
84 @Override
85 public void indexClass(ClassDefEntry classEntry) {
86 if (classEntry.isJre()) {
87 return;
88 }
89
90 for (ClassEntry interfaceEntry : classEntry.getInterfaces()) {
91 if (classEntry.equals(interfaceEntry)) {
92 throw new IllegalArgumentException("Class cannot be its own interface! " + classEntry);
93 }
94 }
95
96 indexers.forEach(indexer -> indexer.indexClass(classEntry));
97 }
98
99 @Override
100 public void indexField(FieldDefEntry fieldEntry) {
101 if (fieldEntry.getParent().isJre()) {
102 return;
103 }
104
105 indexers.forEach(indexer -> indexer.indexField(fieldEntry));
106 }
107
108 @Override
109 public void indexMethod(MethodDefEntry methodEntry) {
110 if (methodEntry.getParent().isJre()) {
111 return;
112 }
113
114 indexers.forEach(indexer -> indexer.indexMethod(methodEntry));
115
116 if (!methodEntry.isConstructor()) {
117 methodImplementations.put(methodEntry.getParent().getFullName(), methodEntry);
118 }
119 }
120
121 @Override
122 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
123 if (callerEntry.getParent().isJre()) {
124 return;
125 }
126
127 indexers.forEach(indexer -> indexer.indexMethodReference(callerEntry, referencedEntry, targetType));
128 }
129
130 @Override
131 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
132 if (callerEntry.getParent().isJre()) {
133 return;
134 }
135
136 indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry, targetType));
137 }
138
139 @Override
140 public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
141 if (callerEntry.getParent().isJre()) {
142 return;
143 }
144
145 indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda, targetType));
146 }
147
148 public EntryIndex getEntryIndex() {
149 return entryIndex;
150 }
151
152 public InheritanceIndex getInheritanceIndex() {
153 return this.inheritanceIndex;
154 }
155
156 public ReferenceIndex getReferenceIndex() {
157 return referenceIndex;
158 }
159
160 public BridgeMethodIndex getBridgeMethodIndex() {
161 return bridgeMethodIndex;
162 }
163
164 public PackageVisibilityIndex getPackageVisibilityIndex() {
165 return packageVisibilityIndex;
166 }
167
168 public EntryResolver getEntryResolver() {
169 return entryResolver;
170 }
171}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java
deleted file mode 100644
index f17e7c9..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java
+++ /dev/null
@@ -1,28 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import cuchaz.enigma.analysis.ReferenceTargetType;
4import cuchaz.enigma.translation.representation.Lambda;
5import cuchaz.enigma.translation.representation.entry.*;
6
7public interface JarIndexer {
8 default void indexClass(ClassDefEntry classEntry) {
9 }
10
11 default void indexField(FieldDefEntry fieldEntry) {
12 }
13
14 default void indexMethod(MethodDefEntry methodEntry) {
15 }
16
17 default void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
18 }
19
20 default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
21 }
22
23 default void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
24 }
25
26 default void processIndex(JarIndex index) {
27 }
28}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java
deleted file mode 100644
index 63eb730..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java
+++ /dev/null
@@ -1,147 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Lists;
5import com.google.common.collect.Maps;
6import com.google.common.collect.Sets;
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.analysis.ReferenceTargetType;
9import cuchaz.enigma.translation.representation.AccessFlags;
10import cuchaz.enigma.translation.representation.entry.*;
11
12import java.util.*;
13
14public class PackageVisibilityIndex implements JarIndexer {
15 private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) {
16 if (entryAcc.isPublic()) {
17 return false;
18 }
19
20 if (entryAcc.isProtected()) {
21 ClassEntry contextClass = ref.context.getContainingClass();
22 ClassEntry referencedClass = ref.entry.getContainingClass();
23
24 if (!inheritanceIndex.getAncestors(contextClass).contains(referencedClass)) {
25 return true; // access to protected member not in superclass
26 }
27
28 if (ref.targetType.getKind() == ReferenceTargetType.Kind.NONE) {
29 return false; // access to superclass or static superclass member
30 }
31
32 // access to instance member only valid if target's class assignable to context class
33 return !(ref.targetType.getKind() == ReferenceTargetType.Kind.UNINITIALIZED ||
34 ((ReferenceTargetType.ClassType) ref.targetType).getEntry().equals(contextClass) ||
35 inheritanceIndex.getAncestors(((ReferenceTargetType.ClassType) ref.targetType).getEntry()).contains(contextClass));
36 }
37
38 return true;
39 }
40
41 private final HashMultimap<ClassEntry, ClassEntry> connections = HashMultimap.create();
42 private final List<Set<ClassEntry>> partitions = Lists.newArrayList();
43 private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap();
44
45 private void addConnection(ClassEntry classA, ClassEntry classB) {
46 if (classA != classB) {
47 connections.put(classA, classB);
48 connections.put(classB, classA);
49 }
50 }
51
52 private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) {
53 for (ClassEntry connected : connections.get(member)) {
54 if (unassignedClasses.remove(connected)) {
55 partition.add(connected);
56 buildPartition(unassignedClasses, partition, connected);
57 }
58 }
59 }
60
61 private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) {
62 for (FieldEntry entry : entryIndex.getFields()) {
63 AccessFlags entryAcc = entryIndex.getFieldAccess(entry);
64 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
65 for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) {
66 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
67 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
68 }
69 }
70 }
71 }
72
73 for (MethodEntry entry : entryIndex.getMethods()) {
74 AccessFlags entryAcc = entryIndex.getMethodAccess(entry);
75 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
76 for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) {
77 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
78 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
79 }
80 }
81 }
82 }
83
84 for (ClassEntry entry : entryIndex.getClasses()) {
85 AccessFlags entryAcc = entryIndex.getClassAccess(entry);
86 if (!entryAcc.isPublic() && !entryAcc.isPrivate()) {
87 for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) {
88 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
89 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
90 }
91 }
92
93 for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) {
94 if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) {
95 addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass());
96 }
97 }
98 }
99
100 for (ClassEntry parent : inheritanceIndex.getParents(entry)) {
101 AccessFlags parentAcc = entryIndex.getClassAccess(parent);
102 if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) {
103 addConnection(entry, parent);
104 }
105 }
106
107 ClassEntry outerClass = entry.getOuterClass();
108 if (outerClass != null) {
109 addConnection(entry, outerClass);
110 }
111 }
112 }
113
114 private void addPartitions(EntryIndex entryIndex) {
115 Set<ClassEntry> unassignedClasses = Sets.newHashSet(entryIndex.getClasses());
116 while (!unassignedClasses.isEmpty()) {
117 Iterator<ClassEntry> iterator = unassignedClasses.iterator();
118 ClassEntry initialEntry = iterator.next();
119 iterator.remove();
120
121 HashSet<ClassEntry> partition = Sets.newHashSet();
122 partition.add(initialEntry);
123 buildPartition(unassignedClasses, partition, initialEntry);
124 partitions.add(partition);
125 for (ClassEntry entry : partition) {
126 classPartitions.put(entry, partition);
127 }
128 }
129 }
130
131 public Collection<Set<ClassEntry>> getPartitions() {
132 return partitions;
133 }
134
135 public Set<ClassEntry> getPartition(ClassEntry classEntry) {
136 return classPartitions.get(classEntry);
137 }
138
139 @Override
140 public void processIndex(JarIndex index) {
141 EntryIndex entryIndex = index.getEntryIndex();
142 ReferenceIndex referenceIndex = index.getReferenceIndex();
143 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
144 addConnections(entryIndex, referenceIndex, inheritanceIndex);
145 addPartitions(entryIndex);
146 }
147}
diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java
deleted file mode 100644
index b6797c2..0000000
--- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java
+++ /dev/null
@@ -1,148 +0,0 @@
1package cuchaz.enigma.analysis.index;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Multimap;
5import cuchaz.enigma.analysis.EntryReference;
6import cuchaz.enigma.analysis.ReferenceTargetType;
7import cuchaz.enigma.translation.mapping.ResolutionStrategy;
8import cuchaz.enigma.translation.representation.Lambda;
9import cuchaz.enigma.translation.representation.MethodDescriptor;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.*;
12
13import java.util.Collection;
14import java.util.Map;
15
16public class ReferenceIndex implements JarIndexer {
17 private Multimap<MethodEntry, MethodEntry> methodReferences = HashMultimap.create();
18
19 private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create();
20 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create();
21 private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create();
22 private Multimap<ClassEntry, EntryReference<ClassEntry, FieldDefEntry>> fieldTypeReferences = HashMultimap.create();
23 private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodTypeReferences = HashMultimap.create();
24
25 @Override
26 public void indexMethod(MethodDefEntry methodEntry) {
27 indexMethodDescriptor(methodEntry, methodEntry.getDesc());
28 }
29
30 private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) {
31 for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) {
32 indexMethodTypeDescriptor(entry, typeDescriptor);
33 }
34 indexMethodTypeDescriptor(entry, descriptor.getReturnDesc());
35 }
36
37 private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) {
38 if (typeDescriptor.isType()) {
39 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
40 methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method));
41 } else if (typeDescriptor.isArray()) {
42 indexMethodTypeDescriptor(method, typeDescriptor.getArrayType());
43 }
44 }
45
46 @Override
47 public void indexField(FieldDefEntry fieldEntry) {
48 indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc());
49 }
50
51 private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) {
52 if (typeDescriptor.isType()) {
53 ClassEntry referencedClass = typeDescriptor.getTypeEntry();
54 fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field));
55 } else if (typeDescriptor.isArray()) {
56 indexFieldTypeDescriptor(field, typeDescriptor.getArrayType());
57 }
58 }
59
60 @Override
61 public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry, ReferenceTargetType targetType) {
62 referencesToMethods.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
63 methodReferences.put(callerEntry, referencedEntry);
64
65 if (referencedEntry.isConstructor()) {
66 ClassEntry referencedClass = referencedEntry.getParent();
67 referencesToClasses.put(referencedClass, new EntryReference<>(referencedClass, referencedEntry.getName(), callerEntry, targetType));
68 }
69 }
70
71 @Override
72 public void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry, ReferenceTargetType targetType) {
73 referencesToFields.put(referencedEntry, new EntryReference<>(referencedEntry, referencedEntry.getName(), callerEntry, targetType));
74 }
75
76 @Override
77 public void indexLambda(MethodDefEntry callerEntry, Lambda lambda, ReferenceTargetType targetType) {
78 if (lambda.getImplMethod() instanceof MethodEntry) {
79 indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod(), targetType);
80 } else {
81 indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod(), targetType);
82 }
83
84 indexMethodDescriptor(callerEntry, lambda.getInvokedType());
85 indexMethodDescriptor(callerEntry, lambda.getSamMethodType());
86 indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType());
87 }
88
89 @Override
90 public void processIndex(JarIndex index) {
91 methodReferences = remapReferences(index, methodReferences);
92 referencesToMethods = remapReferencesTo(index, referencesToMethods);
93 referencesToClasses = remapReferencesTo(index, referencesToClasses);
94 referencesToFields = remapReferencesTo(index, referencesToFields);
95 fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences);
96 methodTypeReferences = remapReferencesTo(index, methodTypeReferences);
97 }
98
99 private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) {
100 final int keySetSize = multimap.keySet().size();
101 Multimap<K, V> resolved = HashMultimap.create(multimap.keySet().size(), keySetSize == 0 ? 0 : multimap.size() / keySetSize);
102 for (Map.Entry<K, V> entry : multimap.entries()) {
103 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue()));
104 }
105 return resolved;
106 }
107
108 private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) {
109 final int keySetSize = multimap.keySet().size();
110 Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize);
111 for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) {
112 resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue()));
113 }
114 return resolved;
115 }
116
117 private <E extends Entry<?>> E remap(JarIndex index, E entry) {
118 return index.getEntryResolver().resolveFirstEntry(entry, ResolutionStrategy.RESOLVE_CLOSEST);
119 }
120
121 private <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> remap(JarIndex index, EntryReference<E, C> reference) {
122 return index.getEntryResolver().resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
123 }
124
125 public Collection<MethodEntry> getMethodsReferencedBy(MethodEntry entry) {
126 return methodReferences.get(entry);
127 }
128
129 public Collection<EntryReference<FieldEntry, MethodDefEntry>> getReferencesToField(FieldEntry entry) {
130 return referencesToFields.get(entry);
131 }
132
133 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getReferencesToClass(ClassEntry entry) {
134 return referencesToClasses.get(entry);
135 }
136
137 public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) {
138 return referencesToMethods.get(entry);
139 }
140
141 public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) {
142 return fieldTypeReferences.get(entry);
143 }
144
145 public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) {
146 return methodTypeReferences.get(entry);
147 }
148}
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java b/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
deleted file mode 100644
index bdd6015..0000000
--- a/src/main/java/cuchaz/enigma/api/EnigmaPlugin.java
+++ /dev/null
@@ -1,5 +0,0 @@
1package cuchaz.enigma.api;
2
3public interface EnigmaPlugin {
4 void init(EnigmaPluginContext ctx);
5}
diff --git a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java b/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
deleted file mode 100644
index a59051a..0000000
--- a/src/main/java/cuchaz/enigma/api/EnigmaPluginContext.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.api;
2
3import cuchaz.enigma.api.service.EnigmaService;
4import cuchaz.enigma.api.service.EnigmaServiceFactory;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7public interface EnigmaPluginContext {
8 <T extends EnigmaService> void registerService(String id, EnigmaServiceType<T> serviceType, EnigmaServiceFactory<T> factory);
9}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java b/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
deleted file mode 100644
index 526dda7..0000000
--- a/src/main/java/cuchaz/enigma/api/service/EnigmaService.java
+++ /dev/null
@@ -1,4 +0,0 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaService {
4}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
deleted file mode 100644
index 9e433fb..0000000
--- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceContext.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.api.service;
2
3import java.util.Optional;
4
5public interface EnigmaServiceContext<T extends EnigmaService> {
6 static <T extends EnigmaService> EnigmaServiceContext<T> empty() {
7 return key -> Optional.empty();
8 }
9
10 Optional<String> getArgument(String key);
11}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
deleted file mode 100644
index 7c10ac2..0000000
--- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceFactory.java
+++ /dev/null
@@ -1,5 +0,0 @@
1package cuchaz.enigma.api.service;
2
3public interface EnigmaServiceFactory<T extends EnigmaService> {
4 T create(EnigmaServiceContext<T> ctx);
5}
diff --git a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java b/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
deleted file mode 100644
index 358828f..0000000
--- a/src/main/java/cuchaz/enigma/api/service/EnigmaServiceType.java
+++ /dev/null
@@ -1,29 +0,0 @@
1package cuchaz.enigma.api.service;
2
3public final class EnigmaServiceType<T extends EnigmaService> {
4 public final String key;
5
6 private EnigmaServiceType(String key) {
7 this.key = key;
8 }
9
10 public static <T extends EnigmaService> EnigmaServiceType<T> create(String key) {
11 return new EnigmaServiceType<>(key);
12 }
13
14 @Override
15 public int hashCode() {
16 return key.hashCode();
17 }
18
19 @Override
20 public boolean equals(Object obj) {
21 if (obj == this) return true;
22
23 if (obj instanceof EnigmaServiceType) {
24 return ((EnigmaServiceType) obj).key.equals(key);
25 }
26
27 return false;
28 }
29}
diff --git a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
deleted file mode 100644
index 0cda199..0000000
--- a/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.analysis.ClassCache;
4import cuchaz.enigma.analysis.index.JarIndex;
5
6public interface JarIndexerService extends EnigmaService {
7 EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer");
8
9 void acceptJar(ClassCache classCache, JarIndex jarIndex);
10}
diff --git a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java b/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
deleted file mode 100644
index 4c357db..0000000
--- a/src/main/java/cuchaz/enigma/api/service/NameProposalService.java
+++ /dev/null
@@ -1,12 +0,0 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4import cuchaz.enigma.translation.representation.entry.Entry;
5
6import java.util.Optional;
7
8public interface NameProposalService extends EnigmaService {
9 EnigmaServiceType<NameProposalService> TYPE = EnigmaServiceType.create("name_proposal");
10
11 Optional<String> proposeName(Entry<?> obfEntry, EntryRemapper remapper);
12}
diff --git a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java b/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
deleted file mode 100644
index af0cf30..0000000
--- a/src/main/java/cuchaz/enigma/api/service/ObfuscationTestService.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.api.service;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5public interface ObfuscationTestService extends EnigmaService {
6 EnigmaServiceType<ObfuscationTestService> TYPE = EnigmaServiceType.create("obfuscation_test");
7
8 boolean testDeobfuscated(Entry<?> entry);
9}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java b/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java
deleted file mode 100644
index 1a2b47f..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/AsmObjectTranslator.java
+++ /dev/null
@@ -1,46 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.TypeDescriptor;
6import cuchaz.enigma.translation.representation.entry.ClassEntry;
7import cuchaz.enigma.translation.representation.entry.MethodEntry;
8import org.objectweb.asm.Handle;
9import org.objectweb.asm.Type;
10
11public class AsmObjectTranslator {
12 public static Type translateType(Translator translator, Type type) {
13 String descString = type.getDescriptor();
14 switch (type.getSort()) {
15 case Type.OBJECT: {
16 ClassEntry classEntry = new ClassEntry(type.getInternalName());
17 return Type.getObjectType(translator.translate(classEntry).getFullName());
18 }
19 case Type.ARRAY: {
20 TypeDescriptor descriptor = new TypeDescriptor(descString);
21 return Type.getType(translator.translate(descriptor).toString());
22 }
23 case Type.METHOD: {
24 MethodDescriptor descriptor = new MethodDescriptor(descString);
25 return Type.getMethodType(translator.translate(descriptor).toString());
26 }
27 }
28 return type;
29 }
30
31 public static Handle translateHandle(Translator translator, Handle handle) {
32 MethodEntry entry = new MethodEntry(new ClassEntry(handle.getOwner()), handle.getName(), new MethodDescriptor(handle.getDesc()));
33 MethodEntry translatedMethod = translator.translate(entry);
34 ClassEntry ownerClass = translatedMethod.getParent();
35 return new Handle(handle.getTag(), ownerClass.getFullName(), translatedMethod.getName(), translatedMethod.getDesc().toString(), handle.isInterface());
36 }
37
38 public static Object translateValue(Translator translator, Object value) {
39 if (value instanceof Type) {
40 return translateType(translator, (Type) value);
41 } else if (value instanceof Handle) {
42 return translateHandle(translator, (Handle) value);
43 }
44 return value;
45 }
46}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
deleted file mode 100644
index cfd8fbe..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/LocalVariableFixVisitor.java
+++ /dev/null
@@ -1,126 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import com.google.common.base.CharMatcher;
4import cuchaz.enigma.translation.LocalNameGenerator;
5import cuchaz.enigma.translation.representation.TypeDescriptor;
6import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
7import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
8import org.objectweb.asm.ClassVisitor;
9import org.objectweb.asm.Label;
10import org.objectweb.asm.MethodVisitor;
11import org.objectweb.asm.Opcodes;
12
13import java.util.HashMap;
14import java.util.List;
15import java.util.Map;
16
17public class LocalVariableFixVisitor extends ClassVisitor {
18 private ClassDefEntry ownerEntry;
19
20 public LocalVariableFixVisitor(int api, ClassVisitor visitor) {
21 super(api, visitor);
22 }
23
24 @Override
25 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
26 ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
27 super.visit(version, access, name, signature, superName, interfaces);
28 }
29
30 @Override
31 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
32 MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature);
33 return new Method(api, methodEntry, super.visitMethod(access, name, descriptor, signature, exceptions));
34 }
35
36 private class Method extends MethodVisitor {
37 private final MethodDefEntry methodEntry;
38 private final Map<Integer, String> parameterNames = new HashMap<>();
39 private final Map<Integer, Integer> parameterIndices = new HashMap<>();
40 private boolean hasParameterTable;
41 private int parameterIndex = 0;
42
43 Method(int api, MethodDefEntry methodEntry, MethodVisitor visitor) {
44 super(api, visitor);
45 this.methodEntry = methodEntry;
46
47 int lvIndex = methodEntry.getAccess().isStatic() ? 0 : 1;
48 List<TypeDescriptor> parameters = methodEntry.getDesc().getArgumentDescs();
49 for (int parameterIndex = 0; parameterIndex < parameters.size(); parameterIndex++) {
50 TypeDescriptor param = parameters.get(parameterIndex);
51 parameterIndices.put(lvIndex, parameterIndex);
52 lvIndex += param.getSize();
53 }
54 }
55
56 @Override
57 public void visitParameter(String name, int access) {
58 hasParameterTable = true;
59 super.visitParameter(fixParameterName(parameterIndex, name), fixParameterAccess(parameterIndex, access));
60 parameterIndex++;
61 }
62
63 @Override
64 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
65 if (index == 0 && !methodEntry.getAccess().isStatic()) {
66 name = "this";
67 } else if (parameterIndices.containsKey(index)) {
68 name = fixParameterName(parameterIndices.get(index), name);
69 } else if (isInvalidName(name)) {
70 name = LocalNameGenerator.generateLocalVariableName(index, new TypeDescriptor(desc));
71 }
72
73 super.visitLocalVariable(name, desc, signature, start, end, index);
74 }
75
76 private boolean isInvalidName(String name) {
77 return name == null || name.isEmpty() || !CharMatcher.ascii().matchesAllOf(name);
78 }
79
80 @Override
81 public void visitEnd() {
82 if (!hasParameterTable) {
83 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
84 for (int argumentIndex = 0; argumentIndex < arguments.size(); argumentIndex++) {
85 super.visitParameter(fixParameterName(argumentIndex, null), fixParameterAccess(argumentIndex, 0));
86 }
87 }
88
89 super.visitEnd();
90 }
91
92 private String fixParameterName(int index, String name) {
93 if (parameterNames.get(index) != null) {
94 return parameterNames.get(index); // to make sure that LVT names are consistent with parameter table names
95 }
96
97 if (isInvalidName(name)) {
98 List<TypeDescriptor> arguments = methodEntry.getDesc().getArgumentDescs();
99 name = LocalNameGenerator.generateArgumentName(index, arguments.get(index), arguments);
100 }
101
102 if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
103 name = "name";
104 }
105
106 if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
107 name = "ordinal";
108 }
109
110 parameterNames.put(index, name);
111 return name;
112 }
113
114 private int fixParameterAccess(int index, int access) {
115 if (index == 0 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
116 access |= Opcodes.ACC_SYNTHETIC;
117 }
118
119 if (index == 1 && ownerEntry.getAccess().isEnum() && methodEntry.getName().equals("<init>")) {
120 access |= Opcodes.ACC_SYNTHETIC;
121 }
122
123 return access;
124 }
125 }
126}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java
deleted file mode 100644
index 2b750ea..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/SourceFixVisitor.java
+++ /dev/null
@@ -1,39 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.analysis.index.BridgeMethodIndex;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
6import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
7import org.objectweb.asm.ClassVisitor;
8import org.objectweb.asm.MethodVisitor;
9import org.objectweb.asm.Opcodes;
10
11public class SourceFixVisitor extends ClassVisitor {
12 private final JarIndex index;
13 private ClassDefEntry ownerEntry;
14
15 public SourceFixVisitor(int api, ClassVisitor visitor, JarIndex index) {
16 super(api, visitor);
17 this.index = index;
18 }
19
20 @Override
21 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
22 ownerEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
23 super.visit(version, access, name, signature, superName, interfaces);
24 }
25
26 @Override
27 public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
28 MethodDefEntry methodEntry = MethodDefEntry.parse(ownerEntry, access, name, descriptor, signature);
29
30 BridgeMethodIndex bridgeIndex = index.getBridgeMethodIndex();
31 if (bridgeIndex.isBridgeMethod(methodEntry)) {
32 access |= Opcodes.ACC_BRIDGE;
33 } else if (bridgeIndex.isSpecializedMethod(methodEntry)) {
34 name = bridgeIndex.getBridgeFromSpecialized(methodEntry).getName();
35 }
36
37 return super.visitMethod(access, name, descriptor, signature, exceptions);
38 }
39}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java
deleted file mode 100644
index cb843ad..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationAnnotationVisitor.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.translation.representation.entry.FieldEntry;
7import org.objectweb.asm.AnnotationVisitor;
8
9public class TranslationAnnotationVisitor extends AnnotationVisitor {
10 private final Translator translator;
11 private final ClassEntry annotationEntry;
12
13 public TranslationAnnotationVisitor(Translator translator, ClassEntry annotationEntry, int api, AnnotationVisitor av) {
14 super(api, av);
15 this.translator = translator;
16 this.annotationEntry = annotationEntry;
17 }
18
19 @Override
20 public void visit(String name, Object value) {
21 super.visit(name, AsmObjectTranslator.translateValue(translator, value));
22 }
23
24 @Override
25 public AnnotationVisitor visitArray(String name) {
26 return new TranslationAnnotationVisitor(translator, annotationEntry, api, super.visitArray(name));
27 }
28
29 @Override
30 public AnnotationVisitor visitAnnotation(String name, String desc) {
31 TypeDescriptor type = new TypeDescriptor(desc);
32 if (name != null) {
33 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
34 return super.visitAnnotation(annotationField.getName(), annotationField.getDesc().toString());
35 } else {
36 return super.visitAnnotation(null, translator.translate(type).toString());
37 }
38 }
39
40 @Override
41 public void visitEnum(String name, String desc, String value) {
42 TypeDescriptor type = new TypeDescriptor(desc);
43 FieldEntry enumField = translator.translate(new FieldEntry(type.getTypeEntry(), value, type));
44 if (name != null) {
45 FieldEntry annotationField = translator.translate(new FieldEntry(annotationEntry, name, type));
46 super.visitEnum(annotationField.getName(), annotationField.getDesc().toString(), enumField.getName());
47 } else {
48 super.visitEnum(null, translator.translate(type).toString(), enumField.getName());
49 }
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
deleted file mode 100644
index e4c41d3..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationClassVisitor.java
+++ /dev/null
@@ -1,102 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.bytecode.translators;
13
14import cuchaz.enigma.translation.Translator;
15import cuchaz.enigma.translation.representation.MethodDescriptor;
16import cuchaz.enigma.translation.representation.TypeDescriptor;
17import cuchaz.enigma.translation.representation.entry.*;
18import org.objectweb.asm.*;
19
20import java.util.Arrays;
21
22public class TranslationClassVisitor extends ClassVisitor {
23 private final Translator translator;
24
25 private ClassDefEntry obfClassEntry;
26
27 public TranslationClassVisitor(Translator translator, int api, ClassVisitor cv) {
28 super(api, cv);
29 this.translator = translator;
30 }
31
32 @Override
33 public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
34 obfClassEntry = ClassDefEntry.parse(access, name, signature, superName, interfaces);
35
36 ClassDefEntry translatedEntry = translator.translate(obfClassEntry);
37 String translatedSuper = translatedEntry.getSuperClass() != null ? translatedEntry.getSuperClass().getFullName() : null;
38 String[] translatedInterfaces = Arrays.stream(translatedEntry.getInterfaces()).map(ClassEntry::getFullName).toArray(String[]::new);
39
40 super.visit(version, translatedEntry.getAccess().getFlags(), translatedEntry.getFullName(), translatedEntry.getSignature().toString(), translatedSuper, translatedInterfaces);
41 }
42
43 @Override
44 public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
45 FieldDefEntry entry = FieldDefEntry.parse(obfClassEntry, access, name, desc, signature);
46 FieldDefEntry translatedEntry = translator.translate(entry);
47 FieldVisitor fv = super.visitField(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), value);
48 return new TranslationFieldVisitor(translator, translatedEntry, api, fv);
49 }
50
51 @Override
52 public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
53 MethodDefEntry entry = MethodDefEntry.parse(obfClassEntry, access, name, desc, signature);
54 MethodDefEntry translatedEntry = translator.translate(entry);
55 String[] translatedExceptions = new String[exceptions.length];
56 for (int i = 0; i < exceptions.length; i++) {
57 translatedExceptions[i] = translator.translate(new ClassEntry(exceptions[i])).getFullName();
58 }
59 MethodVisitor mv = super.visitMethod(translatedEntry.getAccess().getFlags(), translatedEntry.getName(), translatedEntry.getDesc().toString(), translatedEntry.getSignature().toString(), translatedExceptions);
60 return new TranslationMethodVisitor(translator, obfClassEntry, entry, api, mv);
61 }
62
63 @Override
64 public void visitInnerClass(String name, String outerName, String innerName, int access) {
65 ClassDefEntry classEntry = ClassDefEntry.parse(access, name, obfClassEntry.getSignature().toString(), null, new String[0]);
66 ClassDefEntry translatedEntry = translator.translate(classEntry);
67 ClassEntry translatedOuterClass = translatedEntry.getOuterClass();
68 if (translatedOuterClass == null) {
69 throw new IllegalStateException("Translated inner class did not have outer class");
70 }
71
72 // Anonymous classes do not specify an outer or inner name. As we do not translate from the given parameter, ignore if the input is null
73 String translatedName = translatedEntry.getFullName();
74 String translatedOuterName = outerName != null ? translatedOuterClass.getFullName() : null;
75 String translatedInnerName = innerName != null ? translatedEntry.getName() : null;
76 super.visitInnerClass(translatedName, translatedOuterName, translatedInnerName, translatedEntry.getAccess().getFlags());
77 }
78
79 @Override
80 public void visitOuterClass(String owner, String name, String desc) {
81 if (desc != null) {
82 MethodEntry translatedEntry = translator.translate(new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc)));
83 super.visitOuterClass(translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
84 } else {
85 super.visitOuterClass(owner, name, desc);
86 }
87 }
88
89 @Override
90 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
91 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
92 AnnotationVisitor av = super.visitAnnotation(translatedDesc.toString(), visible);
93 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
94 }
95
96 @Override
97 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
98 TypeDescriptor translatedDesc = translator.translate(new TypeDescriptor(desc));
99 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, translatedDesc.toString(), visible);
100 return new TranslationAnnotationVisitor(translator, translatedDesc.getTypeEntry(), api, av);
101 }
102}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java
deleted file mode 100644
index 28fc199..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationFieldVisitor.java
+++ /dev/null
@@ -1,33 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
6import org.objectweb.asm.AnnotationVisitor;
7import org.objectweb.asm.FieldVisitor;
8import org.objectweb.asm.TypePath;
9
10public class TranslationFieldVisitor extends FieldVisitor {
11 private final FieldDefEntry fieldEntry;
12 private final Translator translator;
13
14 public TranslationFieldVisitor(Translator translator, FieldDefEntry fieldEntry, int api, FieldVisitor fv) {
15 super(api, fv);
16 this.translator = translator;
17 this.fieldEntry = fieldEntry;
18 }
19
20 @Override
21 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
22 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
23 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
24 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
25 }
26
27 @Override
28 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
29 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
30 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
31 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
deleted file mode 100644
index a82df1b..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationMethodVisitor.java
+++ /dev/null
@@ -1,145 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.representation.MethodDescriptor;
5import cuchaz.enigma.translation.representation.Signature;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.*;
8import org.objectweb.asm.*;
9
10public class TranslationMethodVisitor extends MethodVisitor {
11 private final MethodDefEntry methodEntry;
12 private final Translator translator;
13
14 private int parameterIndex = 0;
15 private int parameterLvIndex;
16
17 public TranslationMethodVisitor(Translator translator, ClassDefEntry ownerEntry, MethodDefEntry methodEntry, int api, MethodVisitor mv) {
18 super(api, mv);
19 this.translator = translator;
20 this.methodEntry = methodEntry;
21
22 parameterLvIndex = methodEntry.getAccess().isStatic() ? 0 : 1;
23 }
24
25 @Override
26 public void visitParameter(String name, int access) {
27 name = translateVariableName(parameterLvIndex, name);
28 parameterLvIndex += methodEntry.getDesc().getArgumentDescs().get(parameterIndex++).getSize();
29
30 super.visitParameter(name, access);
31 }
32
33 @Override
34 public void visitFieldInsn(int opcode, String owner, String name, String desc) {
35 FieldEntry entry = new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc));
36 FieldEntry translatedEntry = translator.translate(entry);
37 super.visitFieldInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString());
38 }
39
40 @Override
41 public void visitMethodInsn(int opcode, String owner, String name, String desc, boolean itf) {
42 MethodEntry entry = new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc));
43 MethodEntry translatedEntry = translator.translate(entry);
44 super.visitMethodInsn(opcode, translatedEntry.getParent().getFullName(), translatedEntry.getName(), translatedEntry.getDesc().toString(), itf);
45 }
46
47 @Override
48 public void visitFrame(int type, int localCount, Object[] locals, int stackCount, Object[] stack) {
49 Object[] translatedLocals = this.getTranslatedFrame(locals, localCount);
50 Object[] translatedStack = this.getTranslatedFrame(stack, stackCount);
51 super.visitFrame(type, localCount, translatedLocals, stackCount, translatedStack);
52 }
53
54 private Object[] getTranslatedFrame(Object[] array, int count) {
55 if (array == null) {
56 return null;
57 }
58 for (int i = 0; i < count; i++) {
59 Object object = array[i];
60 if (object instanceof String) {
61 String type = (String) object;
62 array[i] = translator.translate(new ClassEntry(type)).getFullName();
63 }
64 }
65 return array;
66 }
67
68 @Override
69 public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
70 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
71 AnnotationVisitor av = super.visitAnnotation(typeDesc.toString(), visible);
72 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
73 }
74
75 @Override
76 public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
77 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
78 AnnotationVisitor av = super.visitParameterAnnotation(parameter, typeDesc.toString(), visible);
79 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
80 }
81
82 @Override
83 public AnnotationVisitor visitTypeAnnotation(int typeRef, TypePath typePath, String desc, boolean visible) {
84 TypeDescriptor typeDesc = translator.translate(new TypeDescriptor(desc));
85 AnnotationVisitor av = super.visitTypeAnnotation(typeRef, typePath, typeDesc.toString(), visible);
86 return new TranslationAnnotationVisitor(translator, typeDesc.getTypeEntry(), api, av);
87 }
88
89 @Override
90 public void visitTypeInsn(int opcode, String type) {
91 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
92 super.visitTypeInsn(opcode, translatedEntry.getFullName());
93 }
94
95 @Override
96 public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) {
97 MethodDescriptor translatedMethodDesc = translator.translate(new MethodDescriptor(desc));
98 Object[] translatedBsmArgs = new Object[bsmArgs.length];
99 for (int i = 0; i < bsmArgs.length; i++) {
100 translatedBsmArgs[i] = AsmObjectTranslator.translateValue(translator, bsmArgs[i]);
101 }
102 super.visitInvokeDynamicInsn(name, translatedMethodDesc.toString(), AsmObjectTranslator.translateHandle(translator, bsm), translatedBsmArgs);
103 }
104
105 @Override
106 public void visitLdcInsn(Object cst) {
107 super.visitLdcInsn(AsmObjectTranslator.translateValue(translator, cst));
108 }
109
110 @Override
111 public void visitMultiANewArrayInsn(String desc, int dims) {
112 super.visitMultiANewArrayInsn(translator.translate(new TypeDescriptor(desc)).toString(), dims);
113 }
114
115 @Override
116 public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
117 if (type != null) {
118 ClassEntry translatedEntry = translator.translate(new ClassEntry(type));
119 super.visitTryCatchBlock(start, end, handler, translatedEntry.getFullName());
120 } else {
121 super.visitTryCatchBlock(start, end, handler, type);
122 }
123 }
124
125 @Override
126 public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
127 signature = translator.translate(Signature.createTypedSignature(signature)).toString();
128 name = translateVariableName(index, name);
129 desc = translator.translate(new TypeDescriptor(desc)).toString();
130
131 super.visitLocalVariable(name, desc, signature, start, end, index);
132 }
133
134 private String translateVariableName(int index, String name) {
135 LocalVariableEntry entry = new LocalVariableEntry(methodEntry, index, "", true,null);
136 LocalVariableEntry translatedEntry = translator.translate(entry);
137 String translatedName = translatedEntry.getName();
138
139 if (!translatedName.isEmpty()) {
140 return translatedName;
141 }
142
143 return name;
144 }
145}
diff --git a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java b/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java
deleted file mode 100644
index eebd650..0000000
--- a/src/main/java/cuchaz/enigma/bytecode/translators/TranslationSignatureVisitor.java
+++ /dev/null
@@ -1,129 +0,0 @@
1package cuchaz.enigma.bytecode.translators;
2
3import cuchaz.enigma.utils.Utils;
4import org.objectweb.asm.signature.SignatureVisitor;
5
6import java.util.Stack;
7import java.util.function.Function;
8
9public class TranslationSignatureVisitor extends SignatureVisitor {
10 private final Function<String, String> remapper;
11
12 private final SignatureVisitor sv;
13 private final Stack<String> classStack = new Stack<>();
14
15 public TranslationSignatureVisitor(Function<String, String> remapper, SignatureVisitor sv) {
16 super(Utils.ASM_VERSION);
17 this.remapper = remapper;
18 this.sv = sv;
19 }
20
21 @Override
22 public void visitClassType(String name) {
23 classStack.push(name);
24 String translatedEntry = this.remapper.apply(name);
25 this.sv.visitClassType(translatedEntry);
26 }
27
28 @Override
29 public void visitInnerClassType(String name) {
30 String lastClass = classStack.pop();
31 if (!name.startsWith(lastClass+"$")){//todo see if there's a way to base this on whether there were type params or not
32 name = lastClass+"$"+name;
33 }
34 String translatedEntry = this.remapper.apply(name);
35 if (translatedEntry.contains("/")){
36 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("/")+1);
37 }
38 if (translatedEntry.contains("$")){
39 translatedEntry = translatedEntry.substring(translatedEntry.lastIndexOf("$")+1);
40 }
41 this.sv.visitInnerClassType(translatedEntry);
42 }
43
44 @Override
45 public void visitFormalTypeParameter(String name) {
46 this.sv.visitFormalTypeParameter(name);
47 }
48
49 @Override
50 public void visitTypeVariable(String name) {
51 this.sv.visitTypeVariable(name);
52 }
53
54 @Override
55 public SignatureVisitor visitArrayType() {
56 this.sv.visitArrayType();
57 return this;
58 }
59
60 @Override
61 public void visitBaseType(char descriptor) {
62 this.sv.visitBaseType(descriptor);
63 }
64
65 @Override
66 public SignatureVisitor visitClassBound() {
67 this.sv.visitClassBound();
68 return this;
69 }
70
71 @Override
72 public SignatureVisitor visitExceptionType() {
73 this.sv.visitExceptionType();
74 return this;
75 }
76
77 @Override
78 public SignatureVisitor visitInterface() {
79 this.sv.visitInterface();
80 return this;
81 }
82
83 @Override
84 public SignatureVisitor visitInterfaceBound() {
85 this.sv.visitInterfaceBound();
86 return this;
87 }
88
89 @Override
90 public SignatureVisitor visitParameterType() {
91 this.sv.visitParameterType();
92 return this;
93 }
94
95 @Override
96 public SignatureVisitor visitReturnType() {
97 this.sv.visitReturnType();
98 return this;
99 }
100
101 @Override
102 public SignatureVisitor visitSuperclass() {
103 this.sv.visitSuperclass();
104 return this;
105 }
106
107 @Override
108 public void visitTypeArgument() {
109 this.sv.visitTypeArgument();
110 }
111
112 @Override
113 public SignatureVisitor visitTypeArgument(char wildcard) {
114 this.sv.visitTypeArgument(wildcard);
115 return this;
116 }
117
118 @Override
119 public void visitEnd() {
120 this.sv.visitEnd();
121 if (!classStack.empty())
122 classStack.pop();
123 }
124
125 @Override
126 public String toString() {
127 return this.sv.toString();
128 }
129}
diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
deleted file mode 100644
index 9d238e3..0000000
--- a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
+++ /dev/null
@@ -1,77 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.analysis.index.JarIndex;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.serde.MappingFormat;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12
13import java.nio.file.Path;
14import java.util.Set;
15import java.util.stream.Collectors;
16
17public class CheckMappingsCommand extends Command {
18
19 public CheckMappingsCommand() {
20 super("checkmappings");
21 }
22
23 @Override
24 public String getUsage() {
25 return "<in jar> <mappings file>";
26 }
27
28 @Override
29 public boolean isValidArgument(int length) {
30 return length == 2;
31 }
32
33 @Override
34 public void run(String... args) throws Exception {
35 Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath();
36 Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true));
37
38 Enigma enigma = Enigma.create();
39
40 System.out.println("Reading JAR...");
41
42 EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none());
43
44 System.out.println("Reading mappings...");
45
46 MappingFormat format = chooseEnigmaFormat(fileMappings);
47 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
48
49 EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.none(), saveParameters);
50 project.setMappings(mappings);
51
52 JarIndex idx = project.getJarIndex();
53
54 boolean error = false;
55
56 for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) {
57 long packages = partition.stream()
58 .map(project.getMapper()::deobfuscate)
59 .map(ClassEntry::getPackageName)
60 .distinct()
61 .count();
62 if (packages > 1) {
63 error = true;
64 System.err.println("ERROR: Must be in one package:\n" + partition.stream()
65 .map(project.getMapper()::deobfuscate)
66 .map(ClassEntry::toString)
67 .sorted()
68 .collect(Collectors.joining("\n"))
69 );
70 }
71 }
72
73 if (error) {
74 throw new IllegalStateException("Errors in package visibility detected, see SysErr above");
75 }
76 }
77}
diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java
deleted file mode 100644
index 09dd321..0000000
--- a/src/main/java/cuchaz/enigma/command/Command.java
+++ /dev/null
@@ -1,154 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.serde.MappingFormat;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10
11import java.io.File;
12import java.nio.file.Files;
13import java.nio.file.Path;
14import java.nio.file.Paths;
15
16import com.google.common.io.MoreFiles;
17
18public abstract class Command {
19 public final String name;
20
21 protected Command(String name) {
22 this.name = name;
23 }
24
25 public abstract String getUsage();
26
27 public abstract boolean isValidArgument(int length);
28
29 public abstract void run(String... args) throws Exception;
30
31 protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception {
32 ProgressListener progress = new ConsoleProgressListener();
33
34 Enigma enigma = Enigma.create();
35
36 System.out.println("Reading jar...");
37 EnigmaProject project = enigma.openJar(fileJarIn, progress);
38
39 if (fileMappings != null) {
40 System.out.println("Reading mappings...");
41
42 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
43 EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters);
44
45 project.setMappings(mappings);
46 }
47
48 return project;
49 }
50
51 protected static MappingFormat chooseEnigmaFormat(Path path) {
52 if (Files.isDirectory(path)) {
53 return MappingFormat.ENIGMA_DIRECTORY;
54 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) {
55 return MappingFormat.ENIGMA_ZIP;
56 } else {
57 return MappingFormat.ENIGMA_FILE;
58 }
59 }
60
61 protected static File getWritableFile(String path) {
62 if (path == null) {
63 return null;
64 }
65 File file = new File(path).getAbsoluteFile();
66 File dir = file.getParentFile();
67 if (dir == null) {
68 throw new IllegalArgumentException("Cannot write file: " + path);
69 }
70 // quick fix to avoid stupid stuff in Gradle code
71 if (!dir.isDirectory()) {
72 dir.mkdirs();
73 }
74 return file;
75 }
76
77 protected static File getWritableFolder(String path) {
78 if (path == null) {
79 return null;
80 }
81 File dir = new File(path).getAbsoluteFile();
82 if (!dir.exists()) {
83 throw new IllegalArgumentException("Cannot write to folder: " + dir);
84 }
85 return dir;
86 }
87
88 protected static File getReadableFile(String path) {
89 if (path == null) {
90 return null;
91 }
92 File file = new File(path).getAbsoluteFile();
93 if (!file.exists()) {
94 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
95 }
96 return file;
97 }
98
99 protected static Path getReadablePath(String path) {
100 if (path == null) {
101 return null;
102 }
103 Path file = Paths.get(path).toAbsolutePath();
104 if (!Files.exists(file)) {
105 throw new IllegalArgumentException("Cannot find file: " + file.toString());
106 }
107 return file;
108 }
109
110 protected static String getArg(String[] args, int i, String name, boolean required) {
111 if (i >= args.length) {
112 if (required) {
113 throw new IllegalArgumentException(name + " is required");
114 } else {
115 return null;
116 }
117 }
118 return args[i];
119 }
120
121 public static class ConsoleProgressListener implements ProgressListener {
122
123 private static final int ReportTime = 5000; // 5s
124
125 private int totalWork;
126 private long startTime;
127 private long lastReportTime;
128
129 @Override
130 public void init(int totalWork, String title) {
131 this.totalWork = totalWork;
132 this.startTime = System.currentTimeMillis();
133 this.lastReportTime = this.startTime;
134 System.out.println(title);
135 }
136
137 @Override
138 public void step(int numDone, String message) {
139 long now = System.currentTimeMillis();
140 boolean isLastUpdate = numDone == this.totalWork;
141 boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime;
142
143 if (shouldReport) {
144 int percent = numDone * 100 / this.totalWork;
145 System.out.println(String.format("\tProgress: %3d%%", percent));
146 this.lastReportTime = now;
147 }
148 if (isLastUpdate) {
149 double elapsedSeconds = (now - this.startTime) / 1000.0;
150 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
151 }
152 }
153 }
154}
diff --git a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java
deleted file mode 100644
index f57f1fa..0000000
--- a/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java
+++ /dev/null
@@ -1,41 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.throwables.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.IOException;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13
14public class ComposeMappingsCommand extends Command {
15 public ComposeMappingsCommand() {
16 super("compose-mappings");
17 }
18
19 @Override
20 public String getUsage() {
21 return "<left-format> <left> <right-format> <right> <result-format> <result> <keep-mode>";
22 }
23
24 @Override
25 public boolean isValidArgument(int length) {
26 return length == 7;
27 }
28
29 @Override
30 public void run(String... args) throws IOException, MappingParseException {
31 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
32
33 EntryTree<EntryMapping> left = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
34 EntryTree<EntryMapping> right = MappingCommandsUtil.read(args[2], Paths.get(args[3]), saveParameters);
35 EntryTree<EntryMapping> result = MappingCommandsUtil.compose(left, right, args[6].equals("left") || args[6].equals("both"), args[6].equals("right") || args[6].equals("both"));
36
37 Path output = Paths.get(args[5]);
38 Utils.delete(output);
39 MappingCommandsUtil.write(result, args[4], output, saveParameters);
40 }
41}
diff --git a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java
deleted file mode 100644
index 689df02..0000000
--- a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java
+++ /dev/null
@@ -1,39 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.throwables.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.IOException;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13
14public class ConvertMappingsCommand extends Command {
15 public ConvertMappingsCommand() {
16 super("convert-mappings");
17 }
18
19 @Override
20 public String getUsage() {
21 return "<source-format> <source> <result-format> <result>";
22 }
23
24 @Override
25 public boolean isValidArgument(int length) {
26 return length == 4;
27 }
28
29 @Override
30 public void run(String... args) throws IOException, MappingParseException {
31 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
32
33 EntryTree<EntryMapping> mappings = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
34
35 Path output = Paths.get(args[3]);
36 Utils.delete(output);
37 MappingCommandsUtil.write(mappings, args[2], output, saveParameters);
38 }
39}
diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java
deleted file mode 100644
index 3d15dac..0000000
--- a/src/main/java/cuchaz/enigma/command/DecompileCommand.java
+++ /dev/null
@@ -1,54 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.source.DecompilerService;
6import cuchaz.enigma.source.Decompilers;
7
8import java.lang.reflect.Field;
9import java.nio.file.Path;
10import java.util.Locale;
11
12public class DecompileCommand extends Command {
13
14 public DecompileCommand() {
15 super("decompile");
16 }
17
18 @Override
19 public String getUsage() {
20 return "<decompiler> <in jar> <out folder> [<mappings file>]";
21 }
22
23 @Override
24 public boolean isValidArgument(int length) {
25 return length == 2 || length == 3;
26 }
27
28 @Override
29 public void run(String... args) throws Exception {
30 String decompilerName = getArg(args, 1, "decompiler", true);
31 Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath();
32 Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath();
33 Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
34
35 DecompilerService decompilerService;
36
37 try {
38 Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT));
39 decompilerService = (DecompilerService) decompilerField.get(null);
40 } catch (NoSuchFieldException e) {
41 System.err.println("Decompiler not found.");
42 return;
43 }
44
45 EnigmaProject project = openProject(fileJarIn, fileMappings);
46
47 ProgressListener progress = new ConsoleProgressListener();
48
49 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
50 EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService);
51
52 source.write(fileJarOut, progress);
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
deleted file mode 100644
index b0d2a7d..0000000
--- a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
+++ /dev/null
@@ -1,37 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
5
6import java.nio.file.Path;
7
8public class DeobfuscateCommand extends Command {
9
10 public DeobfuscateCommand() {
11 super("deobfuscate");
12 }
13
14 @Override
15 public String getUsage() {
16 return "<in jar> <out jar> [<mappings file>]";
17 }
18
19 @Override
20 public boolean isValidArgument(int length) {
21 return length == 2 || length == 3;
22 }
23
24 @Override
25 public void run(String... args) throws Exception {
26 Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true));
27 Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath();
28 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false));
29
30 EnigmaProject project = openProject(fileJarIn, fileMappings);
31
32 ProgressListener progress = new ConsoleProgressListener();
33
34 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
35 jar.write(fileJarOut, progress);
36 }
37}
diff --git a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java
deleted file mode 100644
index cd11e2e..0000000
--- a/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.throwables.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.IOException;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13
14public class InvertMappingsCommand extends Command {
15 public InvertMappingsCommand() {
16 super("invert-mappings");
17 }
18
19 @Override
20 public String getUsage() {
21 return "<source-format> <source> <result-format> <result>";
22 }
23
24 @Override
25 public boolean isValidArgument(int length) {
26 return length == 4;
27 }
28
29 @Override
30 public void run(String... args) throws IOException, MappingParseException {
31 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
32
33 EntryTree<EntryMapping> source = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
34 EntryTree<EntryMapping> result = MappingCommandsUtil.invert(source);
35
36 Path output = Paths.get(args[3]);
37 Utils.delete(output);
38 MappingCommandsUtil.write(result, args[2], output, saveParameters);
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java
deleted file mode 100644
index eb8d5dc..0000000
--- a/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java
+++ /dev/null
@@ -1,69 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.analysis.ClassCache;
5import cuchaz.enigma.analysis.index.BridgeMethodIndex;
6import cuchaz.enigma.analysis.index.JarIndex;
7import cuchaz.enigma.throwables.MappingParseException;
8import cuchaz.enigma.translation.MappingTranslator;
9import cuchaz.enigma.translation.Translator;
10import cuchaz.enigma.translation.mapping.*;
11import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
13import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15import cuchaz.enigma.utils.Utils;
16
17import java.io.IOException;
18import java.nio.file.Path;
19import java.nio.file.Paths;
20import java.util.Map;
21
22public class MapSpecializedMethodsCommand extends Command {
23 public MapSpecializedMethodsCommand() {
24 super("map-specialized-methods");
25 }
26
27 @Override
28 public String getUsage() {
29 return "<jar> <source-format> <source> <result-format> <result>";
30 }
31
32 @Override
33 public boolean isValidArgument(int length) {
34 return length == 5;
35 }
36
37 @Override
38 public void run(String... args) throws IOException, MappingParseException {
39 run(Paths.get(args[0]), args[1], Paths.get(args[2]), args[3], Paths.get(args[4]));
40 }
41
42 public static void run(Path jar, String sourceFormat, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException {
43 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
44 EntryTree<EntryMapping> source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters);
45 EntryTree<EntryMapping> result = new HashEntryTree<>();
46 ClassCache classCache = ClassCache.of(jar);
47 JarIndex jarIndex = classCache.index(ProgressListener.none());
48 BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex();
49 Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver());
50
51 // Copy all non-specialized methods
52 for (EntryTreeNode<EntryMapping> node : source) {
53 if (!(node.getEntry() instanceof MethodEntry) || !bridgeMethodIndex.isSpecializedMethod((MethodEntry) node.getEntry())) {
54 result.insert(node.getEntry(), node.getValue());
55 }
56 }
57
58 // Add correct mappings for specialized methods
59 for (Map.Entry<MethodEntry, MethodEntry> entry : bridgeMethodIndex.getBridgeToSpecialized().entrySet()) {
60 MethodEntry bridge = entry.getKey();
61 MethodEntry specialized = entry.getValue();
62 String name = translator.translate(bridge).getName();
63 result.insert(specialized, new EntryMapping(name));
64 }
65
66 Utils.delete(output);
67 MappingCommandsUtil.write(result, resultFormat, output, saveParameters);
68 }
69}
diff --git a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java b/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java
deleted file mode 100644
index fc7afbc..0000000
--- a/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java
+++ /dev/null
@@ -1,148 +0,0 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.MappingTranslator;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.VoidEntryResolver;
10import cuchaz.enigma.translation.mapping.serde.*;
11import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
13import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18
19import java.io.IOException;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.HashSet;
23import java.util.Set;
24
25public final class MappingCommandsUtil {
26 private MappingCommandsUtil() {}
27
28 public static EntryTree<EntryMapping> invert(EntryTree<EntryMapping> mappings) {
29 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
30 EntryTree<EntryMapping> result = new HashEntryTree<>();
31
32 for (EntryTreeNode<EntryMapping> node : mappings) {
33 Entry<?> leftEntry = node.getEntry();
34 EntryMapping leftMapping = node.getValue();
35
36 if (!(leftEntry instanceof ClassEntry || leftEntry instanceof MethodEntry || leftEntry instanceof FieldEntry)) {
37 result.insert(translator.translate(leftEntry), leftMapping);
38 continue;
39 }
40
41 Entry<?> rightEntry = translator.translate(leftEntry);
42
43 result.insert(rightEntry, leftMapping == null ? null : new EntryMapping(leftEntry.getName())); // TODO: leftMapping.withName once javadoc PR is merged
44 }
45
46 return result;
47 }
48
49 public static EntryTree<EntryMapping> compose(EntryTree<EntryMapping> left, EntryTree<EntryMapping> right, boolean keepLeftOnly, boolean keepRightOnly) {
50 Translator leftTranslator = new MappingTranslator(left, VoidEntryResolver.INSTANCE);
51 EntryTree<EntryMapping> result = new HashEntryTree<>();
52 Set<Entry<?>> addedMappings = new HashSet<>();
53
54 for (EntryTreeNode<EntryMapping> node : left) {
55 Entry<?> leftEntry = node.getEntry();
56 EntryMapping leftMapping = node.getValue();
57
58 Entry<?> rightEntry = leftTranslator.translate(leftEntry);
59
60 EntryMapping rightMapping = right.get(rightEntry);
61 if (rightMapping != null) {
62 result.insert(leftEntry, rightMapping);
63 addedMappings.add(rightEntry);
64 } else if (keepLeftOnly) {
65 result.insert(leftEntry, leftMapping);
66 }
67 }
68
69 if (keepRightOnly) {
70 Translator leftInverseTranslator = new MappingTranslator(invert(left), VoidEntryResolver.INSTANCE);
71 for (EntryTreeNode<EntryMapping> node : right) {
72 Entry<?> rightEntry = node.getEntry();
73 EntryMapping rightMapping = node.getValue();
74
75 if (!addedMappings.contains(rightEntry)) {
76 result.insert(leftInverseTranslator.translate(rightEntry), rightMapping);
77 }
78 }
79 }
80 return result;
81 }
82
83 public static EntryTree<EntryMapping> read(String type, Path path, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
84 if (type.equals("enigma")) {
85 return (Files.isDirectory(path) ? EnigmaMappingsReader.DIRECTORY : EnigmaMappingsReader.ZIP).read(path, ProgressListener.none(), saveParameters);
86 }
87
88 if (type.equals("tiny")) {
89 return TinyMappingsReader.INSTANCE.read(path, ProgressListener.none(), saveParameters);
90 }
91
92 MappingFormat format = null;
93 try {
94 format = MappingFormat.valueOf(type.toUpperCase());
95 } catch (IllegalArgumentException ignored) {
96 if (type.equals("tinyv2")) {
97 format = MappingFormat.TINY_V2;
98 }
99 }
100
101 if (format != null) {
102 return format.getReader().read(path, ProgressListener.none(), saveParameters);
103 }
104
105 throw new IllegalArgumentException("no reader for " + type);
106 }
107
108 public static void write(EntryTree<EntryMapping> mappings, String type, Path path, MappingSaveParameters saveParameters) {
109 if (type.equals("enigma")) {
110 EnigmaMappingsWriter.DIRECTORY.write(mappings, path, ProgressListener.none(), saveParameters);
111 return;
112 }
113
114 if (type.startsWith("tinyv2:") || type.startsWith("tiny_v2:")) {
115 String[] split = type.split(":");
116
117 if (split.length != 3) {
118 throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'");
119 }
120
121 new TinyV2Writer(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters);
122 return;
123 }
124
125 if (type.startsWith("tiny:")) {
126 String[] split = type.split(":");
127
128 if (split.length != 3) {
129 throw new IllegalArgumentException("specify column names as 'tiny:from_column:to_column'");
130 }
131
132 new TinyMappingsWriter(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters);
133 return;
134 }
135
136 MappingFormat format = null;
137 try {
138 format = MappingFormat.valueOf(type.toUpperCase());
139 } catch (IllegalArgumentException ignored) {}
140
141 if (format != null) {
142 format.getWriter().write(mappings, path, ProgressListener.none(), saveParameters);
143 return;
144 }
145
146 throw new IllegalArgumentException("no writer for " + type);
147 }
148}
diff --git a/src/main/java/cuchaz/enigma/config/Config.java b/src/main/java/cuchaz/enigma/config/Config.java
deleted file mode 100644
index e116fce..0000000
--- a/src/main/java/cuchaz/enigma/config/Config.java
+++ /dev/null
@@ -1,261 +0,0 @@
1package cuchaz.enigma.config;
2
3import com.bulenkov.darcula.DarculaLaf;
4import com.google.common.io.Files;
5import com.google.gson.*;
6import cuchaz.enigma.source.DecompilerService;
7import cuchaz.enigma.source.Decompilers;
8
9import cuchaz.enigma.utils.I18n;
10
11import javax.swing.*;
12import javax.swing.plaf.metal.MetalLookAndFeel;
13import java.awt.*;
14import java.awt.image.BufferedImage;
15import java.io.File;
16import java.io.IOException;
17import java.lang.reflect.Type;
18import java.nio.charset.Charset;
19
20public class Config {
21 public static class AlphaColorEntry {
22 public Integer rgb;
23 public float alpha = 1.0f;
24
25 public AlphaColorEntry(Integer rgb, float alpha) {
26 this.rgb = rgb;
27 this.alpha = alpha;
28 }
29
30 public Color get() {
31 if (rgb == null) {
32 return new Color(0, 0, 0, 0);
33 }
34
35 Color baseColor = new Color(rgb);
36 return new Color(baseColor.getRed(), baseColor.getGreen(), baseColor.getBlue(), (int)(255 * alpha));
37 }
38 }
39
40 public enum LookAndFeel {
41 DEFAULT("Default"),
42 DARCULA("Darcula"),
43 SYSTEM("System"),
44 NONE("None (JVM default)");
45
46 // the "JVM default" look and feel, get it at the beginning and store it so we can set it later
47 private static javax.swing.LookAndFeel NONE_LAF = UIManager.getLookAndFeel();
48 private final String name;
49
50 LookAndFeel(String name) {
51 this.name = name;
52 }
53
54 public String getName() {
55 return name;
56 }
57
58 public void setGlobalLAF() {
59 try {
60 switch (this) {
61 case NONE:
62 UIManager.setLookAndFeel(NONE_LAF);
63 break;
64 case DEFAULT:
65 UIManager.setLookAndFeel(new MetalLookAndFeel());
66 break;
67 case DARCULA:
68 UIManager.setLookAndFeel(new DarculaLaf());
69 break;
70 case SYSTEM:
71 UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
72 }
73 } catch (Exception e){
74 throw new Error("Failed to set global look and feel", e);
75 }
76 }
77
78 public static boolean isDarkLaf() {
79 // a bit of a hack because swing doesn't give any API for that, and we need colors that aren't defined in look and feel
80 JPanel panel = new JPanel();
81 panel.setSize(new Dimension(10, 10));
82 panel.doLayout();
83
84 BufferedImage image = new BufferedImage(panel.getSize().width, panel.getSize().height, BufferedImage.TYPE_INT_RGB);
85 panel.printAll(image.getGraphics());
86
87 Color c = new Color(image.getRGB(0, 0));
88
89 // convert the color we got to grayscale
90 int b = (int) (0.3 * c.getRed() + 0.59 * c.getGreen() + 0.11 * c.getBlue());
91 return b < 85;
92 }
93
94 public void apply(Config config) {
95 boolean isDark = this == LookAndFeel.DARCULA || isDarkLaf();
96 if (!isDark) {//Defaults found here: https://github.com/Sciss/SyntaxPane/blob/122da367ff7a5d31627a70c62a48a9f0f4f85a0a/src/main/resources/de/sciss/syntaxpane/defaultsyntaxkit/config.properties#L139
97 config.lineNumbersForeground = 0x333300;
98 config.lineNumbersBackground = 0xEEEEFF;
99 config.lineNumbersSelected = 0xCCCCEE;
100 config.obfuscatedColor = new AlphaColorEntry(0xFFDCDC, 1.0f);
101 config.obfuscatedColorOutline = new AlphaColorEntry(0xA05050, 1.0f);
102 config.proposedColor = new AlphaColorEntry(0x000000, 0.075f);
103 config.proposedColorOutline = new AlphaColorEntry(0x000000, 0.15f);
104 config.deobfuscatedColor = new AlphaColorEntry(0xDCFFDC, 1.0f);
105 config.deobfuscatedColorOutline = new AlphaColorEntry(0x50A050, 1.0f);
106 config.editorBackground = 0xFFFFFF;
107 config.highlightColor = 0x3333EE;
108 config.caretColor = 0x000000;
109 config.selectionHighlightColor = 0x000000;
110 config.stringColor = 0xCC6600;
111 config.numberColor = 0x999933;
112 config.operatorColor = 0x000000;
113 config.delimiterColor = 0x000000;
114 config.typeColor = 0x000000;
115 config.identifierColor = 0x000000;
116 config.defaultTextColor = 0x000000;
117 } else {//Based off colors found here: https://github.com/dracula/dracula-theme/
118 config.lineNumbersForeground = 0xA4A4A3;
119 config.lineNumbersBackground = 0x313335;
120 config.lineNumbersSelected = 0x606366;
121 config.obfuscatedColor = new AlphaColorEntry(0xFF5555, 0.3f);
122 config.obfuscatedColorOutline = new AlphaColorEntry(0xFF5555, 0.5f);
123 config.deobfuscatedColor = new AlphaColorEntry(0x50FA7B, 0.3f);
124 config.deobfuscatedColorOutline = new AlphaColorEntry(0x50FA7B, 0.5f);
125 config.proposedColor = new AlphaColorEntry(0x606366, 0.3f);
126 config.proposedColorOutline = new AlphaColorEntry(0x606366, 0.5f);
127 config.editorBackground = 0x282A36;
128 config.highlightColor = 0xFF79C6;
129 config.caretColor = 0xF8F8F2;
130 config.selectionHighlightColor = 0xF8F8F2;
131 config.stringColor = 0xF1FA8C;
132 config.numberColor = 0xBD93F9;
133 config.operatorColor = 0xF8F8F2;
134 config.delimiterColor = 0xF8F8F2;
135 config.typeColor = 0xF8F8F2;
136 config.identifierColor = 0xF8F8F2;
137 config.defaultTextColor = 0xF8F8F2;
138 }
139 }
140 }
141
142 public enum Decompiler {
143 PROCYON("Procyon", Decompilers.PROCYON),
144 CFR("CFR", Decompilers.CFR);
145
146 public final DecompilerService service;
147 public final String name;
148
149 Decompiler(String name, DecompilerService service) {
150 this.name = name;
151 this.service = service;
152 }
153 }
154
155 private static final File DIR_HOME = new File(System.getProperty("user.home"));
156 private static final File ENIGMA_DIR = new File(DIR_HOME, ".enigma");
157 private static final File CONFIG_FILE = new File(ENIGMA_DIR, "config.json");
158 private static final Config INSTANCE = new Config();
159
160 private final transient Gson gson; // transient to exclude it from being exposed
161
162 public AlphaColorEntry obfuscatedColor;
163 public AlphaColorEntry obfuscatedColorOutline;
164 public AlphaColorEntry proposedColor;
165 public AlphaColorEntry proposedColorOutline;
166 public AlphaColorEntry deobfuscatedColor;
167 public AlphaColorEntry deobfuscatedColorOutline;
168
169 public Integer editorBackground;
170 public Integer highlightColor;
171 public Integer caretColor;
172 public Integer selectionHighlightColor;
173
174 public Integer stringColor;
175 public Integer numberColor;
176 public Integer operatorColor;
177 public Integer delimiterColor;
178 public Integer typeColor;
179 public Integer identifierColor;
180 public Integer defaultTextColor;
181
182 public Integer lineNumbersBackground;
183 public Integer lineNumbersSelected;
184 public Integer lineNumbersForeground;
185
186 public String language = I18n.DEFAULT_LANGUAGE;
187
188 public LookAndFeel lookAndFeel = LookAndFeel.DEFAULT;
189
190 public float scaleFactor = 1.0f;
191
192 public Decompiler decompiler = Decompiler.PROCYON;
193
194 private Config() {
195 gson = new GsonBuilder()
196 .registerTypeAdapter(Integer.class, new IntSerializer())
197 .registerTypeAdapter(Integer.class, new IntDeserializer())
198 .registerTypeAdapter(Config.class, (InstanceCreator<Config>) type -> this)
199 .setPrettyPrinting()
200 .create();
201 try {
202 this.loadConfig();
203 } catch (IOException ignored) {
204 try {
205 this.reset();
206 } catch (IOException ignored1) {
207 }
208 }
209 }
210
211 public void loadConfig() throws IOException {
212 if (!ENIGMA_DIR.exists()) ENIGMA_DIR.mkdirs();
213 File configFile = new File(ENIGMA_DIR, "config.json");
214 boolean loaded = false;
215
216 if (configFile.exists()) {
217 try {
218 gson.fromJson(Files.asCharSource(configFile, Charset.defaultCharset()).read(), Config.class);
219 loaded = true;
220 } catch (Exception e) {
221 e.printStackTrace();
222 }
223 }
224
225 if (!loaded) {
226 this.reset();
227 Files.touch(configFile);
228 }
229 saveConfig();
230 }
231
232 public void saveConfig() throws IOException {
233 Files.asCharSink(CONFIG_FILE, Charset.defaultCharset()).write(gson.toJson(this));
234 }
235
236 public void reset() throws IOException {
237 this.lookAndFeel = LookAndFeel.DEFAULT;
238 this.lookAndFeel.apply(this);
239 this.decompiler = Decompiler.PROCYON;
240 this.language = I18n.DEFAULT_LANGUAGE;
241 this.saveConfig();
242 }
243
244 private static class IntSerializer implements JsonSerializer<Integer> {
245 @Override
246 public JsonElement serialize(Integer src, Type typeOfSrc, JsonSerializationContext context) {
247 return new JsonPrimitive("#" + Integer.toHexString(src).toUpperCase());
248 }
249 }
250
251 private static class IntDeserializer implements JsonDeserializer<Integer> {
252 @Override
253 public Integer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) {
254 return (int) Long.parseLong(json.getAsString().replace("#", ""), 16);
255 }
256 }
257
258 public static Config getInstance() {
259 return INSTANCE;
260 }
261}
diff --git a/src/main/java/cuchaz/enigma/config/Themes.java b/src/main/java/cuchaz/enigma/config/Themes.java
deleted file mode 100644
index 547a420..0000000
--- a/src/main/java/cuchaz/enigma/config/Themes.java
+++ /dev/null
@@ -1,48 +0,0 @@
1package cuchaz.enigma.config;
2
3import java.awt.Font;
4import java.io.IOException;
5import java.lang.reflect.Field;
6
7import javax.swing.SwingUtilities;
8
9import com.github.swingdpi.UiDefaultsScaler;
10import com.google.common.collect.ImmutableMap;
11import cuchaz.enigma.gui.EnigmaSyntaxKit;
12import cuchaz.enigma.gui.Gui;
13import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
14import cuchaz.enigma.gui.highlight.TokenHighlightType;
15import cuchaz.enigma.gui.util.ScaleUtil;
16import de.sciss.syntaxpane.DefaultSyntaxKit;
17
18public class Themes {
19
20 public static void setLookAndFeel(Gui gui, Config.LookAndFeel lookAndFeel) {
21 Config.getInstance().lookAndFeel = lookAndFeel;
22 updateTheme(gui);
23 }
24
25 public static void updateTheme(Gui gui) {
26 Config config = Config.getInstance();
27 config.lookAndFeel.setGlobalLAF();
28 config.lookAndFeel.apply(config);
29 try {
30 config.saveConfig();
31 } catch (IOException e) {
32 e.printStackTrace();
33 }
34 EnigmaSyntaxKit.invalidate();
35 DefaultSyntaxKit.initKit();
36 DefaultSyntaxKit.registerContentType("text/enigma-sources", EnigmaSyntaxKit.class.getName());
37 gui.boxHighlightPainters = ImmutableMap.of(
38 TokenHighlightType.OBFUSCATED, BoxHighlightPainter.create(config.obfuscatedColor, config.obfuscatedColorOutline),
39 TokenHighlightType.PROPOSED, BoxHighlightPainter.create(config.proposedColor, config.proposedColorOutline),
40 TokenHighlightType.DEOBFUSCATED, BoxHighlightPainter.create(config.deobfuscatedColor, config.deobfuscatedColorOutline)
41 );
42 gui.setEditorTheme(config.lookAndFeel);
43 SwingUtilities.updateComponentTreeUI(gui.getFrame());
44 ScaleUtil.applyScaling();
45 }
46
47
48}
diff --git a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java b/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
deleted file mode 100644
index af105db..0000000
--- a/src/main/java/cuchaz/enigma/gui/BrowserCaret.java
+++ /dev/null
@@ -1,28 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import javax.swing.text.DefaultCaret;
15
16public class BrowserCaret extends DefaultCaret {
17
18 @Override
19 public boolean isSelectionVisible() {
20 return true;
21 }
22
23 @Override
24 public boolean isVisible() {
25 return true;
26 }
27
28}
diff --git a/src/main/java/cuchaz/enigma/gui/ClassSelector.java b/src/main/java/cuchaz/enigma/gui/ClassSelector.java
deleted file mode 100644
index a23e24c..0000000
--- a/src/main/java/cuchaz/enigma/gui/ClassSelector.java
+++ /dev/null
@@ -1,532 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import java.awt.event.MouseAdapter;
15import java.awt.event.MouseEvent;
16import java.util.*;
17
18import javax.annotation.Nullable;
19import javax.swing.JOptionPane;
20import javax.swing.JTree;
21import javax.swing.event.CellEditorListener;
22import javax.swing.event.ChangeEvent;
23import javax.swing.tree.*;
24
25import com.google.common.collect.ArrayListMultimap;
26import com.google.common.collect.Lists;
27import com.google.common.collect.Maps;
28import com.google.common.collect.Multimap;
29import cuchaz.enigma.gui.node.ClassSelectorClassNode;
30import cuchaz.enigma.gui.node.ClassSelectorPackageNode;
31import cuchaz.enigma.throwables.IllegalNameException;
32import cuchaz.enigma.translation.Translator;
33import cuchaz.enigma.translation.representation.entry.ClassEntry;
34
35public class ClassSelector extends JTree {
36
37 public static final Comparator<ClassEntry> DEOBF_CLASS_COMPARATOR = Comparator.comparing(ClassEntry::getFullName);
38
39 private final GuiController controller;
40
41 private DefaultMutableTreeNode rootNodes;
42 private ClassSelectionListener selectionListener;
43 private RenameSelectionListener renameSelectionListener;
44 private Comparator<ClassEntry> comparator;
45
46 private final Map<ClassEntry, ClassEntry> displayedObfToDeobf = new HashMap<>();
47
48 public ClassSelector(Gui gui, Comparator<ClassEntry> comparator, boolean isRenamable) {
49 this.comparator = comparator;
50 this.controller = gui.getController();
51
52 // configure the tree control
53 setEditable(true);
54 setRootVisible(false);
55 setShowsRootHandles(false);
56 setModel(null);
57
58 // hook events
59 addMouseListener(new MouseAdapter() {
60 @Override
61 public void mouseClicked(MouseEvent event) {
62 if (selectionListener != null && event.getClickCount() == 2) {
63 // get the selected node
64 TreePath path = getSelectionPath();
65 if (path != null && path.getLastPathComponent() instanceof ClassSelectorClassNode) {
66 ClassSelectorClassNode node = (ClassSelectorClassNode) path.getLastPathComponent();
67 selectionListener.onSelectClass(node.getObfEntry());
68 }
69 }
70 }
71 });
72
73 final JTree tree = this;
74
75 final DefaultTreeCellEditor editor = new DefaultTreeCellEditor(tree,
76 (DefaultTreeCellRenderer) tree.getCellRenderer()) {
77 @Override
78 public boolean isCellEditable(EventObject event) {
79 return isRenamable && !(event instanceof MouseEvent) && super.isCellEditable(event);
80 }
81 };
82 this.setCellEditor(editor);
83 editor.addCellEditorListener(new CellEditorListener() {
84 @Override
85 public void editingStopped(ChangeEvent e) {
86 String data = editor.getCellEditorValue().toString();
87 TreePath path = getSelectionPath();
88
89 Object realPath = path.getLastPathComponent();
90 if (realPath != null && realPath instanceof DefaultMutableTreeNode && data != null) {
91 DefaultMutableTreeNode node = (DefaultMutableTreeNode) realPath;
92 TreeNode parentNode = node.getParent();
93 if (parentNode == null)
94 return;
95 boolean allowEdit = true;
96 for (int i = 0; i < parentNode.getChildCount(); i++) {
97 TreeNode childNode = parentNode.getChildAt(i);
98 if (childNode != null && childNode.toString().equals(data) && childNode != node) {
99 allowEdit = false;
100 break;
101 }
102 }
103 if (allowEdit && renameSelectionListener != null) {
104 Object prevData = node.getUserObject();
105 Object objectData = node.getUserObject() instanceof ClassEntry ? new ClassEntry(((ClassEntry) prevData).getPackageName() + "/" + data) : data;
106 try {
107 renameSelectionListener.onSelectionRename(node.getUserObject(), objectData, node);
108 node.setUserObject(objectData); // Make sure that it's modified
109 } catch (IllegalNameException ex) {
110 JOptionPane.showOptionDialog(gui.getFrame(), ex.getMessage(), "Enigma - Error", JOptionPane.OK_OPTION,
111 JOptionPane.ERROR_MESSAGE, null, new String[]{"Ok"}, "OK");
112 editor.cancelCellEditing();
113 }
114 } else
115 editor.cancelCellEditing();
116 }
117
118 }
119
120 @Override
121 public void editingCanceled(ChangeEvent e) {
122 // NOP
123 }
124 });
125 // init defaults
126 this.selectionListener = null;
127 this.renameSelectionListener = null;
128 }
129
130 public boolean isDuplicate(Object[] nodes, String data) {
131 int count = 0;
132
133 for (Object node : nodes) {
134 if (node.toString().equals(data)) {
135 count++;
136 if (count == 2)
137 return true;
138 }
139 }
140 return false;
141 }
142
143 public void setSelectionListener(ClassSelectionListener val) {
144 this.selectionListener = val;
145 }
146
147 public void setRenameSelectionListener(RenameSelectionListener renameSelectionListener) {
148 this.renameSelectionListener = renameSelectionListener;
149 }
150
151 public void setClasses(Collection<ClassEntry> classEntries) {
152 displayedObfToDeobf.clear();
153
154 List<StateEntry> state = getExpansionState(this);
155 if (classEntries == null) {
156 setModel(null);
157 return;
158 }
159
160 Translator translator = controller.project.getMapper().getDeobfuscator();
161
162 // build the package names
163 Map<String, ClassSelectorPackageNode> packages = Maps.newHashMap();
164 for (ClassEntry obfClass : classEntries) {
165 ClassEntry deobfClass = translator.translate(obfClass);
166 packages.put(deobfClass.getPackageName(), null);
167 }
168
169 // sort the packages
170 List<String> sortedPackageNames = Lists.newArrayList(packages.keySet());
171 sortedPackageNames.sort((a, b) ->
172 {
173 // I can never keep this rule straight when writing these damn things...
174 // a < b => -1, a == b => 0, a > b => +1
175
176 if (b == null || a == null) {
177 return 0;
178 }
179
180 String[] aparts = a.split("/");
181 String[] bparts = b.split("/");
182 for (int i = 0; true; i++) {
183 if (i >= aparts.length) {
184 return -1;
185 } else if (i >= bparts.length) {
186 return 1;
187 }
188
189 int result = aparts[i].compareTo(bparts[i]);
190 if (result != 0) {
191 return result;
192 }
193 }
194 });
195
196 // create the rootNodes node and the package nodes
197 rootNodes = new DefaultMutableTreeNode();
198 for (String packageName : sortedPackageNames) {
199 ClassSelectorPackageNode node = new ClassSelectorPackageNode(packageName);
200 packages.put(packageName, node);
201 rootNodes.add(node);
202 }
203
204 // put the classes into packages
205 Multimap<String, ClassEntry> packagedClassEntries = ArrayListMultimap.create();
206 for (ClassEntry obfClass : classEntries) {
207 ClassEntry deobfClass = translator.translate(obfClass);
208 packagedClassEntries.put(deobfClass.getPackageName(), obfClass);
209 }
210
211 // build the class nodes
212 for (String packageName : packagedClassEntries.keySet()) {
213 // sort the class entries
214 List<ClassEntry> classEntriesInPackage = Lists.newArrayList(packagedClassEntries.get(packageName));
215 classEntriesInPackage.sort((o1, o2) -> comparator.compare(translator.translate(o1), translator.translate(o2)));
216
217 // create the nodes in order
218 for (ClassEntry obfClass : classEntriesInPackage) {
219 ClassEntry deobfClass = translator.translate(obfClass);
220 ClassSelectorPackageNode node = packages.get(packageName);
221 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfClass, deobfClass);
222 displayedObfToDeobf.put(obfClass, deobfClass);
223 node.add(classNode);
224 }
225 }
226
227 // finally, update the tree control
228 setModel(new DefaultTreeModel(rootNodes));
229
230 restoreExpansionState(this, state);
231 }
232
233 public ClassEntry getSelectedClass() {
234 if (!isSelectionEmpty()) {
235 Object selectedNode = getSelectionPath().getLastPathComponent();
236 if (selectedNode instanceof ClassSelectorClassNode) {
237 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
238 return classNode.getClassEntry();
239 }
240 }
241 return null;
242 }
243
244 public String getSelectedPackage() {
245 if (!isSelectionEmpty()) {
246 Object selectedNode = getSelectionPath().getLastPathComponent();
247 if (selectedNode instanceof ClassSelectorPackageNode) {
248 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) selectedNode;
249 return packageNode.getPackageName();
250 } else if (selectedNode instanceof ClassSelectorClassNode) {
251 ClassSelectorClassNode classNode = (ClassSelectorClassNode) selectedNode;
252 return classNode.getClassEntry().getPackageName();
253 }
254 }
255 return null;
256 }
257
258 public boolean isDescendant(TreePath path1, TreePath path2) {
259 int count1 = path1.getPathCount();
260 int count2 = path2.getPathCount();
261 if (count1 <= count2) {
262 return false;
263 }
264 while (count1 != count2) {
265 path1 = path1.getParentPath();
266 count1--;
267 }
268 return path1.equals(path2);
269 }
270
271 public enum State {
272 EXPANDED,
273 SELECTED
274 }
275
276 public static class StateEntry {
277 public final State state;
278 public final TreePath path;
279
280 public StateEntry(State state, TreePath path) {
281 this.state = state;
282 this.path = path;
283 }
284 }
285
286 public List<StateEntry> getExpansionState(JTree tree) {
287 List<StateEntry> state = new ArrayList<>();
288 int rowCount = tree.getRowCount();
289 for (int i = 0; i < rowCount; i++) {
290 TreePath path = tree.getPathForRow(i);
291 if (tree.isPathSelected(path)) {
292 state.add(new StateEntry(State.SELECTED, path));
293 }
294 if (tree.isExpanded(path)) {
295 state.add(new StateEntry(State.EXPANDED, path));
296 }
297 }
298 return state;
299 }
300
301 public void restoreExpansionState(JTree tree, List<StateEntry> expansionState) {
302 tree.clearSelection();
303
304 for (StateEntry entry : expansionState) {
305 switch (entry.state) {
306 case SELECTED:
307 tree.addSelectionPath(entry.path);
308 break;
309 case EXPANDED:
310 tree.expandPath(entry.path);
311 break;
312 }
313 }
314 }
315
316 public List<ClassSelectorPackageNode> packageNodes() {
317 List<ClassSelectorPackageNode> nodes = Lists.newArrayList();
318 DefaultMutableTreeNode root = (DefaultMutableTreeNode) getModel().getRoot();
319 Enumeration<?> children = root.children();
320 while (children.hasMoreElements()) {
321 ClassSelectorPackageNode packageNode = (ClassSelectorPackageNode) children.nextElement();
322 nodes.add(packageNode);
323 }
324 return nodes;
325 }
326
327 public List<ClassSelectorClassNode> classNodes(ClassSelectorPackageNode packageNode) {
328 List<ClassSelectorClassNode> nodes = Lists.newArrayList();
329 Enumeration<?> children = packageNode.children();
330 while (children.hasMoreElements()) {
331 ClassSelectorClassNode classNode = (ClassSelectorClassNode) children.nextElement();
332 nodes.add(classNode);
333 }
334 return nodes;
335 }
336
337 public void expandPackage(String packageName) {
338 if (packageName == null) {
339 return;
340 }
341 for (ClassSelectorPackageNode packageNode : packageNodes()) {
342 if (packageNode.getPackageName().equals(packageName)) {
343 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
344 return;
345 }
346 }
347 }
348
349 public void expandAll() {
350 for (ClassSelectorPackageNode packageNode : packageNodes()) {
351 expandPath(new TreePath(new Object[]{getModel().getRoot(), packageNode}));
352 }
353 }
354
355 public ClassEntry getFirstClass() {
356 ClassSelectorPackageNode packageNode = packageNodes().get(0);
357 if (packageNode != null) {
358 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
359 if (classNode != null) {
360 return classNode.getClassEntry();
361 }
362 }
363 return null;
364 }
365
366 public ClassSelectorPackageNode getPackageNode(ClassEntry entry) {
367 String packageName = entry.getPackageName();
368 if (packageName == null) {
369 packageName = "(none)";
370 }
371 for (ClassSelectorPackageNode packageNode : packageNodes()) {
372 if (packageNode.getPackageName().equals(packageName)) {
373 return packageNode;
374 }
375 }
376 return null;
377 }
378
379 @Nullable
380 public ClassEntry getDisplayedDeobf(ClassEntry obfEntry) {
381 return displayedObfToDeobf.get(obfEntry);
382 }
383
384 public ClassSelectorPackageNode getPackageNode(ClassSelector selector, ClassEntry entry) {
385 ClassSelectorPackageNode packageNode = getPackageNode(entry);
386
387 if (selector != null && packageNode == null && selector.getPackageNode(entry) != null)
388 return selector.getPackageNode(entry);
389 return packageNode;
390 }
391
392 public ClassEntry getNextClass(ClassEntry entry) {
393 boolean foundIt = false;
394 for (ClassSelectorPackageNode packageNode : packageNodes()) {
395 if (!foundIt) {
396 // skip to the package with our target in it
397 if (packageNode.getPackageName().equals(entry.getPackageName())) {
398 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
399 if (!foundIt) {
400 if (classNode.getClassEntry().equals(entry)) {
401 foundIt = true;
402 }
403 } else {
404 // return the next class
405 return classNode.getClassEntry();
406 }
407 }
408 }
409 } else {
410 // return the next class
411 ClassSelectorClassNode classNode = classNodes(packageNode).get(0);
412 if (classNode != null) {
413 return classNode.getClassEntry();
414 }
415 }
416 }
417 return null;
418 }
419
420 public void setSelectionClass(ClassEntry classEntry) {
421 expandPackage(classEntry.getPackageName());
422 for (ClassSelectorPackageNode packageNode : packageNodes()) {
423 for (ClassSelectorClassNode classNode : classNodes(packageNode)) {
424 if (classNode.getClassEntry().equals(classEntry)) {
425 TreePath path = new TreePath(new Object[]{getModel().getRoot(), packageNode, classNode});
426 setSelectionPath(path);
427 scrollPathToVisible(path);
428 }
429 }
430 }
431 }
432
433 public void removeNode(ClassSelectorPackageNode packageNode, ClassEntry entry) {
434 DefaultTreeModel model = (DefaultTreeModel) getModel();
435
436 if (packageNode == null)
437 return;
438
439 for (int i = 0; i < packageNode.getChildCount(); i++) {
440 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) packageNode.getChildAt(i);
441 if (childNode.getUserObject() instanceof ClassEntry && childNode.getUserObject().equals(entry)) {
442 model.removeNodeFromParent(childNode);
443 if (childNode instanceof ClassSelectorClassNode) {
444 displayedObfToDeobf.remove(((ClassSelectorClassNode) childNode).getObfEntry());
445 }
446 break;
447 }
448 }
449 }
450
451 public void removeNodeIfEmpty(ClassSelectorPackageNode packageNode) {
452 if (packageNode != null && packageNode.getChildCount() == 0)
453 ((DefaultTreeModel) getModel()).removeNodeFromParent(packageNode);
454 }
455
456 public void moveClassIn(ClassEntry classEntry) {
457 removeEntry(classEntry);
458 insertNode(classEntry);
459 }
460
461 public void moveClassOut(ClassEntry classEntry) {
462 removeEntry(classEntry);
463 }
464
465 private void removeEntry(ClassEntry classEntry) {
466 ClassEntry previousDeobf = displayedObfToDeobf.get(classEntry);
467 if (previousDeobf != null) {
468 ClassSelectorPackageNode packageNode = getPackageNode(previousDeobf);
469 removeNode(packageNode, previousDeobf);
470 removeNodeIfEmpty(packageNode);
471 }
472 }
473
474 public ClassSelectorPackageNode getOrCreatePackage(ClassEntry entry) {
475 DefaultTreeModel model = (DefaultTreeModel) getModel();
476 ClassSelectorPackageNode newPackageNode = getPackageNode(entry);
477 if (newPackageNode == null) {
478 newPackageNode = new ClassSelectorPackageNode(entry.getPackageName());
479 model.insertNodeInto(newPackageNode, (MutableTreeNode) model.getRoot(), getPlacementIndex(newPackageNode));
480 }
481 return newPackageNode;
482 }
483
484 public void insertNode(ClassEntry obfEntry) {
485 ClassEntry deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
486 ClassSelectorPackageNode packageNode = getOrCreatePackage(deobfEntry);
487
488 DefaultTreeModel model = (DefaultTreeModel) getModel();
489 ClassSelectorClassNode classNode = new ClassSelectorClassNode(obfEntry, deobfEntry);
490 model.insertNodeInto(classNode, packageNode, getPlacementIndex(packageNode, classNode));
491
492 displayedObfToDeobf.put(obfEntry, deobfEntry);
493 }
494
495 public void reload() {
496 DefaultTreeModel model = (DefaultTreeModel) getModel();
497 model.reload(rootNodes);
498 }
499
500 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode, ClassSelectorClassNode classNode) {
501 List<ClassSelectorClassNode> classNodes = classNodes(newPackageNode);
502 classNodes.add(classNode);
503 classNodes.sort((a, b) -> comparator.compare(a.getClassEntry(), b.getClassEntry()));
504 for (int i = 0; i < classNodes.size(); i++)
505 if (classNodes.get(i) == classNode)
506 return i;
507
508 return 0;
509 }
510
511 private int getPlacementIndex(ClassSelectorPackageNode newPackageNode) {
512 List<ClassSelectorPackageNode> packageNodes = packageNodes();
513 if (!packageNodes.contains(newPackageNode)) {
514 packageNodes.add(newPackageNode);
515 packageNodes.sort(Comparator.comparing(ClassSelectorPackageNode::toString));
516 }
517
518 for (int i = 0; i < packageNodes.size(); i++)
519 if (packageNodes.get(i) == newPackageNode)
520 return i;
521
522 return 0;
523 }
524
525 public interface ClassSelectionListener {
526 void onSelectClass(ClassEntry classEntry);
527 }
528
529 public interface RenameSelectionListener {
530 void onSelectionRename(Object prevData, Object data, DefaultMutableTreeNode node);
531 }
532}
diff --git a/src/main/java/cuchaz/enigma/gui/CodeReader.java b/src/main/java/cuchaz/enigma/gui/CodeReader.java
deleted file mode 100644
index e119640..0000000
--- a/src/main/java/cuchaz/enigma/gui/CodeReader.java
+++ /dev/null
@@ -1,73 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.Token;
15
16import javax.swing.*;
17import javax.swing.text.BadLocationException;
18import javax.swing.text.Document;
19import javax.swing.text.Highlighter.HighlightPainter;
20import java.awt.*;
21import java.awt.event.ActionEvent;
22import java.awt.event.ActionListener;
23
24public class CodeReader extends JEditorPane {
25 private static final long serialVersionUID = 3673180950485748810L;
26
27 // HACKHACK: someday we can update the main GUI to use this code reader
28 public static void navigateToToken(final JEditorPane editor, final Token token, final HighlightPainter highlightPainter) {
29
30 // set the caret position to the token
31 Document document = editor.getDocument();
32 int clampedPosition = Math.min(Math.max(token.start, 0), document.getLength());
33
34 editor.setCaretPosition(clampedPosition);
35 editor.grabFocus();
36
37 try {
38 // make sure the token is visible in the scroll window
39 Rectangle start = editor.modelToView(token.start);
40 Rectangle end = editor.modelToView(token.end);
41 final Rectangle show = start.union(end);
42 show.grow(start.width * 10, start.height * 6);
43 SwingUtilities.invokeLater(() -> editor.scrollRectToVisible(show));
44 } catch (BadLocationException ex) {
45 throw new Error(ex);
46 }
47
48 // highlight the token momentarily
49 final Timer timer = new Timer(200, new ActionListener() {
50 private int counter = 0;
51 private Object highlight = null;
52
53 @Override
54 public void actionPerformed(ActionEvent event) {
55 if (counter % 2 == 0) {
56 try {
57 highlight = editor.getHighlighter().addHighlight(token.start, token.end, highlightPainter);
58 } catch (BadLocationException ex) {
59 // don't care
60 }
61 } else if (highlight != null) {
62 editor.getHighlighter().removeHighlight(highlight);
63 }
64
65 if (counter++ > 6) {
66 Timer timer = (Timer) event.getSource();
67 timer.stop();
68 }
69 }
70 });
71 timer.start();
72 }
73}
diff --git a/src/main/java/cuchaz/enigma/gui/ConnectionState.java b/src/main/java/cuchaz/enigma/gui/ConnectionState.java
deleted file mode 100644
index db6590d..0000000
--- a/src/main/java/cuchaz/enigma/gui/ConnectionState.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui;
2
3public enum ConnectionState {
4 NOT_CONNECTED,
5 HOSTING,
6 CONNECTED,
7}
diff --git a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java b/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
deleted file mode 100644
index 08df3e7..0000000
--- a/src/main/java/cuchaz/enigma/gui/DecompiledClassSource.java
+++ /dev/null
@@ -1,159 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.EnigmaServices;
5import cuchaz.enigma.analysis.EntryReference;
6import cuchaz.enigma.analysis.Token;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.gui.highlight.TokenHighlightType;
9import cuchaz.enigma.source.SourceIndex;
10import cuchaz.enigma.translation.LocalNameGenerator;
11import cuchaz.enigma.translation.Translator;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.mapping.ResolutionStrategy;
14import cuchaz.enigma.translation.representation.TypeDescriptor;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16import cuchaz.enigma.translation.representation.entry.Entry;
17import cuchaz.enigma.translation.representation.entry.LocalVariableDefEntry;
18
19import javax.annotation.Nullable;
20import java.util.*;
21
22public class DecompiledClassSource {
23 private final ClassEntry classEntry;
24
25 private final SourceIndex obfuscatedIndex;
26 private SourceIndex remappedIndex;
27
28 private final Map<TokenHighlightType, Collection<Token>> highlightedTokens = new EnumMap<>(TokenHighlightType.class);
29
30 public DecompiledClassSource(ClassEntry classEntry, SourceIndex index) {
31 this.classEntry = classEntry;
32 this.obfuscatedIndex = index;
33 this.remappedIndex = index;
34 }
35
36 public static DecompiledClassSource text(ClassEntry classEntry, String text) {
37 return new DecompiledClassSource(classEntry, new SourceIndex(text));
38 }
39
40 public void remapSource(EnigmaProject project, Translator translator) {
41 highlightedTokens.clear();
42
43 SourceRemapper remapper = new SourceRemapper(obfuscatedIndex.getSource(), obfuscatedIndex.referenceTokens());
44
45 SourceRemapper.Result remapResult = remapper.remap((token, movedToken) -> remapToken(project, token, movedToken, translator));
46 remappedIndex = obfuscatedIndex.remapTo(remapResult);
47 }
48
49 private String remapToken(EnigmaProject project, Token token, Token movedToken, Translator translator) {
50 EntryReference<Entry<?>, Entry<?>> reference = obfuscatedIndex.getReference(token);
51
52 Entry<?> entry = reference.getNameableEntry();
53 Entry<?> translatedEntry = translator.translate(entry);
54
55 if (project.isRenamable(reference)) {
56 if (isDeobfuscated(entry, translatedEntry)) {
57 highlightToken(movedToken, TokenHighlightType.DEOBFUSCATED);
58 return translatedEntry.getSourceRemapName();
59 } else {
60 Optional<String> proposedName = proposeName(project, entry);
61 if (proposedName.isPresent()) {
62 highlightToken(movedToken, TokenHighlightType.PROPOSED);
63 return proposedName.get();
64 }
65
66 highlightToken(movedToken, TokenHighlightType.OBFUSCATED);
67 }
68 }
69
70 String defaultName = generateDefaultName(translatedEntry);
71 if (defaultName != null) {
72 return defaultName;
73 }
74
75 return null;
76 }
77
78 private Optional<String> proposeName(EnigmaProject project, Entry<?> entry) {
79 EnigmaServices services = project.getEnigma().getServices();
80
81 return services.get(NameProposalService.TYPE).stream().flatMap(nameProposalService -> {
82 EntryRemapper mapper = project.getMapper();
83 Collection<Entry<?>> resolved = mapper.getObfResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
84
85 return resolved.stream()
86 .map(e -> nameProposalService.proposeName(e, mapper))
87 .filter(Optional::isPresent)
88 .map(Optional::get);
89 }).findFirst();
90 }
91
92 @Nullable
93 private String generateDefaultName(Entry<?> entry) {
94 if (entry instanceof LocalVariableDefEntry) {
95 LocalVariableDefEntry localVariable = (LocalVariableDefEntry) entry;
96
97 int index = localVariable.getIndex();
98 if (localVariable.isArgument()) {
99 List<TypeDescriptor> arguments = localVariable.getParent().getDesc().getArgumentDescs();
100 return LocalNameGenerator.generateArgumentName(index, localVariable.getDesc(), arguments);
101 } else {
102 return LocalNameGenerator.generateLocalVariableName(index, localVariable.getDesc());
103 }
104 }
105
106 return null;
107 }
108
109 private boolean isDeobfuscated(Entry<?> entry, Entry<?> translatedEntry) {
110 return !entry.getName().equals(translatedEntry.getName());
111 }
112
113 public ClassEntry getEntry() {
114 return classEntry;
115 }
116
117 public SourceIndex getIndex() {
118 return remappedIndex;
119 }
120
121 public Map<TokenHighlightType, Collection<Token>> getHighlightedTokens() {
122 return highlightedTokens;
123 }
124
125 private void highlightToken(Token token, TokenHighlightType highlightType) {
126 highlightedTokens.computeIfAbsent(highlightType, t -> new ArrayList<>()).add(token);
127 }
128
129 public int getObfuscatedOffset(int deobfOffset) {
130 return getOffset(remappedIndex, obfuscatedIndex, deobfOffset);
131 }
132
133 public int getDeobfuscatedOffset(int obfOffset) {
134 return getOffset(obfuscatedIndex, remappedIndex, obfOffset);
135 }
136
137 private static int getOffset(SourceIndex fromIndex, SourceIndex toIndex, int fromOffset) {
138 int relativeOffset = 0;
139
140 Iterator<Token> fromTokenItr = fromIndex.referenceTokens().iterator();
141 Iterator<Token> toTokenItr = toIndex.referenceTokens().iterator();
142 while (fromTokenItr.hasNext() && toTokenItr.hasNext()) {
143 Token fromToken = fromTokenItr.next();
144 Token toToken = toTokenItr.next();
145 if (fromToken.end > fromOffset) {
146 break;
147 }
148
149 relativeOffset = toToken.end - fromToken.end;
150 }
151
152 return fromOffset + relativeOffset;
153 }
154
155 @Override
156 public String toString() {
157 return remappedIndex.getSource();
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java b/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java
deleted file mode 100644
index c912be3..0000000
--- a/src/main/java/cuchaz/enigma/gui/EnigmaQuickFindDialog.java
+++ /dev/null
@@ -1,90 +0,0 @@
1package cuchaz.enigma.gui;
2
3import de.sciss.syntaxpane.actions.DocumentSearchData;
4import de.sciss.syntaxpane.actions.gui.QuickFindDialog;
5
6import javax.swing.*;
7import javax.swing.text.JTextComponent;
8import java.awt.*;
9import java.awt.event.KeyAdapter;
10import java.awt.event.KeyEvent;
11import java.util.stream.IntStream;
12import java.util.stream.Stream;
13
14public class EnigmaQuickFindDialog extends QuickFindDialog {
15 public EnigmaQuickFindDialog(JTextComponent target) {
16 super(target, DocumentSearchData.getFromEditor(target));
17
18 JToolBar toolBar = getToolBar();
19 JTextField textField = getTextField(toolBar);
20
21 textField.addKeyListener(new KeyAdapter() {
22 @Override
23 public void keyPressed(KeyEvent e) {
24 super.keyPressed(e);
25 if (e.getKeyCode() == KeyEvent.VK_ENTER) {
26 JToolBar toolBar = getToolBar();
27 boolean next = !e.isShiftDown();
28 JButton button = next ? getNextButton(toolBar) : getPrevButton(toolBar);
29 button.doClick();
30 }
31 }
32 });
33 }
34
35 @Override
36 public void showFor(JTextComponent target) {
37 String selectedText = target.getSelectedText();
38
39 try {
40 super.showFor(target);
41 } catch (Exception e) {
42 e.printStackTrace();
43 return;
44 }
45
46 Container view = target.getParent();
47 Point loc = new Point(0, view.getHeight() - getSize().height);
48 setLocationRelativeTo(view);
49 SwingUtilities.convertPointToScreen(loc, view);
50 setLocation(loc);
51
52 JToolBar toolBar = getToolBar();
53 JTextField textField = getTextField(toolBar);
54
55 if (selectedText != null) {
56 textField.setText(selectedText);
57 }
58
59 textField.selectAll();
60 }
61
62 private JToolBar getToolBar() {
63 return components(getContentPane(), JToolBar.class).findFirst().orElse(null);
64 }
65
66 private JTextField getTextField(JToolBar toolBar) {
67 return components(toolBar, JTextField.class).findFirst().orElse(null);
68 }
69
70 private JButton getNextButton(JToolBar toolBar) {
71 Stream<JButton> buttons = components(toolBar, JButton.class);
72 return buttons.skip(1).findFirst().orElse(null);
73 }
74
75 private JButton getPrevButton(JToolBar toolBar) {
76 Stream<JButton> buttons = components(toolBar, JButton.class);
77 return buttons.findFirst().orElse(null);
78 }
79
80 private static Stream<Component> components(Container container) {
81 return IntStream.range(0, container.getComponentCount())
82 .mapToObj(container::getComponent);
83 }
84
85 private static <T extends Component> Stream<T> components(Container container, Class<T> type) {
86 return components(container)
87 .filter(type::isInstance)
88 .map(type::cast);
89 }
90}
diff --git a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java b/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
deleted file mode 100644
index 42eaa60..0000000
--- a/src/main/java/cuchaz/enigma/gui/EnigmaSyntaxKit.java
+++ /dev/null
@@ -1,44 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.config.Config;
4import de.sciss.syntaxpane.components.LineNumbersRuler;
5import de.sciss.syntaxpane.syntaxkits.JavaSyntaxKit;
6import de.sciss.syntaxpane.util.Configuration;
7
8public class EnigmaSyntaxKit extends JavaSyntaxKit {
9 private static Configuration configuration = null;
10
11 @Override
12 public Configuration getConfig() {
13 if(configuration == null){
14 initConfig(super.getConfig(JavaSyntaxKit.class));
15 }
16 return configuration;
17 }
18
19 public void initConfig(Configuration baseConfig){
20 configuration = baseConfig;
21 //See de.sciss.syntaxpane.TokenType
22 configuration.put("Style.KEYWORD", Config.getInstance().highlightColor + ", 0");
23 configuration.put("Style.KEYWORD2", Config.getInstance().highlightColor + ", 3");
24 configuration.put("Style.STRING", Config.getInstance().stringColor + ", 0");
25 configuration.put("Style.STRING2", Config.getInstance().stringColor + ", 1");
26 configuration.put("Style.NUMBER", Config.getInstance().numberColor + ", 1");
27 configuration.put("Style.OPERATOR", Config.getInstance().operatorColor + ", 0");
28 configuration.put("Style.DELIMITER", Config.getInstance().delimiterColor + ", 1");
29 configuration.put("Style.TYPE", Config.getInstance().typeColor + ", 2");
30 configuration.put("Style.TYPE2", Config.getInstance().typeColor + ", 1");
31 configuration.put("Style.IDENTIFIER", Config.getInstance().identifierColor + ", 0");
32 configuration.put("Style.DEFAULT", Config.getInstance().defaultTextColor + ", 0");
33 configuration.put(LineNumbersRuler.PROPERTY_BACKGROUND, Config.getInstance().lineNumbersBackground + "");
34 configuration.put(LineNumbersRuler.PROPERTY_FOREGROUND, Config.getInstance().lineNumbersForeground + "");
35 configuration.put(LineNumbersRuler.PROPERTY_CURRENT_BACK, Config.getInstance().lineNumbersSelected + "");
36 configuration.put("RightMarginColumn", "999"); //No need to have a right margin, if someone wants it add a config
37
38 configuration.put("Action.quick-find", "cuchaz.enigma.gui.QuickFindAction, menu F");
39 }
40
41 public static void invalidate(){
42 configuration = null;
43 }
44}
diff --git a/src/main/java/cuchaz/enigma/gui/Gui.java b/src/main/java/cuchaz/enigma/gui/Gui.java
deleted file mode 100644
index ed32469..0000000
--- a/src/main/java/cuchaz/enigma/gui/Gui.java
+++ /dev/null
@@ -1,1058 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import java.awt.*;
15import java.awt.event.*;
16import java.nio.file.Path;
17import java.util.List;
18import java.util.*;
19import java.util.function.Function;
20
21import javax.swing.*;
22import javax.swing.text.BadLocationException;
23import javax.swing.text.Highlighter;
24import javax.swing.tree.*;
25
26import com.google.common.base.Strings;
27import com.google.common.collect.Lists;
28import cuchaz.enigma.Constants;
29import cuchaz.enigma.EnigmaProfile;
30import cuchaz.enigma.ExceptionIgnorer;
31import cuchaz.enigma.analysis.*;
32import cuchaz.enigma.config.Config;
33import cuchaz.enigma.config.Themes;
34import cuchaz.enigma.gui.dialog.CrashDialog;
35import cuchaz.enigma.gui.dialog.JavadocDialog;
36import cuchaz.enigma.gui.dialog.SearchDialog;
37import cuchaz.enigma.gui.elements.CollapsibleTabbedPane;
38import cuchaz.enigma.gui.elements.MenuBar;
39import cuchaz.enigma.gui.elements.PopupMenuBar;
40import cuchaz.enigma.gui.filechooser.FileChooserAny;
41import cuchaz.enigma.gui.filechooser.FileChooserFolder;
42import cuchaz.enigma.gui.highlight.BoxHighlightPainter;
43import cuchaz.enigma.gui.highlight.SelectionHighlightPainter;
44import cuchaz.enigma.gui.highlight.TokenHighlightType;
45import cuchaz.enigma.gui.panels.PanelDeobf;
46import cuchaz.enigma.gui.panels.PanelEditor;
47import cuchaz.enigma.gui.panels.PanelIdentifier;
48import cuchaz.enigma.gui.panels.PanelObf;
49import cuchaz.enigma.gui.util.History;
50import cuchaz.enigma.network.packet.*;
51import cuchaz.enigma.throwables.IllegalNameException;
52import cuchaz.enigma.translation.mapping.*;
53import cuchaz.enigma.translation.representation.entry.*;
54import cuchaz.enigma.utils.I18n;
55import cuchaz.enigma.utils.Message;
56import cuchaz.enigma.gui.util.ScaleUtil;
57import cuchaz.enigma.utils.Utils;
58import de.sciss.syntaxpane.DefaultSyntaxKit;
59
60public class Gui {
61
62 public final PopupMenuBar popupMenu;
63 private final PanelObf obfPanel;
64 private final PanelDeobf deobfPanel;
65
66 private final MenuBar menuBar;
67 // state
68 public History<EntryReference<Entry<?>, Entry<?>>> referenceHistory;
69 public EntryReference<Entry<?>, Entry<?>> renamingReference;
70 public EntryReference<Entry<?>, Entry<?>> cursorReference;
71 private boolean shouldNavigateOnClick;
72 private ConnectionState connectionState;
73 private boolean isJarOpen;
74
75 public FileDialog jarFileChooser;
76 public FileDialog tinyMappingsFileChooser;
77 public SearchDialog searchDialog;
78 public JFileChooser enigmaMappingsFileChooser;
79 public JFileChooser exportSourceFileChooser;
80 public FileDialog exportJarFileChooser;
81 private GuiController controller;
82 private JFrame frame;
83 public Config.LookAndFeel editorFeel;
84 public PanelEditor editor;
85 public JScrollPane sourceScroller;
86 private JPanel classesPanel;
87 private JSplitPane splitClasses;
88 private PanelIdentifier infoPanel;
89 public Map<TokenHighlightType, BoxHighlightPainter> boxHighlightPainters;
90 private SelectionHighlightPainter selectionHighlightPainter;
91 private JTree inheritanceTree;
92 private JTree implementationsTree;
93 private JTree callsTree;
94 private JList<Token> tokens;
95 private JTabbedPane tabs;
96
97 private JSplitPane splitRight;
98 private JSplitPane logSplit;
99 private CollapsibleTabbedPane logTabs;
100 private JList<String> users;
101 private DefaultListModel<String> userModel;
102 private JScrollPane messageScrollPane;
103 private JList<Message> messages;
104 private DefaultListModel<Message> messageModel;
105 private JTextField chatBox;
106
107 private JPanel statusBar;
108 private JLabel connectionStatusLabel;
109 private JLabel statusLabel;
110
111 public JTextField renameTextField;
112 public JTextArea javadocTextArea;
113
114 public void setEditorTheme(Config.LookAndFeel feel) {
115 if (editor != null && (editorFeel == null || editorFeel != feel)) {
116 editor.updateUI();
117 editor.setBackground(new Color(Config.getInstance().editorBackground));
118 if (editorFeel != null) {
119 getController().refreshCurrentClass();
120 }
121
122 editorFeel = feel;
123 }
124 }
125
126 public Gui(EnigmaProfile profile) {
127 Config.getInstance().lookAndFeel.setGlobalLAF();
128
129 // init frame
130 this.frame = new JFrame(Constants.NAME);
131 final Container pane = this.frame.getContentPane();
132 pane.setLayout(new BorderLayout());
133
134 if (Boolean.parseBoolean(System.getProperty("enigma.catchExceptions", "true"))) {
135 // install a global exception handler to the event thread
136 CrashDialog.init(this.frame);
137 Thread.setDefaultUncaughtExceptionHandler((thread, t) -> {
138 t.printStackTrace(System.err);
139 if (!ExceptionIgnorer.shouldIgnore(t)) {
140 CrashDialog.show(t);
141 }
142 });
143 }
144
145 this.controller = new GuiController(this, profile);
146
147 Themes.updateTheme(this);
148
149 // init file choosers
150 this.jarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.jar.open"), FileDialog.LOAD);
151
152 this.tinyMappingsFileChooser = new FileDialog(getFrame(), "Open tiny Mappings", FileDialog.LOAD);
153 this.enigmaMappingsFileChooser = new FileChooserAny();
154 this.exportSourceFileChooser = new FileChooserFolder();
155 this.exportJarFileChooser = new FileDialog(getFrame(), I18n.translate("menu.file.export.jar"), FileDialog.SAVE);
156
157 this.obfPanel = new PanelObf(this);
158 this.deobfPanel = new PanelDeobf(this);
159
160 // set up classes panel (don't add the splitter yet)
161 splitClasses = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, this.obfPanel, this.deobfPanel);
162 splitClasses.setResizeWeight(0.3);
163 this.classesPanel = new JPanel();
164 this.classesPanel.setLayout(new BorderLayout());
165 this.classesPanel.setPreferredSize(ScaleUtil.getDimension(250, 0));
166
167 // init info panel
168 infoPanel = new PanelIdentifier(this);
169 infoPanel.clearReference();
170
171 // init editor
172 selectionHighlightPainter = new SelectionHighlightPainter();
173 this.editor = new PanelEditor(this);
174 this.sourceScroller = new JScrollPane(this.editor);
175 this.editor.setContentType("text/enigma-sources");
176 this.editor.setBackground(new Color(Config.getInstance().editorBackground));
177 DefaultSyntaxKit kit = (DefaultSyntaxKit) this.editor.getEditorKit();
178 kit.toggleComponent(this.editor, "de.sciss.syntaxpane.components.TokenMarker");
179
180 // init editor popup menu
181 this.popupMenu = new PopupMenuBar(this);
182 this.editor.setComponentPopupMenu(this.popupMenu);
183
184 // init inheritance panel
185 inheritanceTree = new JTree();
186 inheritanceTree.setModel(null);
187 inheritanceTree.addMouseListener(new MouseAdapter() {
188 @Override
189 public void mouseClicked(MouseEvent event) {
190 if (event.getClickCount() >= 2) {
191 // get the selected node
192 TreePath path = inheritanceTree.getSelectionPath();
193 if (path == null) {
194 return;
195 }
196
197 Object node = path.getLastPathComponent();
198 if (node instanceof ClassInheritanceTreeNode) {
199 ClassInheritanceTreeNode classNode = (ClassInheritanceTreeNode) node;
200 controller.navigateTo(new ClassEntry(classNode.getObfClassName()));
201 } else if (node instanceof MethodInheritanceTreeNode) {
202 MethodInheritanceTreeNode methodNode = (MethodInheritanceTreeNode) node;
203 if (methodNode.isImplemented()) {
204 controller.navigateTo(methodNode.getMethodEntry());
205 }
206 }
207 }
208 }
209 });
210 TreeCellRenderer cellRenderer = inheritanceTree.getCellRenderer();
211 inheritanceTree.setCellRenderer(new MethodTreeCellRenderer(cellRenderer));
212
213 JPanel inheritancePanel = new JPanel();
214 inheritancePanel.setLayout(new BorderLayout());
215 inheritancePanel.add(new JScrollPane(inheritanceTree));
216
217 // init implementations panel
218 implementationsTree = new JTree();
219 implementationsTree.setModel(null);
220 implementationsTree.addMouseListener(new MouseAdapter() {
221 @Override
222 public void mouseClicked(MouseEvent event) {
223 if (event.getClickCount() >= 2) {
224 // get the selected node
225 TreePath path = implementationsTree.getSelectionPath();
226 if (path == null) {
227 return;
228 }
229
230 Object node = path.getLastPathComponent();
231 if (node instanceof ClassImplementationsTreeNode) {
232 ClassImplementationsTreeNode classNode = (ClassImplementationsTreeNode) node;
233 controller.navigateTo(classNode.getClassEntry());
234 } else if (node instanceof MethodImplementationsTreeNode) {
235 MethodImplementationsTreeNode methodNode = (MethodImplementationsTreeNode) node;
236 controller.navigateTo(methodNode.getMethodEntry());
237 }
238 }
239 }
240 });
241 JPanel implementationsPanel = new JPanel();
242 implementationsPanel.setLayout(new BorderLayout());
243 implementationsPanel.add(new JScrollPane(implementationsTree));
244
245 // init call panel
246 callsTree = new JTree();
247 callsTree.setModel(null);
248 callsTree.addMouseListener(new MouseAdapter() {
249 @SuppressWarnings("unchecked")
250 @Override
251 public void mouseClicked(MouseEvent event) {
252 if (event.getClickCount() >= 2) {
253 // get the selected node
254 TreePath path = callsTree.getSelectionPath();
255 if (path == null) {
256 return;
257 }
258
259 Object node = path.getLastPathComponent();
260 if (node instanceof ReferenceTreeNode) {
261 ReferenceTreeNode<Entry<?>, Entry<?>> referenceNode = ((ReferenceTreeNode<Entry<?>, Entry<?>>) node);
262 if (referenceNode.getReference() != null) {
263 controller.navigateTo(referenceNode.getReference());
264 } else {
265 controller.navigateTo(referenceNode.getEntry());
266 }
267 }
268 }
269 }
270 });
271 tokens = new JList<>();
272 tokens.setCellRenderer(new TokenListCellRenderer(this.controller));
273 tokens.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
274 tokens.setLayoutOrientation(JList.VERTICAL);
275 tokens.addMouseListener(new MouseAdapter() {
276 @Override
277 public void mouseClicked(MouseEvent event) {
278 if (event.getClickCount() == 2) {
279 Token selected = tokens.getSelectedValue();
280 if (selected != null) {
281 showToken(selected);
282 }
283 }
284 }
285 });
286 tokens.setPreferredSize(ScaleUtil.getDimension(0, 200));
287 tokens.setMinimumSize(ScaleUtil.getDimension(0, 200));
288 JSplitPane callPanel = new JSplitPane(
289 JSplitPane.VERTICAL_SPLIT,
290 true,
291 new JScrollPane(callsTree),
292 new JScrollPane(tokens)
293 );
294 callPanel.setResizeWeight(1); // let the top side take all the slack
295 callPanel.resetToPreferredSizes();
296
297 // layout controls
298 JPanel centerPanel = new JPanel();
299 centerPanel.setLayout(new BorderLayout());
300 centerPanel.add(infoPanel, BorderLayout.NORTH);
301 centerPanel.add(sourceScroller, BorderLayout.CENTER);
302 tabs = new JTabbedPane();
303 tabs.setPreferredSize(ScaleUtil.getDimension(250, 0));
304 tabs.addTab(I18n.translate("info_panel.tree.inheritance"), inheritancePanel);
305 tabs.addTab(I18n.translate("info_panel.tree.implementations"), implementationsPanel);
306 tabs.addTab(I18n.translate("info_panel.tree.calls"), callPanel);
307 logTabs = new CollapsibleTabbedPane(JTabbedPane.BOTTOM);
308 userModel = new DefaultListModel<>();
309 users = new JList<>(userModel);
310 messageModel = new DefaultListModel<>();
311 messages = new JList<>(messageModel);
312 messages.setCellRenderer(new MessageListCellRenderer());
313 JPanel messagePanel = new JPanel(new BorderLayout());
314 messageScrollPane = new JScrollPane(this.messages);
315 messagePanel.add(messageScrollPane, BorderLayout.CENTER);
316 JPanel chatPanel = new JPanel(new BorderLayout());
317 chatBox = new JTextField();
318 AbstractAction sendListener = new AbstractAction("Send") {
319 @Override
320 public void actionPerformed(ActionEvent e) {
321 sendMessage();
322 }
323 };
324 chatBox.addActionListener(sendListener);
325 JButton chatSendButton = new JButton(sendListener);
326 chatPanel.add(chatBox, BorderLayout.CENTER);
327 chatPanel.add(chatSendButton, BorderLayout.EAST);
328 messagePanel.add(chatPanel, BorderLayout.SOUTH);
329 logTabs.addTab(I18n.translate("log_panel.users"), new JScrollPane(this.users));
330 logTabs.addTab(I18n.translate("log_panel.messages"), messagePanel);
331 logSplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, true, tabs, logTabs);
332 logSplit.setResizeWeight(0.5);
333 logSplit.resetToPreferredSizes();
334 splitRight = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, centerPanel, this.logSplit);
335 splitRight.setResizeWeight(1); // let the left side take all the slack
336 splitRight.resetToPreferredSizes();
337 JSplitPane splitCenter = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, true, this.classesPanel, splitRight);
338 splitCenter.setResizeWeight(0); // let the right side take all the slack
339 pane.add(splitCenter, BorderLayout.CENTER);
340
341 // init menus
342 this.menuBar = new MenuBar(this);
343 this.frame.setJMenuBar(this.menuBar);
344
345 // init status bar
346 statusBar = new JPanel(new BorderLayout());
347 statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
348 connectionStatusLabel = new JLabel();
349 statusLabel = new JLabel();
350 statusBar.add(statusLabel, BorderLayout.CENTER);
351 statusBar.add(connectionStatusLabel, BorderLayout.EAST);
352 pane.add(statusBar, BorderLayout.SOUTH);
353
354 // init state
355 setConnectionState(ConnectionState.NOT_CONNECTED);
356 onCloseJar();
357
358 this.frame.addWindowListener(new WindowAdapter() {
359 @Override
360 public void windowClosing(WindowEvent event) {
361 close();
362 }
363 });
364
365 // show the frame
366 pane.doLayout();
367 this.frame.setSize(ScaleUtil.getDimension(1024, 576));
368 this.frame.setMinimumSize(ScaleUtil.getDimension(640, 480));
369 this.frame.setVisible(true);
370 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
371 this.frame.setLocationRelativeTo(null);
372 }
373
374 public JFrame getFrame() {
375 return this.frame;
376 }
377
378 public GuiController getController() {
379 return this.controller;
380 }
381
382 public void onStartOpenJar() {
383 this.classesPanel.removeAll();
384 redraw();
385 }
386
387 public void onFinishOpenJar(String jarName) {
388 // update gui
389 this.frame.setTitle(Constants.NAME + " - " + jarName);
390 this.classesPanel.removeAll();
391 this.classesPanel.add(splitClasses);
392 setEditorText(null);
393
394 // update menu
395 isJarOpen = true;
396
397 updateUiState();
398 redraw();
399 }
400
401 public void onCloseJar() {
402
403 // update gui
404 this.frame.setTitle(Constants.NAME);
405 setObfClasses(null);
406 setDeobfClasses(null);
407 setEditorText(null);
408 this.classesPanel.removeAll();
409
410 // update menu
411 isJarOpen = false;
412 setMappingsFile(null);
413
414 updateUiState();
415 redraw();
416 }
417
418 public void setObfClasses(Collection<ClassEntry> obfClasses) {
419 this.obfPanel.obfClasses.setClasses(obfClasses);
420 }
421
422 public void setDeobfClasses(Collection<ClassEntry> deobfClasses) {
423 this.deobfPanel.deobfClasses.setClasses(deobfClasses);
424 }
425
426 public void setMappingsFile(Path path) {
427 this.enigmaMappingsFileChooser.setSelectedFile(path != null ? path.toFile() : null);
428 updateUiState();
429 }
430
431 public void setEditorText(String source) {
432 this.editor.getHighlighter().removeAllHighlights();
433 this.editor.setText(source);
434 }
435
436 public void setSource(DecompiledClassSource source) {
437 editor.setText(source.toString());
438 setHighlightedTokens(source.getHighlightedTokens());
439 }
440
441 public void showToken(final Token token) {
442 if (token == null) {
443 throw new IllegalArgumentException("Token cannot be null!");
444 }
445 CodeReader.navigateToToken(this.editor, token, selectionHighlightPainter);
446 redraw();
447 }
448
449 public void showTokens(Collection<Token> tokens) {
450 Vector<Token> sortedTokens = new Vector<>(tokens);
451 Collections.sort(sortedTokens);
452 if (sortedTokens.size() > 1) {
453 // sort the tokens and update the tokens panel
454 this.tokens.setListData(sortedTokens);
455 this.tokens.setSelectedIndex(0);
456 } else {
457 this.tokens.setListData(new Vector<>());
458 }
459
460 // show the first token
461 showToken(sortedTokens.get(0));
462 }
463
464 public void setHighlightedTokens(Map<TokenHighlightType, Collection<Token>> tokens) {
465 // remove any old highlighters
466 this.editor.getHighlighter().removeAllHighlights();
467
468 if (boxHighlightPainters != null) {
469 for (TokenHighlightType type : tokens.keySet()) {
470 BoxHighlightPainter painter = boxHighlightPainters.get(type);
471 if (painter != null) {
472 setHighlightedTokens(tokens.get(type), painter);
473 }
474 }
475 }
476
477 redraw();
478 }
479
480 private void setHighlightedTokens(Iterable<Token> tokens, Highlighter.HighlightPainter painter) {
481 for (Token token : tokens) {
482 try {
483 this.editor.getHighlighter().addHighlight(token.start, token.end, painter);
484 } catch (BadLocationException ex) {
485 throw new IllegalArgumentException(ex);
486 }
487 }
488 }
489
490 private void showCursorReference(EntryReference<Entry<?>, Entry<?>> reference) {
491 if (reference == null) {
492 infoPanel.clearReference();
493 return;
494 }
495
496 this.cursorReference = reference;
497
498 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(reference);
499
500 infoPanel.removeAll();
501 if (translatedReference.entry instanceof ClassEntry) {
502 showClassEntry((ClassEntry) translatedReference.entry);
503 } else if (translatedReference.entry instanceof FieldEntry) {
504 showFieldEntry((FieldEntry) translatedReference.entry);
505 } else if (translatedReference.entry instanceof MethodEntry) {
506 showMethodEntry((MethodEntry) translatedReference.entry);
507 } else if (translatedReference.entry instanceof LocalVariableEntry) {
508 showLocalVariableEntry((LocalVariableEntry) translatedReference.entry);
509 } else {
510 throw new Error("Unknown entry desc: " + translatedReference.entry.getClass().getName());
511 }
512
513 redraw();
514 }
515
516 private void showLocalVariableEntry(LocalVariableEntry entry) {
517 addNameValue(infoPanel, I18n.translate("info_panel.identifier.variable"), entry.getName());
518 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getContainingClass().getFullName());
519 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getParent().getName());
520 addNameValue(infoPanel, I18n.translate("info_panel.identifier.index"), Integer.toString(entry.getIndex()));
521 }
522
523 private void showClassEntry(ClassEntry entry) {
524 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getFullName());
525 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
526 }
527
528 private void showFieldEntry(FieldEntry entry) {
529 addNameValue(infoPanel, I18n.translate("info_panel.identifier.field"), entry.getName());
530 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
531 addNameValue(infoPanel, I18n.translate("info_panel.identifier.type_descriptor"), entry.getDesc().toString());
532 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
533 }
534
535 private void showMethodEntry(MethodEntry entry) {
536 if (entry.isConstructor()) {
537 addNameValue(infoPanel, I18n.translate("info_panel.identifier.constructor"), entry.getParent().getFullName());
538 } else {
539 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method"), entry.getName());
540 addNameValue(infoPanel, I18n.translate("info_panel.identifier.class"), entry.getParent().getFullName());
541 }
542 addNameValue(infoPanel, I18n.translate("info_panel.identifier.method_descriptor"), entry.getDesc().toString());
543 addModifierComboBox(infoPanel, I18n.translate("info_panel.identifier.modifier"), entry);
544 }
545
546 private void addNameValue(JPanel container, String name, String value) {
547 JPanel panel = new JPanel();
548 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
549
550 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
551 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
552 panel.add(label);
553
554 panel.add(Utils.unboldLabel(new JLabel(value, JLabel.LEFT)));
555
556 container.add(panel);
557 }
558
559 private JComboBox<AccessModifier> addModifierComboBox(JPanel container, String name, Entry<?> entry) {
560 if (!getController().project.isRenamable(entry))
561 return null;
562 JPanel panel = new JPanel();
563 panel.setLayout(new FlowLayout(FlowLayout.LEFT, 6, 0));
564 JLabel label = new JLabel(name + ":", JLabel.RIGHT);
565 label.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
566 panel.add(label);
567 JComboBox<AccessModifier> combo = new JComboBox<>(AccessModifier.values());
568 ((JLabel) combo.getRenderer()).setHorizontalAlignment(JLabel.LEFT);
569 combo.setPreferredSize(ScaleUtil.getDimension(100, ScaleUtil.invert(label.getPreferredSize().height)));
570
571 EntryMapping mapping = controller.project.getMapper().getDeobfMapping(entry);
572 if (mapping != null) {
573 combo.setSelectedIndex(mapping.getAccessModifier().ordinal());
574 } else {
575 combo.setSelectedIndex(AccessModifier.UNCHANGED.ordinal());
576 }
577
578 combo.addItemListener(controller::modifierChange);
579
580 panel.add(combo);
581
582 container.add(panel);
583
584 return combo;
585 }
586
587 public void onCaretMove(int pos, boolean fromClick) {
588 if (controller.project == null)
589 return;
590 EntryRemapper mapper = controller.project.getMapper();
591 Token token = this.controller.getToken(pos);
592 boolean isToken = token != null;
593
594 cursorReference = this.controller.getReference(token);
595 Entry<?> referenceEntry = cursorReference != null ? cursorReference.entry : null;
596
597 if (referenceEntry != null && shouldNavigateOnClick && fromClick) {
598 shouldNavigateOnClick = false;
599 Entry<?> navigationEntry = referenceEntry;
600 if (cursorReference.context == null) {
601 EntryResolver resolver = mapper.getObfResolver();
602 navigationEntry = resolver.resolveFirstEntry(referenceEntry, ResolutionStrategy.RESOLVE_ROOT);
603 }
604 controller.navigateTo(navigationEntry);
605 return;
606 }
607
608 boolean isClassEntry = isToken && referenceEntry instanceof ClassEntry;
609 boolean isFieldEntry = isToken && referenceEntry instanceof FieldEntry;
610 boolean isMethodEntry = isToken && referenceEntry instanceof MethodEntry && !((MethodEntry) referenceEntry).isConstructor();
611 boolean isConstructorEntry = isToken && referenceEntry instanceof MethodEntry && ((MethodEntry) referenceEntry).isConstructor();
612 boolean isRenamable = isToken && this.controller.project.isRenamable(cursorReference);
613
614 if (!isRenaming()) {
615 if (isToken) {
616 showCursorReference(cursorReference);
617 } else {
618 infoPanel.clearReference();
619 }
620 }
621
622 this.popupMenu.renameMenu.setEnabled(isRenamable);
623 this.popupMenu.editJavadocMenu.setEnabled(isRenamable);
624 this.popupMenu.showInheritanceMenu.setEnabled(isClassEntry || isMethodEntry || isConstructorEntry);
625 this.popupMenu.showImplementationsMenu.setEnabled(isClassEntry || isMethodEntry);
626 this.popupMenu.showCallsMenu.setEnabled(isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry);
627 this.popupMenu.showCallsSpecificMenu.setEnabled(isMethodEntry);
628 this.popupMenu.openEntryMenu.setEnabled(isRenamable && (isClassEntry || isFieldEntry || isMethodEntry || isConstructorEntry));
629 this.popupMenu.openPreviousMenu.setEnabled(this.controller.hasPreviousReference());
630 this.popupMenu.openNextMenu.setEnabled(this.controller.hasNextReference());
631 this.popupMenu.toggleMappingMenu.setEnabled(isRenamable);
632
633 if (isToken && !Objects.equals(referenceEntry, mapper.deobfuscate(referenceEntry))) {
634 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.reset_obfuscated"));
635 } else {
636 this.popupMenu.toggleMappingMenu.setText(I18n.translate("popup_menu.mark_deobfuscated"));
637 }
638 }
639
640 public void startDocChange() {
641 EntryReference<Entry<?>, Entry<?>> curReference = cursorReference;
642 if (isRenaming()) {
643 finishRename(false);
644 }
645 renamingReference = curReference;
646
647 // init the text box
648 javadocTextArea = new JTextArea(10, 40);
649
650 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
651 javadocTextArea.setText(Strings.nullToEmpty(translatedReference.entry.getJavadocs()));
652
653 JavadocDialog.init(frame, javadocTextArea, this::finishDocChange);
654 javadocTextArea.grabFocus();
655
656 redraw();
657 }
658
659 private void finishDocChange(JFrame ui, boolean saveName) {
660 String newName = javadocTextArea.getText();
661 if (saveName) {
662 try {
663 this.controller.changeDocs(renamingReference, newName);
664 this.controller.sendPacket(new ChangeDocsC2SPacket(renamingReference.getNameableEntry(), newName));
665 } catch (IllegalNameException ex) {
666 javadocTextArea.setBorder(BorderFactory.createLineBorder(Color.red, 1));
667 javadocTextArea.setToolTipText(ex.getReason());
668 Utils.showToolTipNow(javadocTextArea);
669 return;
670 }
671
672 ui.setVisible(false);
673 showCursorReference(cursorReference);
674 return;
675 }
676
677 // abort the jd change
678 javadocTextArea = null;
679 ui.setVisible(false);
680 showCursorReference(cursorReference);
681
682 this.editor.grabFocus();
683
684 redraw();
685 }
686
687 public void startRename() {
688
689 // init the text box
690 renameTextField = new JTextField();
691
692 EntryReference<Entry<?>, Entry<?>> translatedReference = controller.project.getMapper().deobfuscate(cursorReference);
693 renameTextField.setText(translatedReference.getNameableName());
694
695 renameTextField.setPreferredSize(ScaleUtil.getDimension(360, ScaleUtil.invert(renameTextField.getPreferredSize().height)));
696 renameTextField.addKeyListener(new KeyAdapter() {
697 @Override
698 public void keyPressed(KeyEvent event) {
699 switch (event.getKeyCode()) {
700 case KeyEvent.VK_ENTER:
701 finishRename(true);
702 break;
703
704 case KeyEvent.VK_ESCAPE:
705 finishRename(false);
706 break;
707 default:
708 break;
709 }
710 }
711 });
712
713 // find the label with the name and replace it with the text box
714 JPanel panel = (JPanel) infoPanel.getComponent(0);
715 panel.remove(panel.getComponentCount() - 1);
716 panel.add(renameTextField);
717 renameTextField.grabFocus();
718
719 int offset = renameTextField.getText().lastIndexOf('/') + 1;
720 // If it's a class and isn't in the default package, assume that it's deobfuscated.
721 if (translatedReference.getNameableEntry() instanceof ClassEntry && renameTextField.getText().contains("/") && offset != 0)
722 renameTextField.select(offset, renameTextField.getText().length());
723 else
724 renameTextField.selectAll();
725
726 renamingReference = cursorReference;
727
728 redraw();
729 }
730
731 private void finishRename(boolean saveName) {
732 String newName = renameTextField.getText();
733
734 if (saveName && newName != null && !newName.isEmpty()) {
735 try {
736 this.controller.rename(renamingReference, newName, true);
737 this.controller.sendPacket(new RenameC2SPacket(renamingReference.getNameableEntry(), newName, true));
738 renameTextField = null;
739 } catch (IllegalNameException ex) {
740 renameTextField.setBorder(BorderFactory.createLineBorder(Color.red, 1));
741 renameTextField.setToolTipText(ex.getReason());
742 Utils.showToolTipNow(renameTextField);
743 }
744 return;
745 }
746
747 renameTextField = null;
748
749 // abort the rename
750 showCursorReference(cursorReference);
751
752 this.editor.grabFocus();
753
754 redraw();
755 }
756
757 private boolean isRenaming() {
758 return renameTextField != null;
759 }
760
761 public void showInheritance() {
762
763 if (cursorReference == null) {
764 return;
765 }
766
767 inheritanceTree.setModel(null);
768
769 if (cursorReference.entry instanceof ClassEntry) {
770 // get the class inheritance
771 ClassInheritanceTreeNode classNode = this.controller.getClassInheritance((ClassEntry) cursorReference.entry);
772
773 // show the tree at the root
774 TreePath path = getPathToRoot(classNode);
775 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
776 inheritanceTree.expandPath(path);
777 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
778 } else if (cursorReference.entry instanceof MethodEntry) {
779 // get the method inheritance
780 MethodInheritanceTreeNode classNode = this.controller.getMethodInheritance((MethodEntry) cursorReference.entry);
781
782 // show the tree at the root
783 TreePath path = getPathToRoot(classNode);
784 inheritanceTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
785 inheritanceTree.expandPath(path);
786 inheritanceTree.setSelectionRow(inheritanceTree.getRowForPath(path));
787 }
788
789 tabs.setSelectedIndex(0);
790
791 redraw();
792 }
793
794 public void showImplementations() {
795
796 if (cursorReference == null) {
797 return;
798 }
799
800 implementationsTree.setModel(null);
801
802 DefaultMutableTreeNode node = null;
803
804 // get the class implementations
805 if (cursorReference.entry instanceof ClassEntry)
806 node = this.controller.getClassImplementations((ClassEntry) cursorReference.entry);
807 else // get the method implementations
808 if (cursorReference.entry instanceof MethodEntry)
809 node = this.controller.getMethodImplementations((MethodEntry) cursorReference.entry);
810
811 if (node != null) {
812 // show the tree at the root
813 TreePath path = getPathToRoot(node);
814 implementationsTree.setModel(new DefaultTreeModel((TreeNode) path.getPathComponent(0)));
815 implementationsTree.expandPath(path);
816 implementationsTree.setSelectionRow(implementationsTree.getRowForPath(path));
817 }
818
819 tabs.setSelectedIndex(1);
820
821 redraw();
822 }
823
824 public void showCalls(boolean recurse) {
825 if (cursorReference == null) {
826 return;
827 }
828
829 if (cursorReference.entry instanceof ClassEntry) {
830 ClassReferenceTreeNode node = this.controller.getClassReferences((ClassEntry) cursorReference.entry);
831 callsTree.setModel(new DefaultTreeModel(node));
832 } else if (cursorReference.entry instanceof FieldEntry) {
833 FieldReferenceTreeNode node = this.controller.getFieldReferences((FieldEntry) cursorReference.entry);
834 callsTree.setModel(new DefaultTreeModel(node));
835 } else if (cursorReference.entry instanceof MethodEntry) {
836 MethodReferenceTreeNode node = this.controller.getMethodReferences((MethodEntry) cursorReference.entry, recurse);
837 callsTree.setModel(new DefaultTreeModel(node));
838 }
839
840 tabs.setSelectedIndex(2);
841
842 redraw();
843 }
844
845 public void toggleMapping() {
846 Entry<?> obfEntry = cursorReference.entry;
847 Entry<?> deobfEntry = controller.project.getMapper().deobfuscate(obfEntry);
848
849 if (!Objects.equals(obfEntry, deobfEntry)) {
850 this.controller.removeMapping(cursorReference);
851 this.controller.sendPacket(new RemoveMappingC2SPacket(cursorReference.getNameableEntry()));
852 } else {
853 this.controller.markAsDeobfuscated(cursorReference);
854 this.controller.sendPacket(new MarkDeobfuscatedC2SPacket(cursorReference.getNameableEntry()));
855 }
856 }
857
858 private TreePath getPathToRoot(TreeNode node) {
859 List<TreeNode> nodes = Lists.newArrayList();
860 TreeNode n = node;
861 do {
862 nodes.add(n);
863 n = n.getParent();
864 } while (n != null);
865 Collections.reverse(nodes);
866 return new TreePath(nodes.toArray());
867 }
868
869 public void showDiscardDiag(Function<Integer, Void> callback, String... options) {
870 int response = JOptionPane.showOptionDialog(this.frame, I18n.translate("prompt.close.summary"), I18n.translate("prompt.close.title"), JOptionPane.YES_NO_CANCEL_OPTION,
871 JOptionPane.QUESTION_MESSAGE, null, options, options[2]);
872 callback.apply(response);
873 }
874
875 public void saveMapping() {
876 if (this.enigmaMappingsFileChooser.getSelectedFile() != null || this.enigmaMappingsFileChooser.showSaveDialog(this.frame) == JFileChooser.APPROVE_OPTION)
877 this.controller.saveMappings(this.enigmaMappingsFileChooser.getSelectedFile().toPath());
878 }
879
880 public void close() {
881 if (!this.controller.isDirty()) {
882 // everything is saved, we can exit safely
883 exit();
884 } else {
885 // ask to save before closing
886 showDiscardDiag((response) -> {
887 if (response == JOptionPane.YES_OPTION) {
888 this.saveMapping();
889 exit();
890 } else if (response == JOptionPane.NO_OPTION) {
891 exit();
892 }
893
894 return null;
895 }, I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel"));
896 }
897 }
898
899 private void exit() {
900 if (searchDialog != null) {
901 searchDialog.dispose();
902 }
903 this.frame.dispose();
904 System.exit(0);
905 }
906
907 public void redraw() {
908 this.frame.validate();
909 this.frame.repaint();
910 }
911
912 public void onPanelRename(Object prevData, Object data, DefaultMutableTreeNode node) throws IllegalNameException {
913 // package rename
914 if (data instanceof String) {
915 for (int i = 0; i < node.getChildCount(); i++) {
916 DefaultMutableTreeNode childNode = (DefaultMutableTreeNode) node.getChildAt(i);
917 ClassEntry prevDataChild = (ClassEntry) childNode.getUserObject();
918 ClassEntry dataChild = new ClassEntry(data + "/" + prevDataChild.getSimpleName());
919 this.controller.rename(new EntryReference<>(prevDataChild, prevDataChild.getFullName()), dataChild.getFullName(), false);
920 this.controller.sendPacket(new RenameC2SPacket(prevDataChild, dataChild.getFullName(), false));
921 childNode.setUserObject(dataChild);
922 }
923 node.setUserObject(data);
924 // Ob package will never be modified, just reload deob view
925 this.deobfPanel.deobfClasses.reload();
926 }
927 // class rename
928 else if (data instanceof ClassEntry) {
929 this.controller.rename(new EntryReference<>((ClassEntry) prevData, ((ClassEntry) prevData).getFullName()), ((ClassEntry) data).getFullName(), false);
930 this.controller.sendPacket(new RenameC2SPacket((ClassEntry) prevData, ((ClassEntry) data).getFullName(), false));
931 }
932 }
933
934 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, String newName) {
935 String oldEntry = obfReference.entry.getContainingClass().getPackageName();
936 String newEntry = new ClassEntry(newName).getPackageName();
937 moveClassTree(obfReference, oldEntry == null, newEntry == null);
938 }
939
940 // TODO: getExpansionState will *not* actually update itself based on name changes!
941 public void moveClassTree(EntryReference<Entry<?>, Entry<?>> obfReference, boolean isOldOb, boolean isNewOb) {
942 ClassEntry classEntry = obfReference.entry.getContainingClass();
943
944 List<ClassSelector.StateEntry> stateDeobf = this.deobfPanel.deobfClasses.getExpansionState(this.deobfPanel.deobfClasses);
945 List<ClassSelector.StateEntry> stateObf = this.obfPanel.obfClasses.getExpansionState(this.obfPanel.obfClasses);
946
947 // Ob -> deob
948 if (!isNewOb) {
949 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
950 this.obfPanel.obfClasses.moveClassOut(classEntry);
951 this.deobfPanel.deobfClasses.reload();
952 this.obfPanel.obfClasses.reload();
953 }
954 // Deob -> ob
955 else if (!isOldOb) {
956 this.obfPanel.obfClasses.moveClassIn(classEntry);
957 this.deobfPanel.deobfClasses.moveClassOut(classEntry);
958 this.deobfPanel.deobfClasses.reload();
959 this.obfPanel.obfClasses.reload();
960 }
961 // Local move
962 else if (isOldOb) {
963 this.obfPanel.obfClasses.moveClassIn(classEntry);
964 this.obfPanel.obfClasses.reload();
965 } else {
966 this.deobfPanel.deobfClasses.moveClassIn(classEntry);
967 this.deobfPanel.deobfClasses.reload();
968 }
969
970 this.deobfPanel.deobfClasses.restoreExpansionState(this.deobfPanel.deobfClasses, stateDeobf);
971 this.obfPanel.obfClasses.restoreExpansionState(this.obfPanel.obfClasses, stateObf);
972 }
973
974 public PanelObf getObfPanel() {
975 return obfPanel;
976 }
977
978 public PanelDeobf getDeobfPanel() {
979 return deobfPanel;
980 }
981
982 public void setShouldNavigateOnClick(boolean shouldNavigateOnClick) {
983 this.shouldNavigateOnClick = shouldNavigateOnClick;
984 }
985
986 public SearchDialog getSearchDialog() {
987 if (searchDialog == null) {
988 searchDialog = new SearchDialog(this);
989 }
990 return searchDialog;
991 }
992
993
994 public MenuBar getMenuBar() {
995 return menuBar;
996 }
997
998 public void addMessage(Message message) {
999 JScrollBar verticalScrollBar = messageScrollPane.getVerticalScrollBar();
1000 boolean isAtBottom = verticalScrollBar.getValue() >= verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent();
1001 messageModel.addElement(message);
1002 if (isAtBottom) {
1003 SwingUtilities.invokeLater(() -> verticalScrollBar.setValue(verticalScrollBar.getMaximum() - verticalScrollBar.getModel().getExtent()));
1004 }
1005 statusLabel.setText(message.translate());
1006 }
1007
1008 public void setUserList(List<String> users) {
1009 userModel.clear();
1010 users.forEach(userModel::addElement);
1011 connectionStatusLabel.setText(String.format(I18n.translate("status.connected_user_count"), users.size()));
1012 }
1013
1014 private void sendMessage() {
1015 String text = chatBox.getText().trim();
1016 if (!text.isEmpty()) {
1017 getController().sendPacket(new MessageC2SPacket(text));
1018 }
1019 chatBox.setText("");
1020 }
1021
1022 /**
1023 * Updates the state of the UI elements (button text, enabled state, ...) to reflect the current program state.
1024 * This is a central place to update the UI state to prevent multiple code paths from changing the same state,
1025 * causing inconsistencies.
1026 */
1027 public void updateUiState() {
1028 menuBar.connectToServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.HOSTING);
1029 menuBar.connectToServerMenu.setText(I18n.translate(connectionState != ConnectionState.CONNECTED ? "menu.collab.connect" : "menu.collab.disconnect"));
1030 menuBar.startServerMenu.setEnabled(isJarOpen && connectionState != ConnectionState.CONNECTED);
1031 menuBar.startServerMenu.setText(I18n.translate(connectionState != ConnectionState.HOSTING ? "menu.collab.server.start" : "menu.collab.server.stop"));
1032
1033 menuBar.closeJarMenu.setEnabled(isJarOpen);
1034 menuBar.openMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1035 menuBar.saveMappingsMenu.setEnabled(isJarOpen && enigmaMappingsFileChooser.getSelectedFile() != null && connectionState != ConnectionState.CONNECTED);
1036 menuBar.saveMappingsMenus.forEach(item -> item.setEnabled(isJarOpen));
1037 menuBar.closeMappingsMenu.setEnabled(isJarOpen);
1038 menuBar.exportSourceMenu.setEnabled(isJarOpen);
1039 menuBar.exportJarMenu.setEnabled(isJarOpen);
1040
1041 connectionStatusLabel.setText(I18n.translate(connectionState == ConnectionState.NOT_CONNECTED ? "status.disconnected" : "status.connected"));
1042
1043 if (connectionState == ConnectionState.NOT_CONNECTED) {
1044 logSplit.setLeftComponent(null);
1045 splitRight.setRightComponent(tabs);
1046 } else {
1047 splitRight.setRightComponent(logSplit);
1048 logSplit.setLeftComponent(tabs);
1049 }
1050 }
1051
1052 public void setConnectionState(ConnectionState state) {
1053 connectionState = state;
1054 statusLabel.setText(I18n.translate("status.ready"));
1055 updateUiState();
1056 }
1057
1058}
diff --git a/src/main/java/cuchaz/enigma/gui/GuiController.java b/src/main/java/cuchaz/enigma/gui/GuiController.java
deleted file mode 100644
index cccc9e8..0000000
--- a/src/main/java/cuchaz/enigma/gui/GuiController.java
+++ /dev/null
@@ -1,729 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import com.google.common.collect.Lists;
15import com.google.common.util.concurrent.ThreadFactoryBuilder;
16import cuchaz.enigma.Enigma;
17import cuchaz.enigma.EnigmaProfile;
18import cuchaz.enigma.EnigmaProject;
19import cuchaz.enigma.analysis.*;
20import cuchaz.enigma.api.service.ObfuscationTestService;
21import cuchaz.enigma.bytecode.translators.SourceFixVisitor;
22import cuchaz.enigma.config.Config;
23import cuchaz.enigma.gui.dialog.ProgressDialog;
24import cuchaz.enigma.gui.stats.StatsGenerator;
25import cuchaz.enigma.gui.stats.StatsMember;
26import cuchaz.enigma.gui.util.History;
27import cuchaz.enigma.network.EnigmaClient;
28import cuchaz.enigma.network.EnigmaServer;
29import cuchaz.enigma.network.IntegratedEnigmaServer;
30import cuchaz.enigma.network.ServerPacketHandler;
31import cuchaz.enigma.network.packet.LoginC2SPacket;
32import cuchaz.enigma.network.packet.Packet;
33import cuchaz.enigma.source.*;
34import cuchaz.enigma.throwables.MappingParseException;
35import cuchaz.enigma.translation.Translator;
36import cuchaz.enigma.translation.mapping.*;
37import cuchaz.enigma.translation.mapping.serde.MappingFormat;
38import cuchaz.enigma.translation.mapping.tree.EntryTree;
39import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
40import cuchaz.enigma.translation.representation.entry.ClassEntry;
41import cuchaz.enigma.translation.representation.entry.Entry;
42import cuchaz.enigma.translation.representation.entry.FieldEntry;
43import cuchaz.enigma.translation.representation.entry.MethodEntry;
44import cuchaz.enigma.utils.I18n;
45import cuchaz.enigma.utils.Message;
46import cuchaz.enigma.utils.ReadableToken;
47import cuchaz.enigma.utils.Utils;
48import org.objectweb.asm.tree.ClassNode;
49
50import javax.annotation.Nullable;
51import javax.swing.JOptionPane;
52import javax.swing.SwingUtilities;
53import java.awt.*;
54import java.awt.event.ItemEvent;
55import java.io.*;
56import java.nio.file.Path;
57import java.util.Collection;
58import java.util.List;
59import java.util.Set;
60import java.util.concurrent.CompletableFuture;
61import java.util.concurrent.ExecutorService;
62import java.util.concurrent.Executors;
63import java.util.stream.Collectors;
64import java.util.stream.Stream;
65
66public class GuiController {
67 private static final ExecutorService DECOMPILER_SERVICE = Executors.newSingleThreadExecutor(
68 new ThreadFactoryBuilder()
69 .setDaemon(true)
70 .setNameFormat("decompiler-thread")
71 .build()
72 );
73
74 private final Gui gui;
75 public final Enigma enigma;
76
77 public EnigmaProject project;
78 private DecompilerService decompilerService;
79 private Decompiler decompiler;
80 private IndexTreeBuilder indexTreeBuilder;
81
82 private Path loadedMappingPath;
83 private MappingFormat loadedMappingFormat;
84
85 private DecompiledClassSource currentSource;
86 private Source uncommentedSource;
87
88 private EnigmaClient client;
89 private EnigmaServer server;
90
91 public GuiController(Gui gui, EnigmaProfile profile) {
92 this.gui = gui;
93 this.enigma = Enigma.builder()
94 .setProfile(profile)
95 .build();
96
97 decompilerService = Config.getInstance().decompiler.service;
98 }
99
100 public boolean isDirty() {
101 return project != null && project.getMapper().isDirty();
102 }
103
104 public CompletableFuture<Void> openJar(final Path jarPath) {
105 this.gui.onStartOpenJar();
106
107 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
108 project = enigma.openJar(jarPath, progress);
109 indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex());
110 decompiler = createDecompiler();
111 gui.onFinishOpenJar(jarPath.getFileName().toString());
112 refreshClasses();
113 });
114 }
115
116 private Decompiler createDecompiler() {
117 return decompilerService.create(name -> {
118 ClassNode node = project.getClassCache().getClassNode(name);
119
120 if (node == null) {
121 return null;
122 }
123
124 ClassNode fixedNode = new ClassNode();
125 node.accept(new SourceFixVisitor(Utils.ASM_VERSION, fixedNode, project.getJarIndex()));
126 return fixedNode;
127 }, new SourceSettings(true, true));
128 }
129
130 public void closeJar() {
131 this.project = null;
132 this.gui.onCloseJar();
133 }
134
135 public CompletableFuture<Void> openMappings(MappingFormat format, Path path) {
136 if (project == null) return CompletableFuture.completedFuture(null);
137
138 gui.setMappingsFile(path);
139
140 return ProgressDialog.runOffThread(gui.getFrame(), progress -> {
141 try {
142 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
143
144 EntryTree<EntryMapping> mappings = format.read(path, progress, saveParameters);
145 project.setMappings(mappings);
146
147 loadedMappingFormat = format;
148 loadedMappingPath = path;
149
150 refreshClasses();
151 refreshCurrentClass();
152 } catch (MappingParseException e) {
153 JOptionPane.showMessageDialog(gui.getFrame(), e.getMessage());
154 }
155 });
156 }
157
158 public void openMappings(EntryTree<EntryMapping> mappings) {
159 if (project == null) return;
160
161 project.setMappings(mappings);
162 refreshClasses();
163 refreshCurrentClass();
164 }
165
166 public CompletableFuture<Void> saveMappings(Path path) {
167 return saveMappings(path, loadedMappingFormat);
168 }
169
170 public CompletableFuture<Void> saveMappings(Path path, MappingFormat format) {
171 if (project == null) return CompletableFuture.completedFuture(null);
172
173 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
174 EntryRemapper mapper = project.getMapper();
175 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
176
177 MappingDelta<EntryMapping> delta = mapper.takeMappingDelta();
178 boolean saveAll = !path.equals(loadedMappingPath);
179
180 loadedMappingFormat = format;
181 loadedMappingPath = path;
182
183 if (saveAll) {
184 format.write(mapper.getObfToDeobf(), path, progress, saveParameters);
185 } else {
186 format.write(mapper.getObfToDeobf(), delta, path, progress, saveParameters);
187 }
188 });
189 }
190
191 public void closeMappings() {
192 if (project == null) return;
193
194 project.setMappings(null);
195
196 this.gui.setMappingsFile(null);
197 refreshClasses();
198 refreshCurrentClass();
199 }
200
201 public CompletableFuture<Void> dropMappings() {
202 if (project == null) return CompletableFuture.completedFuture(null);
203
204 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> project.dropMappings(progress));
205 }
206
207 public CompletableFuture<Void> exportSource(final Path path) {
208 if (project == null) return CompletableFuture.completedFuture(null);
209
210 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
211 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
212 EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService);
213
214 source.write(path, progress);
215 });
216 }
217
218 public CompletableFuture<Void> exportJar(final Path path) {
219 if (project == null) return CompletableFuture.completedFuture(null);
220
221 return ProgressDialog.runOffThread(this.gui.getFrame(), progress -> {
222 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
223 jar.write(path, progress);
224 });
225 }
226
227 public Token getToken(int pos) {
228 if (this.currentSource == null) {
229 return null;
230 }
231 return this.currentSource.getIndex().getReferenceToken(pos);
232 }
233
234 @Nullable
235 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
236 if (this.currentSource == null) {
237 return null;
238 }
239 return this.currentSource.getIndex().getReference(token);
240 }
241
242 public ReadableToken getReadableToken(Token token) {
243 if (this.currentSource == null) {
244 return null;
245 }
246
247 SourceIndex index = this.currentSource.getIndex();
248 return new ReadableToken(
249 index.getLineNumber(token.start),
250 index.getColumnNumber(token.start),
251 index.getColumnNumber(token.end)
252 );
253 }
254
255 /**
256 * Navigates to the declaration with respect to navigation history
257 *
258 * @param entry the entry whose declaration will be navigated to
259 */
260 public void openDeclaration(Entry<?> entry) {
261 if (entry == null) {
262 throw new IllegalArgumentException("Entry cannot be null!");
263 }
264 openReference(new EntryReference<>(entry, entry.getName()));
265 }
266
267 /**
268 * Navigates to the reference with respect to navigation history
269 *
270 * @param reference the reference
271 */
272 public void openReference(EntryReference<Entry<?>, Entry<?>> reference) {
273 if (reference == null) {
274 throw new IllegalArgumentException("Reference cannot be null!");
275 }
276 if (this.gui.referenceHistory == null) {
277 this.gui.referenceHistory = new History<>(reference);
278 } else {
279 if (!reference.equals(this.gui.referenceHistory.getCurrent())) {
280 this.gui.referenceHistory.push(reference);
281 }
282 }
283 setReference(reference);
284 }
285
286 /**
287 * Navigates to the reference without modifying history. If the class is not currently loaded, it will be loaded.
288 *
289 * @param reference the reference
290 */
291 private void setReference(EntryReference<Entry<?>, Entry<?>> reference) {
292 // get the reference target class
293 ClassEntry classEntry = reference.getLocationClassEntry();
294 if (!project.isRenamable(classEntry)) {
295 throw new IllegalArgumentException("Obfuscated class " + classEntry + " was not found in the jar!");
296 }
297
298 if (this.currentSource == null || !this.currentSource.getEntry().equals(classEntry)) {
299 // deobfuscate the class, then navigate to the reference
300 loadClass(classEntry, () -> showReference(reference));
301 } else {
302 showReference(reference);
303 }
304 }
305
306 /**
307 * Navigates to the reference without modifying history. Assumes the class is loaded.
308 *
309 * @param reference
310 */
311 private void showReference(EntryReference<Entry<?>, Entry<?>> reference) {
312 Collection<Token> tokens = getTokensForReference(reference);
313 if (tokens.isEmpty()) {
314 // DEBUG
315 System.err.println(String.format("WARNING: no tokens found for %s in %s", reference, this.currentSource.getEntry()));
316 } else {
317 this.gui.showTokens(tokens);
318 }
319 }
320
321 public Collection<Token> getTokensForReference(EntryReference<Entry<?>, Entry<?>> reference) {
322 EntryRemapper mapper = this.project.getMapper();
323
324 SourceIndex index = this.currentSource.getIndex();
325 return mapper.getObfResolver().resolveReference(reference, ResolutionStrategy.RESOLVE_CLOSEST)
326 .stream()
327 .flatMap(r -> index.getReferenceTokens(r).stream())
328 .collect(Collectors.toList());
329 }
330
331 public void openPreviousReference() {
332 if (hasPreviousReference()) {
333 setReference(gui.referenceHistory.goBack());
334 }
335 }
336
337 public boolean hasPreviousReference() {
338 return gui.referenceHistory != null && gui.referenceHistory.canGoBack();
339 }
340
341 public void openNextReference() {
342 if (hasNextReference()) {
343 setReference(gui.referenceHistory.goForward());
344 }
345 }
346
347 public boolean hasNextReference() {
348 return gui.referenceHistory != null && gui.referenceHistory.canGoForward();
349 }
350
351 public void navigateTo(Entry<?> entry) {
352 if (!project.isRenamable(entry)) {
353 // entry is not in the jar. Ignore it
354 return;
355 }
356 openDeclaration(entry);
357 }
358
359 public void navigateTo(EntryReference<Entry<?>, Entry<?>> reference) {
360 if (!project.isRenamable(reference.getLocationClassEntry())) {
361 return;
362 }
363 openReference(reference);
364 }
365
366 private void refreshClasses() {
367 List<ClassEntry> obfClasses = Lists.newArrayList();
368 List<ClassEntry> deobfClasses = Lists.newArrayList();
369 this.addSeparatedClasses(obfClasses, deobfClasses);
370 this.gui.setObfClasses(obfClasses);
371 this.gui.setDeobfClasses(deobfClasses);
372 }
373
374 public void addSeparatedClasses(List<ClassEntry> obfClasses, List<ClassEntry> deobfClasses) {
375 EntryRemapper mapper = project.getMapper();
376
377 Collection<ClassEntry> classes = project.getJarIndex().getEntryIndex().getClasses();
378 Stream<ClassEntry> visibleClasses = classes.stream()
379 .filter(entry -> !entry.isInnerClass());
380
381 visibleClasses.forEach(entry -> {
382 ClassEntry deobfEntry = mapper.deobfuscate(entry);
383
384 List<ObfuscationTestService> obfService = enigma.getServices().get(ObfuscationTestService.TYPE);
385 boolean obfuscated = deobfEntry.equals(entry);
386
387 if (obfuscated && !obfService.isEmpty()) {
388 if (obfService.stream().anyMatch(service -> service.testDeobfuscated(entry))) {
389 obfuscated = false;
390 }
391 }
392
393 if (obfuscated) {
394 obfClasses.add(entry);
395 } else {
396 deobfClasses.add(entry);
397 }
398 });
399 }
400
401 public void refreshCurrentClass() {
402 refreshCurrentClass(null);
403 }
404
405 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference) {
406 refreshCurrentClass(reference, RefreshMode.MINIMAL);
407 }
408
409 private void refreshCurrentClass(EntryReference<Entry<?>, Entry<?>> reference, RefreshMode mode) {
410 if (currentSource != null) {
411 if (reference == null) {
412 int obfSelectionStart = currentSource.getObfuscatedOffset(gui.editor.getSelectionStart());
413 int obfSelectionEnd = currentSource.getObfuscatedOffset(gui.editor.getSelectionEnd());
414
415 Rectangle viewportBounds = gui.sourceScroller.getViewport().getViewRect();
416 // Here we pick an "anchor position", which we want to stay in the same vertical location on the screen after the new text has been set
417 int anchorModelPos = gui.editor.getSelectionStart();
418 Rectangle anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
419 if (anchorViewPos.y < viewportBounds.y || anchorViewPos.y >= viewportBounds.y + viewportBounds.height) {
420 anchorModelPos = gui.editor.viewToModel(new Point(0, viewportBounds.y));
421 anchorViewPos = Utils.safeModelToView(gui.editor, anchorModelPos);
422 }
423 int obfAnchorPos = currentSource.getObfuscatedOffset(anchorModelPos);
424 Rectangle anchorViewPos_f = anchorViewPos;
425 int scrollX = gui.sourceScroller.getHorizontalScrollBar().getValue();
426
427 loadClass(currentSource.getEntry(), () -> SwingUtilities.invokeLater(() -> {
428 int newAnchorModelPos = currentSource.getDeobfuscatedOffset(obfAnchorPos);
429 Rectangle newAnchorViewPos = Utils.safeModelToView(gui.editor, newAnchorModelPos);
430 int newScrollY = newAnchorViewPos.y - (anchorViewPos_f.y - viewportBounds.y);
431
432 gui.editor.select(currentSource.getDeobfuscatedOffset(obfSelectionStart), currentSource.getDeobfuscatedOffset(obfSelectionEnd));
433 // Changing the selection scrolls to the caret position inside a SwingUtilities.invokeLater call, so
434 // we need to wrap our change to the scroll position inside another invokeLater so it happens after
435 // the caret's own scrolling.
436 SwingUtilities.invokeLater(() -> {
437 gui.sourceScroller.getHorizontalScrollBar().setValue(Math.min(scrollX, gui.sourceScroller.getHorizontalScrollBar().getMaximum()));
438 gui.sourceScroller.getVerticalScrollBar().setValue(Math.min(newScrollY, gui.sourceScroller.getVerticalScrollBar().getMaximum()));
439 });
440 }), mode);
441 } else {
442 loadClass(currentSource.getEntry(), () -> showReference(reference), mode);
443 }
444 }
445 }
446
447 private void loadClass(ClassEntry classEntry, Runnable callback) {
448 loadClass(classEntry, callback, RefreshMode.MINIMAL);
449 }
450
451 private void loadClass(ClassEntry classEntry, Runnable callback, RefreshMode mode) {
452 ClassEntry targetClass = classEntry.getOutermostClass();
453
454 boolean requiresDecompile = mode == RefreshMode.FULL || currentSource == null || !currentSource.getEntry().equals(targetClass);
455 if (requiresDecompile) {
456 currentSource = null; // Or the GUI may try to find a nonexistent token
457 gui.setEditorText(I18n.translate("info_panel.editor.class.decompiling"));
458 }
459
460 DECOMPILER_SERVICE.submit(() -> {
461 try {
462 if (requiresDecompile || mode == RefreshMode.JAVADOCS) {
463 currentSource = decompileSource(targetClass, mode == RefreshMode.JAVADOCS);
464 }
465
466 remapSource(project.getMapper().getDeobfuscator());
467 callback.run();
468 } catch (Throwable t) {
469 System.err.println("An exception was thrown while decompiling class " + classEntry.getFullName());
470 t.printStackTrace(System.err);
471 }
472 });
473 }
474
475 private DecompiledClassSource decompileSource(ClassEntry targetClass, boolean onlyRefreshJavadocs) {
476 try {
477 if (!onlyRefreshJavadocs || currentSource == null || !currentSource.getEntry().equals(targetClass)) {
478 uncommentedSource = decompiler.getSource(targetClass.getFullName());
479 }
480
481 Source source = uncommentedSource.addJavadocs(project.getMapper());
482
483 if (source == null) {
484 gui.setEditorText(I18n.translate("info_panel.editor.class.not_found") + " " + targetClass);
485 return DecompiledClassSource.text(targetClass, "Unable to find class");
486 }
487
488 SourceIndex index = source.index();
489 index.resolveReferences(project.getMapper().getObfResolver());
490
491 return new DecompiledClassSource(targetClass, index);
492 } catch (Throwable t) {
493 StringWriter traceWriter = new StringWriter();
494 t.printStackTrace(new PrintWriter(traceWriter));
495
496 return DecompiledClassSource.text(targetClass, traceWriter.toString());
497 }
498 }
499
500 private void remapSource(Translator translator) {
501 if (currentSource == null) {
502 return;
503 }
504
505 currentSource.remapSource(project, translator);
506
507 gui.setEditorTheme(Config.getInstance().lookAndFeel);
508 gui.setSource(currentSource);
509 }
510
511 public void modifierChange(ItemEvent event) {
512 if (event.getStateChange() == ItemEvent.SELECTED) {
513 EntryRemapper mapper = project.getMapper();
514 Entry<?> entry = gui.cursorReference.entry;
515 AccessModifier modifier = (AccessModifier) event.getItem();
516
517 EntryMapping mapping = mapper.getDeobfMapping(entry);
518 if (mapping != null) {
519 mapper.mapFromObf(entry, new EntryMapping(mapping.getTargetName(), modifier));
520 } else {
521 mapper.mapFromObf(entry, new EntryMapping(entry.getName(), modifier));
522 }
523
524 refreshCurrentClass();
525 }
526 }
527
528 public ClassInheritanceTreeNode getClassInheritance(ClassEntry entry) {
529 Translator translator = project.getMapper().getDeobfuscator();
530 ClassInheritanceTreeNode rootNode = indexTreeBuilder.buildClassInheritance(translator, entry);
531 return ClassInheritanceTreeNode.findNode(rootNode, entry);
532 }
533
534 public ClassImplementationsTreeNode getClassImplementations(ClassEntry entry) {
535 Translator translator = project.getMapper().getDeobfuscator();
536 return this.indexTreeBuilder.buildClassImplementations(translator, entry);
537 }
538
539 public MethodInheritanceTreeNode getMethodInheritance(MethodEntry entry) {
540 Translator translator = project.getMapper().getDeobfuscator();
541 MethodInheritanceTreeNode rootNode = indexTreeBuilder.buildMethodInheritance(translator, entry);
542 return MethodInheritanceTreeNode.findNode(rootNode, entry);
543 }
544
545 public MethodImplementationsTreeNode getMethodImplementations(MethodEntry entry) {
546 Translator translator = project.getMapper().getDeobfuscator();
547 List<MethodImplementationsTreeNode> rootNodes = indexTreeBuilder.buildMethodImplementations(translator, entry);
548 if (rootNodes.isEmpty()) {
549 return null;
550 }
551 if (rootNodes.size() > 1) {
552 System.err.println("WARNING: Method " + entry + " implements multiple interfaces. Only showing first one.");
553 }
554 return MethodImplementationsTreeNode.findNode(rootNodes.get(0), entry);
555 }
556
557 public ClassReferenceTreeNode getClassReferences(ClassEntry entry) {
558 Translator deobfuscator = project.getMapper().getDeobfuscator();
559 ClassReferenceTreeNode rootNode = new ClassReferenceTreeNode(deobfuscator, entry);
560 rootNode.load(project.getJarIndex(), true);
561 return rootNode;
562 }
563
564 public FieldReferenceTreeNode getFieldReferences(FieldEntry entry) {
565 Translator translator = project.getMapper().getDeobfuscator();
566 FieldReferenceTreeNode rootNode = new FieldReferenceTreeNode(translator, entry);
567 rootNode.load(project.getJarIndex(), true);
568 return rootNode;
569 }
570
571 public MethodReferenceTreeNode getMethodReferences(MethodEntry entry, boolean recursive) {
572 Translator translator = project.getMapper().getDeobfuscator();
573 MethodReferenceTreeNode rootNode = new MethodReferenceTreeNode(translator, entry);
574 rootNode.load(project.getJarIndex(), true, recursive);
575 return rootNode;
576 }
577
578 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree) {
579 rename(reference, newName, refreshClassTree, true);
580 }
581
582 public void rename(EntryReference<Entry<?>, Entry<?>> reference, String newName, boolean refreshClassTree, boolean jumpToReference) {
583 Entry<?> entry = reference.getNameableEntry();
584 project.getMapper().mapFromObf(entry, new EntryMapping(newName));
585
586 if (refreshClassTree && reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
587 this.gui.moveClassTree(reference, newName);
588
589 refreshCurrentClass(jumpToReference ? reference : null);
590 }
591
592 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference) {
593 removeMapping(reference, true);
594 }
595
596 public void removeMapping(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
597 project.getMapper().removeByObf(reference.getNameableEntry());
598
599 if (reference.entry instanceof ClassEntry)
600 this.gui.moveClassTree(reference, false, true);
601 refreshCurrentClass(jumpToReference ? reference : null);
602 }
603
604 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs) {
605 changeDocs(reference, updatedDocs, true);
606 }
607
608 public void changeDocs(EntryReference<Entry<?>, Entry<?>> reference, String updatedDocs, boolean jumpToReference) {
609 changeDoc(reference.entry, Utils.isBlank(updatedDocs) ? null : updatedDocs);
610
611 refreshCurrentClass(jumpToReference ? reference : null, RefreshMode.JAVADOCS);
612 }
613
614 private void changeDoc(Entry<?> obfEntry, String newDoc) {
615 EntryRemapper mapper = project.getMapper();
616 if (mapper.getDeobfMapping(obfEntry) == null) {
617 markAsDeobfuscated(obfEntry, false); // NPE
618 }
619 mapper.mapFromObf(obfEntry, mapper.getDeobfMapping(obfEntry).withDocs(newDoc), false);
620 }
621
622 private void markAsDeobfuscated(Entry<?> obfEntry, boolean renaming) {
623 EntryRemapper mapper = project.getMapper();
624 mapper.mapFromObf(obfEntry, new EntryMapping(mapper.deobfuscate(obfEntry).getName()), renaming);
625 }
626
627 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference) {
628 markAsDeobfuscated(reference, true);
629 }
630
631 public void markAsDeobfuscated(EntryReference<Entry<?>, Entry<?>> reference, boolean jumpToReference) {
632 EntryRemapper mapper = project.getMapper();
633 Entry<?> entry = reference.getNameableEntry();
634 mapper.mapFromObf(entry, new EntryMapping(mapper.deobfuscate(entry).getName()));
635
636 if (reference.entry instanceof ClassEntry && !((ClassEntry) reference.entry).isInnerClass())
637 this.gui.moveClassTree(reference, true, false);
638
639 refreshCurrentClass(jumpToReference ? reference : null);
640 }
641
642 public void openStats(Set<StatsMember> includedMembers) {
643 ProgressDialog.runOffThread(gui.getFrame(), progress -> {
644 String data = new StatsGenerator(project).generate(progress, includedMembers);
645
646 try {
647 File statsFile = File.createTempFile("stats", ".html");
648
649 try (FileWriter w = new FileWriter(statsFile)) {
650 w.write(
651 Utils.readResourceToString("/stats.html")
652 .replace("/*data*/", data)
653 );
654 }
655
656 Desktop.getDesktop().open(statsFile);
657 } catch (IOException e) {
658 throw new Error(e);
659 }
660 });
661 }
662
663 public void setDecompiler(DecompilerService service) {
664 uncommentedSource = null;
665 decompilerService = service;
666 decompiler = createDecompiler();
667 refreshCurrentClass(null, RefreshMode.FULL);
668 }
669
670 public EnigmaClient getClient() {
671 return client;
672 }
673
674 public EnigmaServer getServer() {
675 return server;
676 }
677
678 public void createClient(String username, String ip, int port, char[] password) throws IOException {
679 client = new EnigmaClient(this, ip, port);
680 client.connect();
681 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, username));
682 gui.setConnectionState(ConnectionState.CONNECTED);
683 }
684
685 public void createServer(int port, char[] password) throws IOException {
686 server = new IntegratedEnigmaServer(project.getJarChecksum(), password, EntryRemapper.mapped(project.getJarIndex(), new HashEntryTree<>(project.getMapper().getObfToDeobf())), port);
687 server.start();
688 client = new EnigmaClient(this, "127.0.0.1", port);
689 client.connect();
690 client.sendPacket(new LoginC2SPacket(project.getJarChecksum(), password, EnigmaServer.OWNER_USERNAME));
691 gui.setConnectionState(ConnectionState.HOSTING);
692 }
693
694 public synchronized void disconnectIfConnected(String reason) {
695 if (client == null && server == null) {
696 return;
697 }
698
699 if (client != null) {
700 client.disconnect();
701 }
702 if (server != null) {
703 server.stop();
704 }
705 client = null;
706 server = null;
707 SwingUtilities.invokeLater(() -> {
708 if (reason != null) {
709 JOptionPane.showMessageDialog(gui.getFrame(), I18n.translate(reason), I18n.translate("disconnect.disconnected"), JOptionPane.INFORMATION_MESSAGE);
710 }
711 gui.setConnectionState(ConnectionState.NOT_CONNECTED);
712 });
713 }
714
715 public void sendPacket(Packet<ServerPacketHandler> packet) {
716 if (client != null) {
717 client.sendPacket(packet);
718 }
719 }
720
721 public void addMessage(Message message) {
722 gui.addMessage(message);
723 }
724
725 public void updateUserList(List<String> users) {
726 gui.setUserList(users);
727 }
728
729}
diff --git a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java
deleted file mode 100644
index c9e38cb..0000000
--- a/src/main/java/cuchaz/enigma/gui/MessageListCellRenderer.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package cuchaz.enigma.gui;
2
3import java.awt.Component;
4
5import javax.swing.DefaultListCellRenderer;
6import javax.swing.JList;
7
8import cuchaz.enigma.utils.Message;
9
10// For now, just render the translated text.
11// TODO: Icons or something later?
12public class MessageListCellRenderer extends DefaultListCellRenderer {
13
14 @Override
15 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
16 super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
17 Message message = (Message) value;
18 if (message != null) {
19 setText(message.translate());
20 }
21 return this;
22 }
23
24}
diff --git a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java b/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
deleted file mode 100644
index 05d90a9..0000000
--- a/src/main/java/cuchaz/enigma/gui/MethodTreeCellRenderer.java
+++ /dev/null
@@ -1,42 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
15import cuchaz.enigma.config.Config;
16
17import javax.swing.*;
18import javax.swing.tree.TreeCellRenderer;
19import java.awt.*;
20
21class MethodTreeCellRenderer implements TreeCellRenderer {
22
23 private final TreeCellRenderer parent;
24
25 MethodTreeCellRenderer(TreeCellRenderer parent) {
26 this.parent = parent;
27 }
28
29 @Override
30 public Component getTreeCellRendererComponent(JTree tree, Object value, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
31 Component ret = parent.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
32 Config config = Config.getInstance();
33 if (!(value instanceof MethodInheritanceTreeNode) || ((MethodInheritanceTreeNode) value).isImplemented()) {
34 ret.setForeground(new Color(config.defaultTextColor));
35 ret.setFont(ret.getFont().deriveFont(Font.PLAIN));
36 } else {
37 ret.setForeground(new Color(config.numberColor));
38 ret.setFont(ret.getFont().deriveFont(Font.ITALIC));
39 }
40 return ret;
41 }
42}
diff --git a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java b/src/main/java/cuchaz/enigma/gui/QuickFindAction.java
deleted file mode 100644
index b7fa2eb..0000000
--- a/src/main/java/cuchaz/enigma/gui/QuickFindAction.java
+++ /dev/null
@@ -1,45 +0,0 @@
1package cuchaz.enigma.gui;
2
3import de.sciss.syntaxpane.SyntaxDocument;
4import de.sciss.syntaxpane.actions.DefaultSyntaxAction;
5
6import javax.swing.text.JTextComponent;
7import java.awt.event.ActionEvent;
8
9public final class QuickFindAction extends DefaultSyntaxAction {
10 public QuickFindAction() {
11 super("quick-find");
12 }
13
14 @Override
15 public void actionPerformed(JTextComponent target, SyntaxDocument document, int dot, ActionEvent event) {
16 Data data = Data.get(target);
17 data.showFindDialog(target);
18 }
19
20 private static class Data {
21 private static final String KEY = "enigma-find-data";
22 private EnigmaQuickFindDialog findDialog;
23
24 private Data() {
25 }
26
27 public static Data get(JTextComponent target) {
28 Object o = target.getDocument().getProperty(KEY);
29 if (o instanceof Data) {
30 return (Data) o;
31 }
32
33 Data data = new Data();
34 target.getDocument().putProperty(KEY, data);
35 return data;
36 }
37
38 public void showFindDialog(JTextComponent target) {
39 if (findDialog == null) {
40 findDialog = new EnigmaQuickFindDialog(target);
41 }
42 findDialog.showFor(target);
43 }
44 }
45}
diff --git a/src/main/java/cuchaz/enigma/gui/RefreshMode.java b/src/main/java/cuchaz/enigma/gui/RefreshMode.java
deleted file mode 100644
index 87cb83b..0000000
--- a/src/main/java/cuchaz/enigma/gui/RefreshMode.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui;
2
3public enum RefreshMode {
4 MINIMAL,
5 JAVADOCS,
6 FULL
7}
diff --git a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java b/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
deleted file mode 100644
index f38f44e..0000000
--- a/src/main/java/cuchaz/enigma/gui/SourceRemapper.java
+++ /dev/null
@@ -1,64 +0,0 @@
1package cuchaz.enigma.gui;
2
3import cuchaz.enigma.analysis.Token;
4
5import java.util.HashMap;
6import java.util.Map;
7
8public class SourceRemapper {
9 private final String source;
10 private final Iterable<Token> tokens;
11
12 public SourceRemapper(String source, Iterable<Token> tokens) {
13 this.source = source;
14 this.tokens = tokens;
15 }
16
17 public Result remap(Remapper remapper) {
18 StringBuffer remappedSource = new StringBuffer(source);
19 Map<Token, Token> remappedTokens = new HashMap<>();
20
21 int accumulatedOffset = 0;
22 for (Token token : tokens) {
23 Token movedToken = token.move(accumulatedOffset);
24
25 String remappedName = remapper.remap(token, movedToken);
26 if (remappedName != null) {
27 accumulatedOffset += movedToken.getRenameOffset(remappedName);
28 movedToken.rename(remappedSource, remappedName);
29 }
30
31 if (!token.equals(movedToken)) {
32 remappedTokens.put(token, movedToken);
33 }
34 }
35
36 return new Result(remappedSource.toString(), remappedTokens);
37 }
38
39 public static class Result {
40 private final String remappedSource;
41 private final Map<Token, Token> remappedTokens;
42
43 Result(String remappedSource, Map<Token, Token> remappedTokens) {
44 this.remappedSource = remappedSource;
45 this.remappedTokens = remappedTokens;
46 }
47
48 public String getSource() {
49 return remappedSource;
50 }
51
52 public Token getRemappedToken(Token token) {
53 return remappedTokens.getOrDefault(token, token);
54 }
55
56 public boolean isEmpty() {
57 return remappedTokens.isEmpty();
58 }
59 }
60
61 public interface Remapper {
62 String remap(Token token, Token movedToken);
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
deleted file mode 100644
index 7375111..0000000
--- a/src/main/java/cuchaz/enigma/gui/TokenListCellRenderer.java
+++ /dev/null
@@ -1,35 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui;
13
14import cuchaz.enigma.analysis.Token;
15
16import javax.swing.*;
17import java.awt.*;
18
19public class TokenListCellRenderer implements ListCellRenderer<Token> {
20
21 private GuiController controller;
22 private DefaultListCellRenderer defaultRenderer;
23
24 public TokenListCellRenderer(GuiController controller) {
25 this.controller = controller;
26 this.defaultRenderer = new DefaultListCellRenderer();
27 }
28
29 @Override
30 public Component getListCellRendererComponent(JList<? extends Token> list, Token token, int index, boolean isSelected, boolean hasFocus) {
31 JLabel label = (JLabel) this.defaultRenderer.getListCellRendererComponent(list, token, index, isSelected, hasFocus);
32 label.setText(this.controller.getReadableToken(token).toString());
33 return label;
34 }
35}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
deleted file mode 100644
index 43b8265..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/AboutDialog.java
+++ /dev/null
@@ -1,69 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.utils.I18n;
16import cuchaz.enigma.gui.util.ScaleUtil;
17import cuchaz.enigma.utils.Utils;
18
19import javax.swing.*;
20import java.awt.*;
21import java.io.IOException;
22
23public class AboutDialog {
24
25 public static void show(JFrame parent) {
26 // init frame
27 final JFrame frame = new JFrame(String.format(I18n.translate("menu.help.about.title"), Constants.NAME));
28 final Container pane = frame.getContentPane();
29 pane.setLayout(new FlowLayout());
30
31 // load the content
32 try {
33 String html = Utils.readResourceToString("/about.html");
34 html = String.format(html, Constants.NAME, Constants.VERSION);
35 JLabel label = new JLabel(html);
36 label.setHorizontalAlignment(JLabel.CENTER);
37 pane.add(label);
38 } catch (IOException ex) {
39 throw new Error(ex);
40 }
41
42 // show the link
43 String html = "<html><a href=\"%s\">%s</a></html>";
44 html = String.format(html, Constants.URL, Constants.URL);
45 JButton link = new JButton(html);
46 link.addActionListener(event -> Utils.openUrl(Constants.URL));
47 link.setBorderPainted(false);
48 link.setOpaque(false);
49 link.setBackground(Color.WHITE);
50 link.setCursor(new Cursor(Cursor.HAND_CURSOR));
51 link.setFocusable(false);
52 JPanel linkPanel = new JPanel();
53 linkPanel.add(link);
54 pane.add(linkPanel);
55
56 // show ok button
57 JButton okButton = new JButton(I18n.translate("menu.help.about.ok"));
58 pane.add(okButton);
59 okButton.addActionListener(arg0 -> frame.dispose());
60
61 // show the frame
62 pane.doLayout();
63 frame.setSize(ScaleUtil.getDimension(400, 220));
64 frame.setResizable(false);
65 frame.setLocationRelativeTo(parent);
66 frame.setVisible(true);
67 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
68 }
69}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
deleted file mode 100644
index 64219ab..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ChangeDialog.java
+++ /dev/null
@@ -1,50 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import java.awt.BorderLayout;
4import java.awt.event.KeyAdapter;
5import java.awt.event.KeyEvent;
6
7import javax.swing.JButton;
8import javax.swing.JFrame;
9import javax.swing.JLabel;
10import javax.swing.JPanel;
11
12import cuchaz.enigma.gui.Gui;
13import cuchaz.enigma.utils.I18n;
14
15public class ChangeDialog {
16
17 public static void show(Gui gui) {
18 // init frame
19 JFrame frame = new JFrame(I18n.translate("menu.view.change.title"));
20 JPanel textPanel = new JPanel();
21 JPanel buttonPanel = new JPanel();
22 frame.setLayout(new BorderLayout());
23 frame.add(BorderLayout.NORTH, textPanel);
24 frame.add(BorderLayout.SOUTH, buttonPanel);
25
26 // show text
27 JLabel text = new JLabel((I18n.translate("menu.view.change.summary")));
28 text.setHorizontalAlignment(JLabel.CENTER);
29 textPanel.add(text);
30
31 // show ok button
32 JButton okButton = new JButton(I18n.translate("menu.view.change.ok"));
33 buttonPanel.add(okButton);
34 okButton.addActionListener(event -> frame.dispose());
35 okButton.addKeyListener(new KeyAdapter() {
36 @Override
37 public void keyPressed(KeyEvent e) {
38 if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
39 frame.dispose();
40 }
41 }
42 });
43
44 // show the frame
45 frame.pack();
46 frame.setVisible(true);
47 frame.setResizable(false);
48 frame.setLocationRelativeTo(gui.getFrame());
49 }
50}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
deleted file mode 100644
index c5f505c..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ConnectToServerDialog.java
+++ /dev/null
@@ -1,82 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.Frame;
8
9public class ConnectToServerDialog {
10
11 public static Result show(Frame parentComponent) {
12 JTextField usernameField = new JTextField(System.getProperty("user.name"), 20);
13 JPanel usernameRow = new JPanel();
14 usernameRow.add(new JLabel(I18n.translate("prompt.connect.username")));
15 usernameRow.add(usernameField);
16 JTextField ipField = new JTextField(20);
17 JPanel ipRow = new JPanel();
18 ipRow.add(new JLabel(I18n.translate("prompt.connect.ip")));
19 ipRow.add(ipField);
20 JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10);
21 JPanel portRow = new JPanel();
22 portRow.add(new JLabel(I18n.translate("prompt.port")));
23 portRow.add(portField);
24 JPasswordField passwordField = new JPasswordField(20);
25 JPanel passwordRow = new JPanel();
26 passwordRow.add(new JLabel(I18n.translate("prompt.password")));
27 passwordRow.add(passwordField);
28
29 int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{usernameRow, ipRow, portRow, passwordRow}, I18n.translate("prompt.connect.title"), JOptionPane.OK_CANCEL_OPTION);
30 if (response != JOptionPane.OK_OPTION) {
31 return null;
32 }
33
34 String username = usernameField.getText();
35 String ip = ipField.getText();
36 int port;
37 try {
38 port = Integer.parseInt(portField.getText());
39 } catch (NumberFormatException e) {
40 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE);
41 return null;
42 }
43 if (port < 0 || port >= 65536) {
44 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.connect.title"), JOptionPane.ERROR_MESSAGE);
45 return null;
46 }
47 char[] password = passwordField.getPassword();
48
49 return new Result(username, ip, port, password);
50 }
51
52 public static class Result {
53 private final String username;
54 private final String ip;
55 private final int port;
56 private final char[] password;
57
58 public Result(String username, String ip, int port, char[] password) {
59 this.username = username;
60 this.ip = ip;
61 this.port = port;
62 this.password = password;
63 }
64
65 public String getUsername() {
66 return username;
67 }
68
69 public String getIp() {
70 return ip;
71 }
72
73 public int getPort() {
74 return port;
75 }
76
77 public char[] getPassword() {
78 return password;
79 }
80 }
81
82}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
deleted file mode 100644
index 908b42e..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/CrashDialog.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.utils.I18n;
16import cuchaz.enigma.gui.util.ScaleUtil;
17import cuchaz.enigma.utils.Utils;
18
19import javax.swing.*;
20import java.awt.*;
21import java.io.PrintWriter;
22import java.io.StringWriter;
23import java.io.FileWriter;
24import java.io.File;
25import java.io.IOException;
26
27public class CrashDialog {
28
29 private static CrashDialog instance = null;
30
31 private JFrame frame;
32 private JTextArea text;
33
34 private CrashDialog(JFrame parent) {
35 // init frame
36 frame = new JFrame(String.format(I18n.translate("crash.title"), Constants.NAME));
37 final Container pane = frame.getContentPane();
38 pane.setLayout(new BorderLayout());
39
40 JLabel label = new JLabel(String.format(I18n.translate("crash.summary"), Constants.NAME));
41 label.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
42 pane.add(label, BorderLayout.NORTH);
43
44 // report panel
45 text = new JTextArea();
46 text.setTabSize(2);
47 pane.add(new JScrollPane(text), BorderLayout.CENTER);
48
49 // buttons panel
50 JPanel buttonsPanel = new JPanel();
51 buttonsPanel.setLayout(new BoxLayout(buttonsPanel, BoxLayout.LINE_AXIS));
52 JButton exportButton = new JButton(I18n.translate("crash.export"));
53 exportButton.addActionListener(event -> {
54 JFileChooser chooser = new JFileChooser();
55 chooser.setSelectedFile(new File("enigma_crash.log"));
56 if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
57 try {
58 File file = chooser.getSelectedFile();
59 FileWriter writer = new FileWriter(file);
60 writer.write(instance.text.getText());
61 writer.close();
62 } catch (IOException ex) {
63 ex.printStackTrace();
64 }
65 }
66 });
67 buttonsPanel.add(exportButton);
68 buttonsPanel.add(Box.createHorizontalGlue());
69 buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("crash.exit.warning"))));
70 JButton ignoreButton = new JButton(I18n.translate("crash.ignore"));
71 ignoreButton.addActionListener(event -> {
72 // close (hide) the dialog
73 frame.setVisible(false);
74 });
75 buttonsPanel.add(ignoreButton);
76 JButton exitButton = new JButton(I18n.translate("crash.exit"));
77 exitButton.addActionListener(event -> {
78 // exit enigma
79 System.exit(1);
80 });
81 buttonsPanel.add(exitButton);
82 pane.add(buttonsPanel, BorderLayout.SOUTH);
83
84 // show the frame
85 frame.setSize(ScaleUtil.getDimension(600, 400));
86 frame.setLocationRelativeTo(parent);
87 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
88 }
89
90 public static void init(JFrame parent) {
91 instance = new CrashDialog(parent);
92 }
93
94 public static void show(Throwable ex) {
95 // get the error report
96 StringWriter buf = new StringWriter();
97 ex.printStackTrace(new PrintWriter(buf));
98 String report = buf.toString();
99
100 // show it!
101 instance.text.setText(report);
102 instance.frame.doLayout();
103 instance.frame.setVisible(true);
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
deleted file mode 100644
index eea1dff..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/CreateServerDialog.java
+++ /dev/null
@@ -1,65 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.*;
8
9public class CreateServerDialog {
10
11 public static Result show(Frame parentComponent) {
12 JTextField portField = new JTextField(String.valueOf(EnigmaServer.DEFAULT_PORT), 10);
13 JPanel portRow = new JPanel();
14 portRow.add(new JLabel(I18n.translate("prompt.port")));
15 portRow.add(portField);
16 JPasswordField passwordField = new JPasswordField(20);
17 JPanel passwordRow = new JPanel();
18 passwordRow.add(new JLabel(I18n.translate("prompt.password")));
19 passwordRow.add(passwordField);
20
21 int response = JOptionPane.showConfirmDialog(parentComponent, new Object[]{portRow, passwordRow}, I18n.translate("prompt.create_server.title"), JOptionPane.OK_CANCEL_OPTION);
22 if (response != JOptionPane.OK_OPTION) {
23 return null;
24 }
25
26 int port;
27 try {
28 port = Integer.parseInt(portField.getText());
29 } catch (NumberFormatException e) {
30 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.nan"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
31 return null;
32 }
33 if (port < 0 || port >= 65536) {
34 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.port.invalid"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
35 return null;
36 }
37
38 char[] password = passwordField.getPassword();
39 if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) {
40 JOptionPane.showMessageDialog(parentComponent, I18n.translate("prompt.password.too_long"), I18n.translate("prompt.create_server.title"), JOptionPane.ERROR_MESSAGE);
41 return null;
42 }
43
44 return new Result(port, password);
45 }
46
47 public static class Result {
48 private final int port;
49 private final char[] password;
50
51 public Result(int port, char[] password) {
52 this.port = port;
53 this.password = password;
54 }
55
56 public int getPort() {
57 return port;
58 }
59
60 public char[] getPassword() {
61 return password;
62 }
63 }
64
65}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
deleted file mode 100644
index 7e41441..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/JavadocDialog.java
+++ /dev/null
@@ -1,159 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.utils.I18n;
15import cuchaz.enigma.gui.util.ScaleUtil;
16import cuchaz.enigma.utils.Utils;
17
18import javax.swing.*;
19import javax.swing.text.html.HTML;
20
21import java.awt.*;
22import java.awt.event.KeyAdapter;
23import java.awt.event.KeyEvent;
24
25public class JavadocDialog {
26
27 private static JavadocDialog instance = null;
28
29 private JFrame frame;
30
31 private JavadocDialog(JFrame parent, JTextArea text, Callback callback) {
32 // init frame
33 frame = new JFrame(I18n.translate("javadocs.edit"));
34 final Container pane = frame.getContentPane();
35 pane.setLayout(new BorderLayout());
36
37 // editor panel
38 text.setTabSize(2);
39 pane.add(new JScrollPane(text), BorderLayout.CENTER);
40 text.addKeyListener(new KeyAdapter() {
41 @Override
42 public void keyPressed(KeyEvent event) {
43 switch (event.getKeyCode()) {
44 case KeyEvent.VK_ENTER:
45 if (event.isControlDown())
46 callback.closeUi(frame, true);
47 break;
48 case KeyEvent.VK_ESCAPE:
49 callback.closeUi(frame, false);
50 break;
51 default:
52 break;
53 }
54 }
55 });
56
57 // buttons panel
58 JPanel buttonsPanel = new JPanel();
59 FlowLayout buttonsLayout = new FlowLayout();
60 buttonsLayout.setAlignment(FlowLayout.RIGHT);
61 buttonsPanel.setLayout(buttonsLayout);
62 buttonsPanel.add(Utils.unboldLabel(new JLabel(I18n.translate("javadocs.instruction"))));
63 JButton cancelButton = new JButton(I18n.translate("javadocs.cancel"));
64 cancelButton.addActionListener(event -> {
65 // close (hide) the dialog
66 callback.closeUi(frame, false);
67 });
68 buttonsPanel.add(cancelButton);
69 JButton saveButton = new JButton(I18n.translate("javadocs.save"));
70 saveButton.addActionListener(event -> {
71 // exit enigma
72 callback.closeUi(frame, true);
73 });
74 buttonsPanel.add(saveButton);
75 pane.add(buttonsPanel, BorderLayout.SOUTH);
76
77 // tags panel
78 JMenuBar tagsMenu = new JMenuBar();
79
80 // add javadoc tags
81 for (JavadocTag tag : JavadocTag.values()) {
82 JButton tagButton = new JButton(tag.getText());
83 tagButton.addActionListener(action -> {
84 boolean textSelected = text.getSelectedText() != null;
85 String tagText = tag.isInline() ? "{" + tag.getText() + " }" : tag.getText() + " ";
86
87 if (textSelected) {
88 if (tag.isInline()) {
89 tagText = "{" + tag.getText() + " " + text.getSelectedText() + "}";
90 } else {
91 tagText = tag.getText() + " " + text.getSelectedText();
92 }
93 text.replaceSelection(tagText);
94 } else {
95 text.insert(tagText, text.getCaretPosition());
96 }
97
98 if (tag.isInline()) {
99 text.setCaretPosition(text.getCaretPosition() - 1);
100 }
101 text.grabFocus();
102 });
103 tagsMenu.add(tagButton);
104 }
105
106 // add html tags
107 JComboBox<String> htmlList = new JComboBox<String>();
108 htmlList.setPreferredSize(new Dimension());
109 for (HTML.Tag htmlTag : HTML.getAllTags()) {
110 htmlList.addItem(htmlTag.toString());
111 }
112 htmlList.addActionListener(action -> {
113 String tagText = "<" + htmlList.getSelectedItem().toString() + ">";
114 text.insert(tagText, text.getCaretPosition());
115 text.grabFocus();
116 });
117 tagsMenu.add(htmlList);
118
119 pane.add(tagsMenu, BorderLayout.NORTH);
120
121 // show the frame
122 frame.setSize(ScaleUtil.getDimension(600, 400));
123 frame.setLocationRelativeTo(parent);
124 frame.setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
125 }
126
127 public static void init(JFrame parent, JTextArea area, Callback callback) {
128 instance = new JavadocDialog(parent, area, callback);
129 instance.frame.doLayout();
130 instance.frame.setVisible(true);
131 }
132
133 public interface Callback {
134 void closeUi(JFrame frame, boolean save);
135 }
136
137 private enum JavadocTag {
138 CODE(true),
139 LINK(true),
140 LINKPLAIN(true),
141 RETURN(false),
142 SEE(false),
143 THROWS(false);
144
145 private boolean inline;
146
147 private JavadocTag(boolean inline) {
148 this.inline = inline;
149 }
150
151 public String getText() {
152 return "@" + this.name().toLowerCase();
153 }
154
155 public boolean isInline() {
156 return this.inline;
157 }
158 }
159}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
deleted file mode 100644
index e33ae82..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/ProgressDialog.java
+++ /dev/null
@@ -1,109 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import cuchaz.enigma.Constants;
15import cuchaz.enigma.ProgressListener;
16import cuchaz.enigma.utils.I18n;
17import cuchaz.enigma.gui.util.ScaleUtil;
18import cuchaz.enigma.utils.Utils;
19
20import javax.swing.*;
21import java.awt.*;
22import java.util.concurrent.CompletableFuture;
23
24public class ProgressDialog implements ProgressListener, AutoCloseable {
25
26 private JFrame frame;
27 private JLabel labelTitle;
28 private JLabel labelText;
29 private JProgressBar progress;
30
31 public ProgressDialog(JFrame parent) {
32
33 // init frame
34 this.frame = new JFrame(String.format(I18n.translate("progress.operation"), Constants.NAME));
35 final Container pane = this.frame.getContentPane();
36 FlowLayout layout = new FlowLayout();
37 layout.setAlignment(FlowLayout.LEFT);
38 pane.setLayout(layout);
39
40 this.labelTitle = new JLabel();
41 pane.add(this.labelTitle);
42
43 // set up the progress bar
44 JPanel panel = new JPanel();
45 pane.add(panel);
46 panel.setLayout(new BorderLayout());
47 this.labelText = Utils.unboldLabel(new JLabel());
48 this.progress = new JProgressBar();
49 this.labelText.setBorder(BorderFactory.createEmptyBorder(0, 0, 10, 0));
50 panel.add(this.labelText, BorderLayout.NORTH);
51 panel.add(this.progress, BorderLayout.CENTER);
52 panel.setPreferredSize(ScaleUtil.getDimension(360, 50));
53
54 // show the frame
55 pane.doLayout();
56 this.frame.setSize(ScaleUtil.getDimension(400, 120));
57 this.frame.setResizable(false);
58 this.frame.setLocationRelativeTo(parent);
59 this.frame.setVisible(true);
60 this.frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
61 }
62
63 public static CompletableFuture<Void> runOffThread(final JFrame parent, final ProgressRunnable runnable) {
64 CompletableFuture<Void> future = new CompletableFuture<>();
65 new Thread(() ->
66 {
67 try (ProgressDialog progress = new ProgressDialog(parent)) {
68 runnable.run(progress);
69 future.complete(null);
70 } catch (Exception ex) {
71 future.completeExceptionally(ex);
72 throw new Error(ex);
73 }
74 }).start();
75 return future;
76 }
77
78 @Override
79 public void close() {
80 this.frame.dispose();
81 }
82
83 @Override
84 public void init(int totalWork, String title) {
85 this.labelTitle.setText(title);
86 this.progress.setMinimum(0);
87 this.progress.setMaximum(totalWork);
88 this.progress.setValue(0);
89 }
90
91 @Override
92 public void step(int numDone, String message) {
93 this.labelText.setText(message);
94 if (numDone != -1) {
95 this.progress.setValue(numDone);
96 this.progress.setIndeterminate(false);
97 } else {
98 this.progress.setIndeterminate(true);
99 }
100
101 // update the frame
102 this.frame.validate();
103 this.frame.repaint();
104 }
105
106 public interface ProgressRunnable {
107 void run(ProgressListener listener) throws Exception;
108 }
109}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
deleted file mode 100644
index b283a37..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/SearchDialog.java
+++ /dev/null
@@ -1,261 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.dialog;
13
14import java.awt.BorderLayout;
15import java.awt.Color;
16import java.awt.FlowLayout;
17import java.awt.Font;
18import java.awt.event.*;
19import java.util.Arrays;
20import java.util.Collections;
21import java.util.List;
22
23import javax.swing.*;
24import javax.swing.event.DocumentEvent;
25import javax.swing.event.DocumentListener;
26
27import cuchaz.enigma.gui.Gui;
28import cuchaz.enigma.gui.GuiController;
29import cuchaz.enigma.gui.util.AbstractListCellRenderer;
30import cuchaz.enigma.gui.util.ScaleUtil;
31import cuchaz.enigma.translation.representation.entry.ClassEntry;
32import cuchaz.enigma.utils.I18n;
33import cuchaz.enigma.utils.search.SearchEntry;
34import cuchaz.enigma.utils.search.SearchUtil;
35
36public class SearchDialog {
37
38 private final JTextField searchField;
39 private DefaultListModel<SearchEntryImpl> classListModel;
40 private final JList<SearchEntryImpl> classList;
41 private final JDialog dialog;
42
43 private final Gui parent;
44 private final SearchUtil<SearchEntryImpl> su;
45 private SearchUtil.SearchControl currentSearch;
46
47 public SearchDialog(Gui parent) {
48 this.parent = parent;
49
50 su = new SearchUtil<>();
51
52 dialog = new JDialog(parent.getFrame(), I18n.translate("menu.view.search"), true);
53 JPanel contentPane = new JPanel();
54 contentPane.setBorder(ScaleUtil.createEmptyBorder(4, 4, 4, 4));
55 contentPane.setLayout(new BorderLayout(ScaleUtil.scale(4), ScaleUtil.scale(4)));
56
57 searchField = new JTextField();
58 searchField.getDocument().addDocumentListener(new DocumentListener() {
59
60 @Override
61 public void insertUpdate(DocumentEvent e) {
62 updateList();
63 }
64
65 @Override
66 public void removeUpdate(DocumentEvent e) {
67 updateList();
68 }
69
70 @Override
71 public void changedUpdate(DocumentEvent e) {
72 updateList();
73 }
74
75 });
76 searchField.addKeyListener(new KeyAdapter() {
77 @Override
78 public void keyPressed(KeyEvent e) {
79 if (e.getKeyCode() == KeyEvent.VK_DOWN) {
80 int next = classList.isSelectionEmpty() ? 0 : classList.getSelectedIndex() + 1;
81 classList.setSelectedIndex(next);
82 classList.ensureIndexIsVisible(next);
83 } else if (e.getKeyCode() == KeyEvent.VK_UP) {
84 int prev = classList.isSelectionEmpty() ? classList.getModel().getSize() : classList.getSelectedIndex() - 1;
85 classList.setSelectedIndex(prev);
86 classList.ensureIndexIsVisible(prev);
87 } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
88 close();
89 }
90 }
91 });
92 searchField.addActionListener(e -> openSelected());
93 contentPane.add(searchField, BorderLayout.NORTH);
94
95 classListModel = new DefaultListModel<>();
96 classList = new JList<>();
97 classList.setModel(classListModel);
98 classList.setCellRenderer(new ListCellRendererImpl());
99 classList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
100 classList.addMouseListener(new MouseAdapter() {
101 @Override
102 public void mouseClicked(MouseEvent mouseEvent) {
103 if (mouseEvent.getClickCount() >= 2) {
104 int idx = classList.locationToIndex(mouseEvent.getPoint());
105 SearchEntryImpl entry = classList.getModel().getElementAt(idx);
106 openEntry(entry);
107 }
108 }
109 });
110 contentPane.add(new JScrollPane(classList, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER), BorderLayout.CENTER);
111
112 JPanel buttonBar = new JPanel();
113 buttonBar.setLayout(new FlowLayout(FlowLayout.RIGHT));
114 JButton open = new JButton(I18n.translate("prompt.open"));
115 open.addActionListener(event -> openSelected());
116 buttonBar.add(open);
117 JButton cancel = new JButton(I18n.translate("prompt.cancel"));
118 cancel.addActionListener(event -> close());
119 buttonBar.add(cancel);
120 contentPane.add(buttonBar, BorderLayout.SOUTH);
121
122 // apparently the class list doesn't update by itself when the list
123 // state changes and the dialog is hidden
124 dialog.addComponentListener(new ComponentAdapter() {
125 @Override
126 public void componentShown(ComponentEvent e) {
127 classList.updateUI();
128 }
129 });
130
131 dialog.setContentPane(contentPane);
132 dialog.setSize(ScaleUtil.getDimension(400, 500));
133 dialog.setLocationRelativeTo(parent.getFrame());
134 }
135
136 public void show() {
137 su.clear();
138 parent.getController().project.getJarIndex().getEntryIndex().getClasses().parallelStream()
139 .filter(e -> !e.isInnerClass())
140 .map(e -> SearchEntryImpl.from(e, parent.getController()))
141 .map(SearchUtil.Entry::from)
142 .sequential()
143 .forEach(su::add);
144
145 updateList();
146
147 searchField.requestFocus();
148 searchField.selectAll();
149
150 dialog.setVisible(true);
151 }
152
153 private void openSelected() {
154 SearchEntryImpl selectedValue = classList.getSelectedValue();
155 if (selectedValue != null) {
156 openEntry(selectedValue);
157 }
158 }
159
160 private void openEntry(SearchEntryImpl e) {
161 close();
162 su.hit(e);
163 parent.getController().navigateTo(e.obf);
164 if (e.deobf != null) {
165 parent.getDeobfPanel().deobfClasses.setSelectionClass(e.deobf);
166 } else {
167 parent.getObfPanel().obfClasses.setSelectionClass(e.obf);
168 }
169 }
170
171 private void close() {
172 dialog.setVisible(false);
173 }
174
175 // Updates the list of class names
176 private void updateList() {
177 if (currentSearch != null) currentSearch.stop();
178
179 DefaultListModel<SearchEntryImpl> classListModel = new DefaultListModel<>();
180 this.classListModel = classListModel;
181 classList.setModel(classListModel);
182
183 currentSearch = su.asyncSearch(searchField.getText(), (idx, e) -> SwingUtilities.invokeLater(() -> classListModel.insertElementAt(e, idx)));
184 }
185
186 public void dispose() {
187 dialog.dispose();
188 }
189
190 private static final class SearchEntryImpl implements SearchEntry {
191
192 public final ClassEntry obf;
193 public final ClassEntry deobf;
194
195 private SearchEntryImpl(ClassEntry obf, ClassEntry deobf) {
196 this.obf = obf;
197 this.deobf = deobf;
198 }
199
200 @Override
201 public List<String> getSearchableNames() {
202 if (deobf != null) {
203 return Arrays.asList(obf.getSimpleName(), deobf.getSimpleName());
204 } else {
205 return Collections.singletonList(obf.getSimpleName());
206 }
207 }
208
209 @Override
210 public String getIdentifier() {
211 return obf.getFullName();
212 }
213
214 @Override
215 public String toString() {
216 return String.format("SearchEntryImpl { obf: %s, deobf: %s }", obf, deobf);
217 }
218
219 public static SearchEntryImpl from(ClassEntry e, GuiController controller) {
220 ClassEntry deobf = controller.project.getMapper().deobfuscate(e);
221 if (deobf.equals(e)) deobf = null;
222 return new SearchEntryImpl(e, deobf);
223 }
224
225 }
226
227 private static final class ListCellRendererImpl extends AbstractListCellRenderer<SearchEntryImpl> {
228
229 private final JLabel mainName;
230 private final JLabel secondaryName;
231
232 public ListCellRendererImpl() {
233 this.setLayout(new BorderLayout());
234
235 mainName = new JLabel();
236 this.add(mainName, BorderLayout.WEST);
237
238 secondaryName = new JLabel();
239 secondaryName.setFont(secondaryName.getFont().deriveFont(Font.ITALIC));
240 secondaryName.setForeground(Color.GRAY);
241 this.add(secondaryName, BorderLayout.EAST);
242 }
243
244 @Override
245 public void updateUiForEntry(JList<? extends SearchEntryImpl> list, SearchEntryImpl value, int index, boolean isSelected, boolean cellHasFocus) {
246 if (value.deobf == null) {
247 mainName.setText(value.obf.getSimpleName());
248 mainName.setToolTipText(value.obf.getFullName());
249 secondaryName.setText("");
250 secondaryName.setToolTipText("");
251 } else {
252 mainName.setText(value.deobf.getSimpleName());
253 mainName.setToolTipText(value.deobf.getFullName());
254 secondaryName.setText(value.obf.getSimpleName());
255 secondaryName.setToolTipText(value.obf.getFullName());
256 }
257 }
258
259 }
260
261}
diff --git a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java b/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java
deleted file mode 100644
index 868eba7..0000000
--- a/src/main/java/cuchaz/enigma/gui/dialog/StatsDialog.java
+++ /dev/null
@@ -1,82 +0,0 @@
1package cuchaz.enigma.gui.dialog;
2
3import java.awt.BorderLayout;
4import java.util.Arrays;
5import java.util.Locale;
6import java.util.Map;
7import java.util.Set;
8import java.util.stream.Collectors;
9
10import javax.swing.JButton;
11import javax.swing.JCheckBox;
12import javax.swing.JFrame;
13import javax.swing.JPanel;
14
15import cuchaz.enigma.gui.Gui;
16import cuchaz.enigma.gui.stats.StatsMember;
17import cuchaz.enigma.gui.util.ScaleUtil;
18import cuchaz.enigma.utils.I18n;
19
20public class StatsDialog {
21
22 public static void show(Gui gui) {
23 // init frame
24 JFrame frame = new JFrame(I18n.translate("menu.file.stats.title"));
25 JPanel checkboxesPanel = new JPanel();
26 JPanel buttonPanel = new JPanel();
27 frame.setLayout(new BorderLayout());
28 frame.add(BorderLayout.NORTH, checkboxesPanel);
29 frame.add(BorderLayout.SOUTH, buttonPanel);
30
31 // show checkboxes
32 Map<StatsMember, JCheckBox> checkboxes = Arrays
33 .stream(StatsMember.values())
34 .collect(Collectors.toMap(m -> m, m -> {
35 JCheckBox checkbox = new JCheckBox(I18n.translate("type." + m.name().toLowerCase(Locale.ROOT)));
36 checkboxesPanel.add(checkbox);
37 return checkbox;
38 }));
39
40 // show generate button
41 JButton button = new JButton(I18n.translate("menu.file.stats.generate"));
42 buttonPanel.add(button);
43 button.setEnabled(false);
44 button.addActionListener(action -> {
45 frame.dispose();
46 generateStats(gui, checkboxes);
47 });
48
49 // add action listener to each checkbox
50 checkboxes.entrySet().forEach(checkbox -> {
51 checkbox.getValue().addActionListener(action -> {
52 if (!button.isEnabled()) {
53 button.setEnabled(true);
54 } else if (checkboxes.entrySet().stream().allMatch(entry -> !entry.getValue().isSelected())) {
55 button.setEnabled(false);
56 }
57 });
58 });
59
60 // show the frame
61 frame.pack();
62 frame.setVisible(true);
63 frame.setSize(ScaleUtil.getDimension(500, 120));
64 frame.setResizable(false);
65 frame.setLocationRelativeTo(gui.getFrame());
66 }
67
68 private static void generateStats(Gui gui, Map<StatsMember, JCheckBox> checkboxes) {
69 // get members from selected checkboxes
70 Set<StatsMember> includedMembers = checkboxes
71 .entrySet()
72 .stream()
73 .filter(entry -> entry.getValue().isSelected())
74 .map(Map.Entry::getKey)
75 .collect(Collectors.toSet());
76
77 // checks if a projet is open
78 if (gui.getController().project != null) {
79 gui.getController().openStats(includedMembers);
80 }
81 }
82}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java b/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java
deleted file mode 100644
index fb497b1..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/CollapsibleTabbedPane.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import java.awt.event.MouseEvent;
4
5import javax.swing.JTabbedPane;
6
7public class CollapsibleTabbedPane extends JTabbedPane {
8
9 public CollapsibleTabbedPane() {
10 }
11
12 public CollapsibleTabbedPane(int tabPlacement) {
13 super(tabPlacement);
14 }
15
16 public CollapsibleTabbedPane(int tabPlacement, int tabLayoutPolicy) {
17 super(tabPlacement, tabLayoutPolicy);
18 }
19
20 @Override
21 protected void processMouseEvent(MouseEvent e) {
22 int id = e.getID();
23 if (id == MouseEvent.MOUSE_PRESSED) {
24 if (!isEnabled()) return;
25 int tabIndex = getUI().tabForCoordinate(this, e.getX(), e.getY());
26 if (tabIndex >= 0 && isEnabledAt(tabIndex)) {
27 if (tabIndex == getSelectedIndex()) {
28 if (isFocusOwner() && isRequestFocusEnabled()) {
29 requestFocus();
30 } else {
31 setSelectedIndex(-1);
32 }
33 return;
34 }
35 }
36 }
37 super.processMouseEvent(e);
38 }
39
40}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
deleted file mode 100644
index dc2cf8f..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/MenuBar.java
+++ /dev/null
@@ -1,386 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import cuchaz.enigma.config.Config;
4import cuchaz.enigma.config.Themes;
5import cuchaz.enigma.gui.Gui;
6import cuchaz.enigma.gui.dialog.AboutDialog;
7import cuchaz.enigma.gui.dialog.ChangeDialog;
8import cuchaz.enigma.gui.dialog.ConnectToServerDialog;
9import cuchaz.enigma.gui.dialog.CreateServerDialog;
10import cuchaz.enigma.gui.dialog.StatsDialog;
11import cuchaz.enigma.gui.util.ScaleUtil;
12import cuchaz.enigma.translation.mapping.serde.MappingFormat;
13import cuchaz.enigma.utils.I18n;
14import cuchaz.enigma.utils.Pair;
15
16import java.awt.Desktop;
17import java.awt.event.InputEvent;
18import java.awt.event.KeyEvent;
19import java.io.File;
20import java.io.IOException;
21import java.net.URISyntaxException;
22import java.net.URL;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.nio.file.Paths;
26import java.util.*;
27import java.util.List;
28import java.util.stream.Collectors;
29import java.util.stream.IntStream;
30import javax.swing.*;
31
32public class MenuBar extends JMenuBar {
33
34 public final JMenuItem closeJarMenu;
35 public final List<JMenuItem> openMappingsMenus;
36 public final JMenuItem saveMappingsMenu;
37 public final List<JMenuItem> saveMappingsMenus;
38 public final JMenuItem closeMappingsMenu;
39 public final JMenuItem dropMappingsMenu;
40 public final JMenuItem exportSourceMenu;
41 public final JMenuItem exportJarMenu;
42 public final JMenuItem connectToServerMenu;
43 public final JMenuItem startServerMenu;
44 private final Gui gui;
45
46 public MenuBar(Gui gui) {
47 this.gui = gui;
48
49 /*
50 * File menu
51 */
52 {
53 JMenu menu = new JMenu(I18n.translate("menu.file"));
54 this.add(menu);
55 {
56 JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.open"));
57 menu.add(item);
58 item.addActionListener(event -> {
59 this.gui.jarFileChooser.setVisible(true);
60 String file = this.gui.jarFileChooser.getFile();
61 // checks if the file name is not empty
62 if (file != null) {
63 Path path = Paths.get(this.gui.jarFileChooser.getDirectory()).resolve(file);
64 // checks if the file name corresponds to an existing file
65 if (Files.exists(path)) {
66 gui.getController().openJar(path);
67 }
68 }
69 });
70 }
71 {
72 JMenuItem item = new JMenuItem(I18n.translate("menu.file.jar.close"));
73 menu.add(item);
74 item.addActionListener(event -> this.gui.getController().closeJar());
75 this.closeJarMenu = item;
76 }
77 menu.addSeparator();
78 JMenu openMenu = new JMenu(I18n.translate("menu.file.mappings.open"));
79 menu.add(openMenu);
80 {
81 openMappingsMenus = new ArrayList<>();
82 for (MappingFormat format : MappingFormat.values()) {
83 if (format.getReader() != null) {
84 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
85 openMenu.add(item);
86 item.addActionListener(event -> {
87 if (this.gui.enigmaMappingsFileChooser.showOpenDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
88 File selectedFile = this.gui.enigmaMappingsFileChooser.getSelectedFile();
89 this.gui.getController().openMappings(format, selectedFile.toPath());
90 }
91 });
92 openMappingsMenus.add(item);
93 }
94 }
95 }
96 {
97 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.save"));
98 menu.add(item);
99 item.addActionListener(event -> {
100 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath());
101 });
102 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK));
103 this.saveMappingsMenu = item;
104 }
105 JMenu saveMenu = new JMenu(I18n.translate("menu.file.mappings.save_as"));
106 menu.add(saveMenu);
107 {
108 saveMappingsMenus = new ArrayList<>();
109 for (MappingFormat format : MappingFormat.values()) {
110 if (format.getWriter() != null) {
111 JMenuItem item = new JMenuItem(I18n.translate("mapping_format." + format.name().toLowerCase(Locale.ROOT)));
112 saveMenu.add(item);
113 item.addActionListener(event -> {
114 // TODO: Use a specific file chooser for it
115 if (this.gui.enigmaMappingsFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
116 this.gui.getController().saveMappings(this.gui.enigmaMappingsFileChooser.getSelectedFile().toPath(), format);
117 this.saveMappingsMenu.setEnabled(true);
118 }
119 });
120 saveMappingsMenus.add(item);
121 }
122 }
123 }
124 {
125 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.close"));
126 menu.add(item);
127 item.addActionListener(event -> {
128 if (this.gui.getController().isDirty()) {
129 this.gui.showDiscardDiag((response -> {
130 if (response == JOptionPane.YES_OPTION) {
131 gui.saveMapping();
132 this.gui.getController().closeMappings();
133 } else if (response == JOptionPane.NO_OPTION)
134 this.gui.getController().closeMappings();
135 return null;
136 }), I18n.translate("prompt.close.save"), I18n.translate("prompt.close.discard"), I18n.translate("prompt.close.cancel"));
137 } else
138 this.gui.getController().closeMappings();
139
140 });
141 this.closeMappingsMenu = item;
142 }
143 {
144 JMenuItem item = new JMenuItem(I18n.translate("menu.file.mappings.drop"));
145 menu.add(item);
146 item.addActionListener(event -> this.gui.getController().dropMappings());
147 this.dropMappingsMenu = item;
148 }
149 menu.addSeparator();
150 {
151 JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.source"));
152 menu.add(item);
153 item.addActionListener(event -> {
154 if (this.gui.exportSourceFileChooser.showSaveDialog(this.gui.getFrame()) == JFileChooser.APPROVE_OPTION) {
155 this.gui.getController().exportSource(this.gui.exportSourceFileChooser.getSelectedFile().toPath());
156 }
157 });
158 this.exportSourceMenu = item;
159 }
160 {
161 JMenuItem item = new JMenuItem(I18n.translate("menu.file.export.jar"));
162 menu.add(item);
163 item.addActionListener(event -> {
164 this.gui.exportJarFileChooser.setVisible(true);
165 if (this.gui.exportJarFileChooser.getFile() != null) {
166 Path path = Paths.get(this.gui.exportJarFileChooser.getDirectory(), this.gui.exportJarFileChooser.getFile());
167 this.gui.getController().exportJar(path);
168 }
169 });
170 this.exportJarMenu = item;
171 }
172 menu.addSeparator();
173 {
174 JMenuItem stats = new JMenuItem(I18n.translate("menu.file.stats"));
175 menu.add(stats);
176 stats.addActionListener(event -> StatsDialog.show(this.gui));
177 }
178 menu.addSeparator();
179 {
180 JMenuItem item = new JMenuItem(I18n.translate("menu.file.exit"));
181 menu.add(item);
182 item.addActionListener(event -> this.gui.close());
183 }
184 }
185
186 /*
187 * Decompiler menu
188 */
189 {
190 JMenu menu = new JMenu(I18n.translate("menu.decompiler"));
191 this.add(menu);
192
193 ButtonGroup decompilerGroup = new ButtonGroup();
194
195 for (Config.Decompiler decompiler : Config.Decompiler.values()) {
196 JRadioButtonMenuItem decompilerButton = new JRadioButtonMenuItem(decompiler.name);
197 decompilerGroup.add(decompilerButton);
198 if (decompiler.equals(Config.getInstance().decompiler)) {
199 decompilerButton.setSelected(true);
200 }
201 menu.add(decompilerButton);
202 decompilerButton.addActionListener(event -> {
203 gui.getController().setDecompiler(decompiler.service);
204
205 try {
206 Config.getInstance().decompiler = decompiler;
207 Config.getInstance().saveConfig();
208 } catch (IOException e) {
209 throw new RuntimeException(e);
210 }
211 });
212 }
213 }
214
215 /*
216 * View menu
217 */
218 {
219 JMenu menu = new JMenu(I18n.translate("menu.view"));
220 this.add(menu);
221 {
222 JMenu themes = new JMenu(I18n.translate("menu.view.themes"));
223 menu.add(themes);
224 ButtonGroup themeGroup = new ButtonGroup();
225 for (Config.LookAndFeel lookAndFeel : Config.LookAndFeel.values()) {
226 JRadioButtonMenuItem themeButton = new JRadioButtonMenuItem(I18n.translate("menu.view.themes." + lookAndFeel.name().toLowerCase(Locale.ROOT)));
227 themeGroup.add(themeButton);
228 if (lookAndFeel.equals(Config.getInstance().lookAndFeel)) {
229 themeButton.setSelected(true);
230 }
231 themes.add(themeButton);
232 themeButton.addActionListener(event -> Themes.setLookAndFeel(gui, lookAndFeel));
233 }
234 }
235 {
236 JMenu languages = new JMenu(I18n.translate("menu.view.languages"));
237 menu.add(languages);
238 ButtonGroup languageGroup = new ButtonGroup();
239 for (String lang : I18n.getAvailableLanguages()) {
240 JRadioButtonMenuItem languageButton = new JRadioButtonMenuItem(I18n.getLanguageName(lang));
241 languageGroup.add(languageButton);
242 if (lang.equals(Config.getInstance().language)) {
243 languageButton.setSelected(true);
244 }
245 languages.add(languageButton);
246 languageButton.addActionListener(event -> {
247 I18n.setLanguage(lang);
248 ChangeDialog.show(this.gui);
249 });
250 }
251 }
252 {
253 JMenu scale = new JMenu(I18n.translate("menu.view.scale"));
254 {
255 ButtonGroup scaleGroup = new ButtonGroup();
256 Map<Float, JRadioButtonMenuItem> map = IntStream.of(100, 125, 150, 175, 200)
257 .mapToObj(scaleFactor -> {
258 float realScaleFactor = scaleFactor / 100f;
259 JRadioButtonMenuItem menuItem = new JRadioButtonMenuItem(String.format("%d%%", scaleFactor));
260 menuItem.addActionListener(event -> ScaleUtil.setScaleFactor(realScaleFactor));
261 menuItem.addActionListener(event -> ChangeDialog.show(this.gui));
262 scaleGroup.add(menuItem);
263 scale.add(menuItem);
264 return new Pair<>(realScaleFactor, menuItem);
265 })
266 .collect(Collectors.toMap(x -> x.a, x -> x.b));
267
268 JMenuItem customScale = new JMenuItem(I18n.translate("menu.view.scale.custom"));
269 customScale.addActionListener(event -> {
270 String answer = (String) JOptionPane.showInputDialog(gui.getFrame(), I18n.translate("menu.view.scale.custom.title"), I18n.translate("menu.view.scale.custom.title"),
271 JOptionPane.QUESTION_MESSAGE, null, null, Float.toString(ScaleUtil.getScaleFactor() * 100));
272 if (answer == null) return;
273 float newScale = 1.0f;
274 try {
275 newScale = Float.parseFloat(answer) / 100f;
276 } catch (NumberFormatException ignored) {
277 }
278 ScaleUtil.setScaleFactor(newScale);
279 ChangeDialog.show(this.gui);
280 });
281 scale.add(customScale);
282 ScaleUtil.addListener((newScale, _oldScale) -> {
283 JRadioButtonMenuItem mi = map.get(newScale);
284 if (mi != null) {
285 mi.setSelected(true);
286 } else {
287 scaleGroup.clearSelection();
288 }
289 });
290 JRadioButtonMenuItem mi = map.get(ScaleUtil.getScaleFactor());
291 if (mi != null) {
292 mi.setSelected(true);
293 }
294 }
295 menu.add(scale);
296 }
297 menu.addSeparator();
298 {
299 JMenuItem search = new JMenuItem(I18n.translate("menu.view.search"));
300 search.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, InputEvent.SHIFT_MASK));
301 menu.add(search);
302 search.addActionListener(event -> {
303 if (this.gui.getController().project != null) {
304 this.gui.getSearchDialog().show();
305 }
306 });
307 }
308 }
309
310 /*
311 * Collab menu
312 */
313 {
314 JMenu menu = new JMenu(I18n.translate("menu.collab"));
315 this.add(menu);
316 {
317 JMenuItem item = new JMenuItem(I18n.translate("menu.collab.connect"));
318 menu.add(item);
319 item.addActionListener(event -> {
320 if (this.gui.getController().getClient() != null) {
321 this.gui.getController().disconnectIfConnected(null);
322 return;
323 }
324 ConnectToServerDialog.Result result = ConnectToServerDialog.show(this.gui.getFrame());
325 if (result == null) {
326 return;
327 }
328 this.gui.getController().disconnectIfConnected(null);
329 try {
330 this.gui.getController().createClient(result.getUsername(), result.getIp(), result.getPort(), result.getPassword());
331 } catch (IOException e) {
332 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.connect.error"), JOptionPane.ERROR_MESSAGE);
333 this.gui.getController().disconnectIfConnected(null);
334 }
335 Arrays.fill(result.getPassword(), (char)0);
336 });
337 this.connectToServerMenu = item;
338 }
339 {
340 JMenuItem item = new JMenuItem(I18n.translate("menu.collab.server.start"));
341 menu.add(item);
342 item.addActionListener(event -> {
343 if (this.gui.getController().getServer() != null) {
344 this.gui.getController().disconnectIfConnected(null);
345 return;
346 }
347 CreateServerDialog.Result result = CreateServerDialog.show(this.gui.getFrame());
348 if (result == null) {
349 return;
350 }
351 this.gui.getController().disconnectIfConnected(null);
352 try {
353 this.gui.getController().createServer(result.getPort(), result.getPassword());
354 } catch (IOException e) {
355 JOptionPane.showMessageDialog(this.gui.getFrame(), e.toString(), I18n.translate("menu.collab.server.start.error"), JOptionPane.ERROR_MESSAGE);
356 this.gui.getController().disconnectIfConnected(null);
357 }
358 });
359 this.startServerMenu = item;
360 }
361 }
362
363 /*
364 * Help menu
365 */
366 {
367 JMenu menu = new JMenu(I18n.translate("menu.help"));
368 this.add(menu);
369 {
370 JMenuItem item = new JMenuItem(I18n.translate("menu.help.about"));
371 menu.add(item);
372 item.addActionListener(event -> AboutDialog.show(this.gui.getFrame()));
373 }
374 {
375 JMenuItem item = new JMenuItem(I18n.translate("menu.help.github"));
376 menu.add(item);
377 item.addActionListener(event -> {
378 try {
379 Desktop.getDesktop().browse(new URL("https://github.com/FabricMC/Enigma").toURI());
380 } catch (URISyntaxException | IOException ignored) {
381 }
382 });
383 }
384 }
385 }
386}
diff --git a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java b/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
deleted file mode 100644
index b92041c..0000000
--- a/src/main/java/cuchaz/enigma/gui/elements/PopupMenuBar.java
+++ /dev/null
@@ -1,125 +0,0 @@
1package cuchaz.enigma.gui.elements;
2
3import cuchaz.enigma.gui.Gui;
4import cuchaz.enigma.utils.I18n;
5
6import javax.swing.*;
7import java.awt.event.InputEvent;
8import java.awt.event.KeyEvent;
9
10public class PopupMenuBar extends JPopupMenu {
11
12 public final JMenuItem renameMenu;
13 public final JMenuItem editJavadocMenu;
14 public final JMenuItem showInheritanceMenu;
15 public final JMenuItem showImplementationsMenu;
16 public final JMenuItem showCallsMenu;
17 public final JMenuItem showCallsSpecificMenu;
18 public final JMenuItem openEntryMenu;
19 public final JMenuItem openPreviousMenu;
20 public final JMenuItem openNextMenu;
21 public final JMenuItem toggleMappingMenu;
22
23 public PopupMenuBar(Gui gui) {
24 {
25 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.rename"));
26 menu.addActionListener(event -> gui.startRename());
27 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, InputEvent.CTRL_DOWN_MASK));
28 menu.setEnabled(false);
29 this.add(menu);
30 this.renameMenu = menu;
31 }
32 {
33 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.javadoc"));
34 menu.addActionListener(event -> gui.startDocChange());
35 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK));
36 menu.setEnabled(false);
37 this.add(menu);
38 this.editJavadocMenu = menu;
39 }
40 {
41 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.inheritance"));
42 menu.addActionListener(event -> gui.showInheritance());
43 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, InputEvent.CTRL_DOWN_MASK));
44 menu.setEnabled(false);
45 this.add(menu);
46 this.showInheritanceMenu = menu;
47 }
48 {
49 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.implementations"));
50 menu.addActionListener(event -> gui.showImplementations());
51 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, InputEvent.CTRL_DOWN_MASK));
52 menu.setEnabled(false);
53 this.add(menu);
54 this.showImplementationsMenu = menu;
55 }
56 {
57 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls"));
58 menu.addActionListener(event -> gui.showCalls(true));
59 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK));
60 menu.setEnabled(false);
61 this.add(menu);
62 this.showCallsMenu = menu;
63 }
64 {
65 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.calls.specific"));
66 menu.addActionListener(event -> gui.showCalls(false));
67 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK + InputEvent.SHIFT_DOWN_MASK));
68 menu.setEnabled(false);
69 this.add(menu);
70 this.showCallsSpecificMenu = menu;
71 }
72 {
73 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.declaration"));
74 menu.addActionListener(event -> gui.getController().navigateTo(gui.cursorReference.entry));
75 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, InputEvent.CTRL_DOWN_MASK));
76 menu.setEnabled(false);
77 this.add(menu);
78 this.openEntryMenu = menu;
79 }
80 {
81 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.back"));
82 menu.addActionListener(event -> gui.getController().openPreviousReference());
83 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_P, InputEvent.CTRL_DOWN_MASK));
84 menu.setEnabled(false);
85 this.add(menu);
86 this.openPreviousMenu = menu;
87 }
88 {
89 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.forward"));
90 menu.addActionListener(event -> gui.getController().openNextReference());
91 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, InputEvent.CTRL_DOWN_MASK));
92 menu.setEnabled(false);
93 this.add(menu);
94 this.openNextMenu = menu;
95 }
96 {
97 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.mark_deobfuscated"));
98 menu.addActionListener(event -> gui.toggleMapping());
99 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, InputEvent.CTRL_DOWN_MASK));
100 menu.setEnabled(false);
101 this.add(menu);
102 this.toggleMappingMenu = menu;
103 }
104 {
105 this.add(new JSeparator());
106 }
107 {
108 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.in"));
109 menu.addActionListener(event -> gui.editor.offsetEditorZoom(2));
110 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, InputEvent.CTRL_DOWN_MASK));
111 this.add(menu);
112 }
113 {
114 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.out"));
115 menu.addActionListener(event -> gui.editor.offsetEditorZoom(-2));
116 menu.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, InputEvent.CTRL_DOWN_MASK));
117 this.add(menu);
118 }
119 {
120 JMenuItem menu = new JMenuItem(I18n.translate("popup_menu.zoom.reset"));
121 menu.addActionListener(event -> gui.editor.resetEditorZoom());
122 this.add(menu);
123 }
124 }
125}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
deleted file mode 100644
index f5f6628..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserAny.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserAny extends JFileChooser {
6 public FileChooserAny() {
7 this.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
8 this.setAcceptAllFileFilterUsed(false);
9 }
10} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
deleted file mode 100644
index cea11a6..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFile.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserFile extends JFileChooser {
6 public FileChooserFile() {
7 }
8}
diff --git a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java b/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
deleted file mode 100644
index c16e0af..0000000
--- a/src/main/java/cuchaz/enigma/gui/filechooser/FileChooserFolder.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.gui.filechooser;
2
3import javax.swing.*;
4
5public class FileChooserFolder extends JFileChooser {
6
7 public FileChooserFolder() {
8 this.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
9 this.setAcceptAllFileFilterUsed(false);
10 }
11}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
deleted file mode 100644
index cef6494..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/BoxHighlightPainter.java
+++ /dev/null
@@ -1,69 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.highlight;
13
14import cuchaz.enigma.config.Config;
15
16import javax.swing.text.BadLocationException;
17import javax.swing.text.Highlighter;
18import javax.swing.text.JTextComponent;
19import java.awt.*;
20
21public class BoxHighlightPainter implements Highlighter.HighlightPainter {
22 private Color fillColor;
23 private Color borderColor;
24
25 protected BoxHighlightPainter(Color fillColor, Color borderColor) {
26 this.fillColor = fillColor;
27 this.borderColor = borderColor;
28 }
29
30 public static BoxHighlightPainter create(Config.AlphaColorEntry entry, Config.AlphaColorEntry entryOutline) {
31 return new BoxHighlightPainter(entry != null ? entry.get() : null, entryOutline != null ? entryOutline.get() : null);
32 }
33
34 public static Rectangle getBounds(JTextComponent text, int start, int end) {
35 try {
36 // determine the bounds of the text
37 Rectangle startRect = text.modelToView(start);
38 Rectangle endRect = text.modelToView(end);
39 Rectangle bounds = startRect.union(endRect);
40
41 // adjust the box so it looks nice
42 bounds.x -= 2;
43 bounds.width += 2;
44 bounds.y += 1;
45 bounds.height -= 2;
46
47 return bounds;
48 } catch (BadLocationException ex) {
49 // don't care... just return something
50 return new Rectangle(0, 0, 0, 0);
51 }
52 }
53
54 @Override
55 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
56 Rectangle bounds = getBounds(text, start, end);
57
58 // fill the area
59 if (this.fillColor != null) {
60 g.setColor(this.fillColor);
61 g.fillRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
62 }
63
64 // draw a box around the area
65 g.setColor(this.borderColor);
66 g.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
67 }
68
69}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java b/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
deleted file mode 100644
index 81a70a9..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/SelectionHighlightPainter.java
+++ /dev/null
@@ -1,31 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.highlight;
13
14import cuchaz.enigma.config.Config;
15
16import javax.swing.text.Highlighter;
17import javax.swing.text.JTextComponent;
18import java.awt.*;
19
20public class SelectionHighlightPainter implements Highlighter.HighlightPainter {
21
22 @Override
23 public void paint(Graphics g, int start, int end, Shape shape, JTextComponent text) {
24 // draw a thick border
25 Graphics2D g2d = (Graphics2D) g;
26 Rectangle bounds = BoxHighlightPainter.getBounds(text, start, end);
27 g2d.setColor(new Color(Config.getInstance().selectionHighlightColor));
28 g2d.setStroke(new BasicStroke(2.0f));
29 g2d.drawRoundRect(bounds.x, bounds.y, bounds.width, bounds.height, 4, 4);
30 }
31}
diff --git a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java b/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
deleted file mode 100644
index ae23f32..0000000
--- a/src/main/java/cuchaz/enigma/gui/highlight/TokenHighlightType.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.gui.highlight;
2
3public enum TokenHighlightType {
4 OBFUSCATED,
5 DEOBFUSCATED,
6 PROPOSED
7}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
deleted file mode 100644
index 922f8f2..0000000
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorClassNode.java
+++ /dev/null
@@ -1,72 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.node;
13
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15
16import javax.swing.tree.DefaultMutableTreeNode;
17
18public class ClassSelectorClassNode extends DefaultMutableTreeNode {
19
20 private final ClassEntry obfEntry;
21 private ClassEntry classEntry;
22
23 public ClassSelectorClassNode(ClassEntry obfEntry, ClassEntry classEntry) {
24 this.obfEntry = obfEntry;
25 this.classEntry = classEntry;
26 this.setUserObject(classEntry);
27 }
28
29 public ClassEntry getObfEntry() {
30 return obfEntry;
31 }
32
33 public ClassEntry getClassEntry() {
34 return this.classEntry;
35 }
36
37 @Override
38 public String toString() {
39 return this.classEntry.getSimpleName();
40 }
41
42 @Override
43 public boolean equals(Object other) {
44 return other instanceof ClassSelectorClassNode && equals((ClassSelectorClassNode) other);
45 }
46
47 @Override
48 public int hashCode() {
49 return 17 + (classEntry != null ? classEntry.hashCode() : 0);
50 }
51
52 @Override
53 public Object getUserObject() {
54 return classEntry;
55 }
56
57 @Override
58 public void setUserObject(Object userObject) {
59 String packageName = "";
60 if (classEntry.getPackageName() != null)
61 packageName = classEntry.getPackageName() + "/";
62 if (userObject instanceof String)
63 this.classEntry = new ClassEntry(packageName + userObject);
64 else if (userObject instanceof ClassEntry)
65 this.classEntry = (ClassEntry) userObject;
66 super.setUserObject(classEntry);
67 }
68
69 public boolean equals(ClassSelectorClassNode other) {
70 return this.classEntry.equals(other.classEntry);
71 }
72}
diff --git a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java b/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
deleted file mode 100644
index caa985c..0000000
--- a/src/main/java/cuchaz/enigma/gui/node/ClassSelectorPackageNode.java
+++ /dev/null
@@ -1,58 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.gui.node;
13
14import javax.swing.tree.DefaultMutableTreeNode;
15
16public class ClassSelectorPackageNode extends DefaultMutableTreeNode {
17
18 private String packageName;
19
20 public ClassSelectorPackageNode(String packageName) {
21 this.packageName = packageName != null ? packageName : "(none)";
22 }
23
24 public String getPackageName() {
25 return packageName;
26 }
27
28 @Override
29 public Object getUserObject() {
30 return packageName;
31 }
32
33 @Override
34 public void setUserObject(Object userObject) {
35 if (userObject instanceof String)
36 this.packageName = (String) userObject;
37 super.setUserObject(userObject);
38 }
39
40 @Override
41 public String toString() {
42 return !packageName.equals("(none)") ? this.packageName : "(none)";
43 }
44
45 @Override
46 public boolean equals(Object other) {
47 return other instanceof ClassSelectorPackageNode && equals((ClassSelectorPackageNode) other);
48 }
49
50 @Override
51 public int hashCode() {
52 return packageName.hashCode();
53 }
54
55 public boolean equals(ClassSelectorPackageNode other) {
56 return other != null && this.packageName.equals(other.packageName);
57 }
58}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
deleted file mode 100644
index c24226b..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelDeobf.java
+++ /dev/null
@@ -1,26 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.utils.I18n;
6
7import javax.swing.*;
8import java.awt.*;
9
10public class PanelDeobf extends JPanel {
11
12 public final ClassSelector deobfClasses;
13 private final Gui gui;
14
15 public PanelDeobf(Gui gui) {
16 this.gui = gui;
17
18 this.deobfClasses = new ClassSelector(gui, ClassSelector.DEOBF_CLASS_COMPARATOR, true);
19 this.deobfClasses.setSelectionListener(gui.getController()::navigateTo);
20 this.deobfClasses.setRenameSelectionListener(gui::onPanelRename);
21
22 this.setLayout(new BorderLayout());
23 this.add(new JLabel(I18n.translate("info_panel.classes.deobfuscated")), BorderLayout.NORTH);
24 this.add(new JScrollPane(this.deobfClasses), BorderLayout.CENTER);
25 }
26}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java b/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
deleted file mode 100644
index 8637afd..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelEditor.java
+++ /dev/null
@@ -1,171 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.config.Config;
6import cuchaz.enigma.gui.BrowserCaret;
7import cuchaz.enigma.gui.Gui;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10import cuchaz.enigma.gui.util.ScaleUtil;
11
12import javax.swing.*;
13import java.awt.*;
14import java.awt.event.KeyAdapter;
15import java.awt.event.KeyEvent;
16import java.awt.event.MouseAdapter;
17import java.awt.event.MouseEvent;
18
19public class PanelEditor extends JEditorPane {
20 private boolean mouseIsPressed = false;
21 public int fontSize = 12;
22
23 public PanelEditor(Gui gui) {
24 this.setEditable(false);
25 this.setSelectionColor(new Color(31, 46, 90));
26 this.setCaret(new BrowserCaret());
27 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
28 this.addCaretListener(event -> gui.onCaretMove(event.getDot(), mouseIsPressed));
29 final PanelEditor self = this;
30 this.addMouseListener(new MouseAdapter() {
31 @Override
32 public void mousePressed(MouseEvent mouseEvent) {
33 mouseIsPressed = true;
34 }
35
36 @Override
37 public void mouseReleased(MouseEvent e) {
38 switch (e.getButton()) {
39 case MouseEvent.BUTTON3: // Right click
40 self.setCaretPosition(self.viewToModel(e.getPoint()));
41 break;
42
43 case 4: // Back navigation
44 gui.getController().openPreviousReference();
45 break;
46
47 case 5: // Forward navigation
48 gui.getController().openNextReference();
49 break;
50 }
51 mouseIsPressed = false;
52 }
53 });
54 this.addKeyListener(new KeyAdapter() {
55 @Override
56 public void keyPressed(KeyEvent event) {
57 if (event.isControlDown()) {
58 gui.setShouldNavigateOnClick(false);
59 switch (event.getKeyCode()) {
60 case KeyEvent.VK_I:
61 gui.popupMenu.showInheritanceMenu.doClick();
62 break;
63
64 case KeyEvent.VK_M:
65 gui.popupMenu.showImplementationsMenu.doClick();
66 break;
67
68 case KeyEvent.VK_N:
69 gui.popupMenu.openEntryMenu.doClick();
70 break;
71
72 case KeyEvent.VK_P:
73 gui.popupMenu.openPreviousMenu.doClick();
74 break;
75
76 case KeyEvent.VK_E:
77 gui.popupMenu.openNextMenu.doClick();
78 break;
79
80 case KeyEvent.VK_C:
81 if (event.isShiftDown()) {
82 gui.popupMenu.showCallsSpecificMenu.doClick();
83 } else {
84 gui.popupMenu.showCallsMenu.doClick();
85 }
86 break;
87
88 case KeyEvent.VK_O:
89 gui.popupMenu.toggleMappingMenu.doClick();
90 break;
91
92 case KeyEvent.VK_R:
93 gui.popupMenu.renameMenu.doClick();
94 break;
95
96 case KeyEvent.VK_D:
97 gui.popupMenu.editJavadocMenu.doClick();
98 break;
99
100 case KeyEvent.VK_F5:
101 gui.getController().refreshCurrentClass();
102 break;
103
104 case KeyEvent.VK_F:
105 // prevent navigating on click when quick find activated
106 break;
107
108 case KeyEvent.VK_ADD:
109 case KeyEvent.VK_EQUALS:
110 case KeyEvent.VK_PLUS:
111 self.offsetEditorZoom(2);
112 break;
113 case KeyEvent.VK_SUBTRACT:
114 case KeyEvent.VK_MINUS:
115 self.offsetEditorZoom(-2);
116 break;
117
118 default:
119 gui.setShouldNavigateOnClick(true); // CTRL
120 break;
121 }
122 }
123 }
124
125 @Override
126 public void keyTyped(KeyEvent event) {
127 if (!gui.popupMenu.renameMenu.isEnabled()) return;
128
129 if (!event.isControlDown() && !event.isAltDown() && Character.isJavaIdentifierPart(event.getKeyChar())) {
130 EnigmaProject project = gui.getController().project;
131 EntryReference<Entry<?>, Entry<?>> reference = project.getMapper().deobfuscate(gui.cursorReference);
132 Entry<?> entry = reference.getNameableEntry();
133
134 String name = String.valueOf(event.getKeyChar());
135 if (entry instanceof ClassEntry && ((ClassEntry) entry).getParent() == null) {
136 String packageName = ((ClassEntry) entry).getPackageName();
137 if (packageName != null) {
138 name = packageName + "/" + name;
139 }
140 }
141
142 gui.popupMenu.renameMenu.doClick();
143 gui.renameTextField.setText(name);
144 }
145 }
146
147 @Override
148 public void keyReleased(KeyEvent event) {
149 gui.setShouldNavigateOnClick(event.isControlDown());
150 }
151 });
152 }
153
154 public void offsetEditorZoom(int zoomAmount) {
155 int newResult = this.fontSize + zoomAmount;
156 if (newResult > 8 && newResult < 72) {
157 this.fontSize = newResult;
158 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
159 }
160 }
161
162 public void resetEditorZoom() {
163 this.fontSize = 12;
164 this.setFont(ScaleUtil.getFont(this.getFont().getFontName(), Font.PLAIN, this.fontSize));
165 }
166
167 @Override
168 public Color getCaretColor() {
169 return new Color(Config.getInstance().caretColor);
170 }
171}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java b/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
deleted file mode 100644
index de069bc..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelIdentifier.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.Gui;
4import cuchaz.enigma.utils.I18n;
5import cuchaz.enigma.gui.util.ScaleUtil;
6import cuchaz.enigma.utils.Utils;
7
8import javax.swing.*;
9import java.awt.*;
10
11public class PanelIdentifier extends JPanel {
12
13 private final Gui gui;
14
15 public PanelIdentifier(Gui gui) {
16 this.gui = gui;
17
18 this.setLayout(new GridLayout(4, 1, 0, 0));
19 this.setPreferredSize(ScaleUtil.getDimension(0, 100));
20 this.setBorder(BorderFactory.createTitledBorder(I18n.translate("info_panel.identifier")));
21 }
22
23 public void clearReference() {
24 this.removeAll();
25 JLabel label = new JLabel(I18n.translate("info_panel.identifier.none"));
26 Utils.unboldLabel(label);
27 label.setHorizontalAlignment(JLabel.CENTER);
28 this.add(label);
29
30 gui.redraw();
31 }
32}
diff --git a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java b/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
deleted file mode 100644
index dd7f9f9..0000000
--- a/src/main/java/cuchaz/enigma/gui/panels/PanelObf.java
+++ /dev/null
@@ -1,37 +0,0 @@
1package cuchaz.enigma.gui.panels;
2
3import cuchaz.enigma.gui.ClassSelector;
4import cuchaz.enigma.gui.Gui;
5import cuchaz.enigma.translation.representation.entry.ClassEntry;
6import cuchaz.enigma.utils.I18n;
7
8import javax.swing.*;
9import java.awt.*;
10import java.util.Comparator;
11
12public class PanelObf extends JPanel {
13
14 public final ClassSelector obfClasses;
15 private final Gui gui;
16
17 public PanelObf(Gui gui) {
18 this.gui = gui;
19
20 Comparator<ClassEntry> obfClassComparator = (a, b) -> {
21 String aname = a.getFullName();
22 String bname = b.getFullName();
23 if (aname.length() != bname.length()) {
24 return aname.length() - bname.length();
25 }
26 return aname.compareTo(bname);
27 };
28
29 this.obfClasses = new ClassSelector(gui, obfClassComparator, false);
30 this.obfClasses.setSelectionListener(gui.getController()::navigateTo);
31 this.obfClasses.setRenameSelectionListener(gui::onPanelRename);
32
33 this.setLayout(new BorderLayout());
34 this.add(new JLabel(I18n.translate("info_panel.classes.obfuscated")), BorderLayout.NORTH);
35 this.add(new JScrollPane(this.obfClasses), BorderLayout.CENTER);
36 }
37}
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java b/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
deleted file mode 100644
index e783530..0000000
--- a/src/main/java/cuchaz/enigma/gui/stats/StatsGenerator.java
+++ /dev/null
@@ -1,197 +0,0 @@
1package cuchaz.enigma.gui.stats;
2
3import com.google.gson.GsonBuilder;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.analysis.index.EntryIndex;
7import cuchaz.enigma.api.service.NameProposalService;
8import cuchaz.enigma.api.service.ObfuscationTestService;
9import cuchaz.enigma.translation.mapping.EntryRemapper;
10import cuchaz.enigma.translation.mapping.EntryResolver;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.*;
14import cuchaz.enigma.utils.I18n;
15
16import java.util.*;
17
18public class StatsGenerator {
19 private final EntryIndex entryIndex;
20 private final EntryRemapper mapper;
21 private final EntryResolver entryResolver;
22 private final List<ObfuscationTestService> obfuscationTestServices;
23 private final List<NameProposalService> nameProposalServices;
24
25 public StatsGenerator(EnigmaProject project) {
26 entryIndex = project.getJarIndex().getEntryIndex();
27 mapper = project.getMapper();
28 entryResolver = project.getJarIndex().getEntryResolver();
29 obfuscationTestServices = project.getEnigma().getServices().get(ObfuscationTestService.TYPE);
30 nameProposalServices = project.getEnigma().getServices().get(NameProposalService.TYPE);
31 }
32
33 public String generate(ProgressListener progress, Set<StatsMember> includedMembers) {
34 includedMembers = EnumSet.copyOf(includedMembers);
35 int totalWork = 0;
36
37 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
38 totalWork += entryIndex.getMethods().size();
39 }
40
41 if (includedMembers.contains(StatsMember.FIELDS)) {
42 totalWork += entryIndex.getFields().size();
43 }
44
45 if (includedMembers.contains(StatsMember.CLASSES)) {
46 totalWork += entryIndex.getClasses().size();
47 }
48
49 progress.init(totalWork, I18n.translate("progress.stats"));
50
51 Map<String, Integer> counts = new HashMap<>();
52
53 int numDone = 0;
54 if (includedMembers.contains(StatsMember.METHODS) || includedMembers.contains(StatsMember.PARAMETERS)) {
55 for (MethodEntry method : entryIndex.getMethods()) {
56 progress.step(numDone++, I18n.translate("type.methods"));
57 MethodEntry root = entryResolver
58 .resolveEntry(method, ResolutionStrategy.RESOLVE_ROOT)
59 .stream()
60 .findFirst()
61 .orElseThrow(AssertionError::new);
62
63 if (root == method && !((MethodDefEntry) method).getAccess().isSynthetic()) {
64 if (includedMembers.contains(StatsMember.METHODS)) {
65 update(counts, method);
66 }
67
68 if (includedMembers.contains(StatsMember.PARAMETERS)) {
69 int index = ((MethodDefEntry) method).getAccess().isStatic() ? 0 : 1;
70 for (TypeDescriptor argument : method.getDesc().getArgumentDescs()) {
71 update(counts, new LocalVariableEntry(method, index, "", true,null));
72 index += argument.getSize();
73 }
74 }
75 }
76 }
77 }
78
79 if (includedMembers.contains(StatsMember.FIELDS)) {
80 for (FieldEntry field : entryIndex.getFields()) {
81 progress.step(numDone++, I18n.translate("type.fields"));
82 update(counts, field);
83 }
84 }
85
86 if (includedMembers.contains(StatsMember.CLASSES)) {
87 for (ClassEntry clazz : entryIndex.getClasses()) {
88 progress.step(numDone++, I18n.translate("type.classes"));
89 update(counts, clazz);
90 }
91 }
92
93 progress.step(-1, I18n.translate("progress.stats.data"));
94
95 Tree<Integer> tree = new Tree<>();
96
97 for (Map.Entry<String, Integer> entry : counts.entrySet()) {
98 if (entry.getKey().startsWith("com.mojang")) continue; // just a few unmapped names, no point in having a subsection
99 tree.getNode(entry.getKey()).value = entry.getValue();
100 }
101
102 tree.collapse(tree.root);
103 return new GsonBuilder().setPrettyPrinting().create().toJson(tree.root);
104 }
105
106 private void update(Map<String, Integer> counts, Entry<?> entry) {
107 if (isObfuscated(entry)) {
108 String parent = mapper.deobfuscate(entry.getAncestry().get(0)).getName().replace('/', '.');
109 counts.put(parent, counts.getOrDefault(parent, 0) + 1);
110 }
111 }
112
113 private boolean isObfuscated(Entry<?> entry) {
114 String name = entry.getName();
115
116 if (!obfuscationTestServices.isEmpty()) {
117 for (ObfuscationTestService service : obfuscationTestServices) {
118 if (service.testDeobfuscated(entry)) {
119 return false;
120 }
121 }
122 }
123
124 if (!nameProposalServices.isEmpty()) {
125 for (NameProposalService service : nameProposalServices) {
126 if (service.proposeName(entry, mapper).isPresent()) {
127 return false;
128 }
129 }
130 }
131
132 String mappedName = mapper.deobfuscate(entry).getName();
133 if (mappedName != null && !mappedName.isEmpty() && !mappedName.equals(name)) {
134 return false;
135 }
136
137 return true;
138 }
139
140 private static class Tree<T> {
141 public final Node<T> root;
142 private final Map<String, Node<T>> nodes = new HashMap<>();
143
144 public static class Node<T> {
145 public String name;
146 public T value;
147 public List<Node<T>> children = new ArrayList<>();
148 private final transient Map<String, Node<T>> namedChildren = new HashMap<>();
149
150 public Node(String name, T value) {
151 this.name = name;
152 this.value = value;
153 }
154 }
155
156 public Tree() {
157 root = new Node<>("", null);
158 }
159
160 public Node<T> getNode(String name) {
161 Node<T> node = nodes.get(name);
162
163 if (node == null) {
164 node = root;
165
166 for (String part : name.split("\\.")) {
167 Node<T> child = node.namedChildren.get(part);
168
169 if (child == null) {
170 child = new Node<>(part, null);
171 node.namedChildren.put(part, child);
172 node.children.add(child);
173 }
174
175 node = child;
176 }
177
178 nodes.put(name, node);
179 }
180
181 return node;
182 }
183
184 public void collapse(Node<T> node) {
185 while (node.children.size() == 1) {
186 Node<T> child = node.children.get(0);
187 node.name = node.name.isEmpty() ? child.name : node.name + "." + child.name;
188 node.children = child.children;
189 node.value = child.value;
190 }
191
192 for (Node<T> child : node.children) {
193 collapse(child);
194 }
195 }
196 }
197}
diff --git a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java b/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
deleted file mode 100644
index 70b4f40..0000000
--- a/src/main/java/cuchaz/enigma/gui/stats/StatsMember.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.stats;
2
3public enum StatsMember {
4 METHODS,
5 FIELDS,
6 PARAMETERS,
7 CLASSES
8}
diff --git a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java b/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java
deleted file mode 100644
index 612e3e9..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/AbstractListCellRenderer.java
+++ /dev/null
@@ -1,77 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import java.awt.Component;
4import java.awt.event.MouseEvent;
5
6import javax.swing.*;
7import javax.swing.border.Border;
8
9public abstract class AbstractListCellRenderer<E> extends JPanel implements ListCellRenderer<E> {
10
11 private static final Border NO_FOCUS_BORDER = BorderFactory.createEmptyBorder(1, 1, 1, 1);
12
13 private Border noFocusBorder;
14
15 public AbstractListCellRenderer() {
16 setBorder(getNoFocusBorder());
17 }
18
19 protected Border getNoFocusBorder() {
20 if (noFocusBorder == null) {
21 Border border = UIManager.getLookAndFeel().getDefaults().getBorder("List.List.cellNoFocusBorder");
22 noFocusBorder = border != null ? border : NO_FOCUS_BORDER;
23 }
24 return noFocusBorder;
25 }
26
27 protected Border getBorder(boolean isSelected, boolean cellHasFocus) {
28 Border b = null;
29 if (cellHasFocus) {
30 UIDefaults defaults = UIManager.getLookAndFeel().getDefaults();
31 if (isSelected) {
32 b = defaults.getBorder("List.focusSelectedCellHighlightBorder");
33 }
34 if (b == null) {
35 b = defaults.getBorder("List.focusCellHighlightBorder");
36 }
37 } else {
38 b = getNoFocusBorder();
39 }
40 return b;
41 }
42
43 public abstract void updateUiForEntry(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus);
44
45 @Override
46 public Component getListCellRendererComponent(JList<? extends E> list, E value, int index, boolean isSelected, boolean cellHasFocus) {
47 updateUiForEntry(list, value, index, isSelected, cellHasFocus);
48
49 if (isSelected) {
50 setBackground(list.getSelectionBackground());
51 setForeground(list.getSelectionForeground());
52 } else {
53 setBackground(list.getBackground());
54 setForeground(list.getForeground());
55 }
56
57 setEnabled(list.isEnabled());
58 setFont(list.getFont());
59
60 setBorder(getBorder(isSelected, cellHasFocus));
61
62 // This isn't the width of the cell, but it's close enough for where it's needed (getComponentAt in getToolTipText)
63 setSize(list.getWidth(), getPreferredSize().height);
64
65 return this;
66 }
67
68 @Override
69 public String getToolTipText(MouseEvent event) {
70 Component c = getComponentAt(event.getPoint());
71 if (c instanceof JComponent) {
72 return ((JComponent) c).getToolTipText();
73 }
74 return getToolTipText();
75 }
76
77}
diff --git a/src/main/java/cuchaz/enigma/gui/util/History.java b/src/main/java/cuchaz/enigma/gui/util/History.java
deleted file mode 100644
index 94f3105..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/History.java
+++ /dev/null
@@ -1,49 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import com.google.common.collect.Queues;
4
5import java.util.Deque;
6
7public class History<T> {
8 private final Deque<T> previous = Queues.newArrayDeque();
9 private final Deque<T> next = Queues.newArrayDeque();
10 private T current;
11
12 public History(T initial) {
13 current = initial;
14 }
15
16 public T getCurrent() {
17 return current;
18 }
19
20 public void push(T value) {
21 previous.addLast(current);
22 current = value;
23 next.clear();
24 }
25
26 public void replace(T value) {
27 current = value;
28 }
29
30 public boolean canGoBack() {
31 return !previous.isEmpty();
32 }
33
34 public T goBack() {
35 next.addFirst(current);
36 current = previous.removeLast();
37 return current;
38 }
39
40 public boolean canGoForward() {
41 return !next.isEmpty();
42 }
43
44 public T goForward() {
45 previous.addLast(current);
46 current = next.removeFirst();
47 return current;
48 }
49}
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java b/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java
deleted file mode 100644
index d045c6d..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/ScaleChangeListener.java
+++ /dev/null
@@ -1,8 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3@FunctionalInterface
4public interface ScaleChangeListener {
5
6 void onScaleChanged(float scale, float oldScale);
7
8}
diff --git a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java b/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
deleted file mode 100644
index 9f722e9..0000000
--- a/src/main/java/cuchaz/enigma/gui/util/ScaleUtil.java
+++ /dev/null
@@ -1,110 +0,0 @@
1package cuchaz.enigma.gui.util;
2
3import java.awt.Dimension;
4import java.awt.Font;
5import java.io.IOException;
6import java.lang.reflect.Field;
7import java.util.ArrayList;
8import java.util.List;
9
10import javax.swing.BorderFactory;
11import javax.swing.UIManager;
12import javax.swing.border.Border;
13
14import com.github.swingdpi.UiDefaultsScaler;
15import com.github.swingdpi.plaf.BasicTweaker;
16import com.github.swingdpi.plaf.MetalTweaker;
17import com.github.swingdpi.plaf.NimbusTweaker;
18import com.github.swingdpi.plaf.WindowsTweaker;
19import cuchaz.enigma.config.Config;
20import de.sciss.syntaxpane.DefaultSyntaxKit;
21
22public class ScaleUtil {
23
24 private static List<ScaleChangeListener> listeners = new ArrayList<>();
25
26 public static float getScaleFactor() {
27 return Config.getInstance().scaleFactor;
28 }
29
30 public static void setScaleFactor(float scaleFactor) {
31 float oldScale = getScaleFactor();
32 float clamped = Math.min(Math.max(0.25f, scaleFactor), 10.0f);
33 Config.getInstance().scaleFactor = clamped;
34 try {
35 Config.getInstance().saveConfig();
36 } catch (IOException e) {
37 e.printStackTrace();
38 }
39 listeners.forEach(l -> l.onScaleChanged(clamped, oldScale));
40 }
41
42 public static void addListener(ScaleChangeListener listener) {
43 listeners.add(listener);
44 }
45
46 public static void removeListener(ScaleChangeListener listener) {
47 listeners.remove(listener);
48 }
49
50 public static Dimension getDimension(int width, int height) {
51 return new Dimension(scale(width), scale(height));
52 }
53
54 public static Font getFont(String fontName, int plain, int fontSize) {
55 return scaleFont(new Font(fontName, plain, fontSize));
56 }
57
58 public static Font scaleFont(Font font) {
59 return createTweakerForCurrentLook(getScaleFactor()).modifyFont("", font);
60 }
61
62 public static float scale(float f) {
63 return f * getScaleFactor();
64 }
65
66 public static float invert(float f) {
67 return f / getScaleFactor();
68 }
69
70 public static int scale(int i) {
71 return (int) (i * getScaleFactor());
72 }
73
74 public static Border createEmptyBorder(int top, int left, int bottom, int right) {
75 return BorderFactory.createEmptyBorder(scale(top), scale(left), scale(bottom), scale(right));
76 }
77
78 public static int invert(int i) {
79 return (int) (i / getScaleFactor());
80 }
81
82 public static void applyScaling() {
83 float scale = getScaleFactor();
84 UiDefaultsScaler.updateAndApplyGlobalScaling((int) (100 * scale), true);
85 try {
86 Field defaultFontField = DefaultSyntaxKit.class.getDeclaredField("DEFAULT_FONT");
87 defaultFontField.setAccessible(true);
88 Font font = (Font) defaultFontField.get(null);
89 font = font.deriveFont(12 * scale);
90 defaultFontField.set(null, font);
91 } catch (NoSuchFieldException | IllegalAccessException e) {
92 e.printStackTrace();
93 }
94 }
95
96 private static BasicTweaker createTweakerForCurrentLook(float dpiScaling) {
97 String testString = UIManager.getLookAndFeel().getName().toLowerCase();
98 if (testString.contains("windows")) {
99 return new WindowsTweaker(dpiScaling, testString.contains("classic"));
100 }
101 if (testString.contains("metal")) {
102 return new MetalTweaker(dpiScaling);
103 }
104 if (testString.contains("nimbus")) {
105 return new NimbusTweaker(dpiScaling);
106 }
107 return new BasicTweaker(dpiScaling);
108 }
109
110}
diff --git a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
deleted file mode 100644
index 2cfe823..0000000
--- a/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java
+++ /dev/null
@@ -1,164 +0,0 @@
1package cuchaz.enigma.network;
2
3import com.google.common.io.MoreFiles;
4import cuchaz.enigma.*;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryRemapper;
7import cuchaz.enigma.translation.mapping.serde.MappingFormat;
8import cuchaz.enigma.utils.Utils;
9import joptsimple.OptionParser;
10import joptsimple.OptionSet;
11import joptsimple.OptionSpec;
12
13import java.io.IOException;
14import java.io.PrintWriter;
15import java.nio.file.Files;
16import java.nio.file.Path;
17import java.nio.file.Paths;
18import java.util.concurrent.BlockingQueue;
19import java.util.concurrent.Executors;
20import java.util.concurrent.LinkedBlockingDeque;
21import java.util.concurrent.TimeUnit;
22
23public class DedicatedEnigmaServer extends EnigmaServer {
24
25 private final EnigmaProfile profile;
26 private final MappingFormat mappingFormat;
27 private final Path mappingsFile;
28 private final PrintWriter log;
29 private BlockingQueue<Runnable> tasks = new LinkedBlockingDeque<>();
30
31 public DedicatedEnigmaServer(
32 byte[] jarChecksum,
33 char[] password,
34 EnigmaProfile profile,
35 MappingFormat mappingFormat,
36 Path mappingsFile,
37 PrintWriter log,
38 EntryRemapper mappings,
39 int port
40 ) {
41 super(jarChecksum, password, mappings, port);
42 this.profile = profile;
43 this.mappingFormat = mappingFormat;
44 this.mappingsFile = mappingsFile;
45 this.log = log;
46 }
47
48 @Override
49 protected void runOnThread(Runnable task) {
50 tasks.add(task);
51 }
52
53 @Override
54 public void log(String message) {
55 super.log(message);
56 log.println(message);
57 }
58
59 public static void main(String[] args) {
60 OptionParser parser = new OptionParser();
61
62 OptionSpec<Path> jarOpt = parser.accepts("jar", "Jar file to open at startup")
63 .withRequiredArg()
64 .required()
65 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
66
67 OptionSpec<Path> mappingsOpt = parser.accepts("mappings", "Mappings file to open at startup")
68 .withRequiredArg()
69 .required()
70 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
71
72 OptionSpec<Path> profileOpt = parser.accepts("profile", "Profile json to apply at startup")
73 .withRequiredArg()
74 .withValuesConvertedBy(Main.PathConverter.INSTANCE);
75
76 OptionSpec<Integer> portOpt = parser.accepts("port", "Port to run the server on")
77 .withOptionalArg()
78 .ofType(Integer.class)
79 .defaultsTo(EnigmaServer.DEFAULT_PORT);
80
81 OptionSpec<String> passwordOpt = parser.accepts("password", "The password to join the server")
82 .withRequiredArg()
83 .defaultsTo("");
84
85 OptionSpec<Path> logFileOpt = parser.accepts("log", "The log file to write to")
86 .withRequiredArg()
87 .withValuesConvertedBy(Main.PathConverter.INSTANCE)
88 .defaultsTo(Paths.get("log.txt"));
89
90 OptionSet parsedArgs = parser.parse(args);
91 Path jar = parsedArgs.valueOf(jarOpt);
92 Path mappingsFile = parsedArgs.valueOf(mappingsOpt);
93 Path profileFile = parsedArgs.valueOf(profileOpt);
94 int port = parsedArgs.valueOf(portOpt);
95 char[] password = parsedArgs.valueOf(passwordOpt).toCharArray();
96 if (password.length > EnigmaServer.MAX_PASSWORD_LENGTH) {
97 System.err.println("Password too long, must be at most " + EnigmaServer.MAX_PASSWORD_LENGTH + " characters");
98 System.exit(1);
99 }
100 Path logFile = parsedArgs.valueOf(logFileOpt);
101
102 System.out.println("Starting Enigma server");
103 DedicatedEnigmaServer server;
104 try {
105 byte[] checksum = Utils.zipSha1(parsedArgs.valueOf(jarOpt));
106
107 EnigmaProfile profile = EnigmaProfile.read(profileFile);
108 Enigma enigma = Enigma.builder().setProfile(profile).build();
109 System.out.println("Indexing Jar...");
110 EnigmaProject project = enigma.openJar(jar, ProgressListener.none());
111
112 MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
113 EntryRemapper mappings;
114 if (!Files.exists(mappingsFile)) {
115 mappings = EntryRemapper.empty(project.getJarIndex());
116 } else {
117 System.out.println("Reading mappings...");
118 if (Files.isDirectory(mappingsFile)) {
119 mappingFormat = MappingFormat.ENIGMA_DIRECTORY;
120 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(mappingsFile))) {
121 mappingFormat = MappingFormat.ENIGMA_ZIP;
122 } else {
123 mappingFormat = MappingFormat.ENIGMA_FILE;
124 }
125 mappings = EntryRemapper.mapped(project.getJarIndex(), mappingFormat.read(mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters()));
126 }
127
128 PrintWriter log = new PrintWriter(Files.newBufferedWriter(logFile));
129
130 server = new DedicatedEnigmaServer(checksum, password, profile, mappingFormat, mappingsFile, log, mappings, port);
131 server.start();
132 System.out.println("Server started");
133 } catch (IOException | MappingParseException e) {
134 System.err.println("Error starting server!");
135 e.printStackTrace();
136 System.exit(1);
137 return;
138 }
139
140 // noinspection RedundantSuppression
141 // noinspection Convert2MethodRef - javac 8 bug
142 Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() -> server.runOnThread(() -> server.saveMappings()), 0, 1, TimeUnit.MINUTES);
143 Runtime.getRuntime().addShutdownHook(new Thread(server::saveMappings));
144
145 while (true) {
146 try {
147 server.tasks.take().run();
148 } catch (InterruptedException e) {
149 break;
150 }
151 }
152 }
153
154 @Override
155 public synchronized void stop() {
156 super.stop();
157 System.exit(0);
158 }
159
160 private void saveMappings() {
161 mappingFormat.write(getMappings().getObfToDeobf(), getMappings().takeMappingDelta(), mappingsFile, ProgressListener.none(), profile.getMappingSaveParameters());
162 log.flush();
163 }
164}
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaClient.java b/src/main/java/cuchaz/enigma/network/EnigmaClient.java
deleted file mode 100644
index bfa53d7..0000000
--- a/src/main/java/cuchaz/enigma/network/EnigmaClient.java
+++ /dev/null
@@ -1,85 +0,0 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.packet.LoginC2SPacket;
5import cuchaz.enigma.network.packet.Packet;
6import cuchaz.enigma.network.packet.PacketRegistry;
7
8import javax.swing.SwingUtilities;
9import java.io.DataInput;
10import java.io.DataInputStream;
11import java.io.DataOutput;
12import java.io.DataOutputStream;
13import java.io.EOFException;
14import java.io.IOException;
15import java.net.Socket;
16import java.net.SocketException;
17
18public class EnigmaClient {
19
20 private final GuiController controller;
21
22 private final String ip;
23 private final int port;
24 private Socket socket;
25 private DataOutput output;
26
27 public EnigmaClient(GuiController controller, String ip, int port) {
28 this.controller = controller;
29 this.ip = ip;
30 this.port = port;
31 }
32
33 public void connect() throws IOException {
34 socket = new Socket(ip, port);
35 output = new DataOutputStream(socket.getOutputStream());
36 Thread thread = new Thread(() -> {
37 try {
38 DataInput input = new DataInputStream(socket.getInputStream());
39 while (true) {
40 int packetId;
41 try {
42 packetId = input.readUnsignedByte();
43 } catch (EOFException | SocketException e) {
44 break;
45 }
46 Packet<GuiController> packet = PacketRegistry.createS2CPacket(packetId);
47 if (packet == null) {
48 throw new IOException("Received invalid packet id " + packetId);
49 }
50 packet.read(input);
51 SwingUtilities.invokeLater(() -> packet.handle(controller));
52 }
53 } catch (IOException e) {
54 controller.disconnectIfConnected(e.toString());
55 return;
56 }
57 controller.disconnectIfConnected("Disconnected");
58 });
59 thread.setName("Client I/O thread");
60 thread.setDaemon(true);
61 thread.start();
62 }
63
64 public synchronized void disconnect() {
65 if (socket != null && !socket.isClosed()) {
66 try {
67 socket.close();
68 } catch (IOException e1) {
69 System.err.println("Failed to close socket");
70 e1.printStackTrace();
71 }
72 }
73 }
74
75
76 public void sendPacket(Packet<ServerPacketHandler> packet) {
77 try {
78 output.writeByte(PacketRegistry.getC2SId(packet));
79 packet.write(output);
80 } catch (IOException e) {
81 controller.disconnectIfConnected(e.toString());
82 }
83 }
84
85}
diff --git a/src/main/java/cuchaz/enigma/network/EnigmaServer.java b/src/main/java/cuchaz/enigma/network/EnigmaServer.java
deleted file mode 100644
index b0e15a3..0000000
--- a/src/main/java/cuchaz/enigma/network/EnigmaServer.java
+++ /dev/null
@@ -1,292 +0,0 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.packet.KickS2CPacket;
5import cuchaz.enigma.network.packet.MessageS2CPacket;
6import cuchaz.enigma.network.packet.Packet;
7import cuchaz.enigma.network.packet.PacketRegistry;
8import cuchaz.enigma.network.packet.RemoveMappingS2CPacket;
9import cuchaz.enigma.network.packet.RenameS2CPacket;
10import cuchaz.enigma.network.packet.UserListS2CPacket;
11import cuchaz.enigma.translation.mapping.EntryMapping;
12import cuchaz.enigma.translation.mapping.EntryRemapper;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.utils.Message;
15
16import java.io.DataInput;
17import java.io.DataInputStream;
18import java.io.DataOutput;
19import java.io.DataOutputStream;
20import java.io.EOFException;
21import java.io.IOException;
22import java.net.ServerSocket;
23import java.net.Socket;
24import java.net.SocketException;
25import java.util.ArrayList;
26import java.util.Collections;
27import java.util.HashMap;
28import java.util.HashSet;
29import java.util.List;
30import java.util.Map;
31import java.util.Set;
32import java.util.concurrent.CopyOnWriteArrayList;
33
34public abstract class EnigmaServer {
35
36 // https://discordapp.com/channels/507304429255393322/566418023372816394/700292322918793347
37 public static final int DEFAULT_PORT = 34712;
38 public static final int PROTOCOL_VERSION = 0;
39 public static final String OWNER_USERNAME = "Owner";
40 public static final int CHECKSUM_SIZE = 20;
41 public static final int MAX_PASSWORD_LENGTH = 255; // length is written as a byte in the login packet
42
43 private final int port;
44 private ServerSocket socket;
45 private List<Socket> clients = new CopyOnWriteArrayList<>();
46 private Map<Socket, String> usernames = new HashMap<>();
47 private Set<Socket> unapprovedClients = new HashSet<>();
48
49 private final byte[] jarChecksum;
50 private final char[] password;
51
52 public static final int DUMMY_SYNC_ID = 0;
53 private final EntryRemapper mappings;
54 private Map<Entry<?>, Integer> syncIds = new HashMap<>();
55 private Map<Integer, Entry<?>> inverseSyncIds = new HashMap<>();
56 private Map<Integer, Set<Socket>> clientsNeedingConfirmation = new HashMap<>();
57 private int nextSyncId = DUMMY_SYNC_ID + 1;
58
59 private static int nextIoId = 0;
60
61 public EnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) {
62 this.jarChecksum = jarChecksum;
63 this.password = password;
64 this.mappings = mappings;
65 this.port = port;
66 }
67
68 public void start() throws IOException {
69 socket = new ServerSocket(port);
70 log("Server started on " + socket.getInetAddress() + ":" + port);
71 Thread thread = new Thread(() -> {
72 try {
73 while (!socket.isClosed()) {
74 acceptClient();
75 }
76 } catch (SocketException e) {
77 System.out.println("Server closed");
78 } catch (IOException e) {
79 e.printStackTrace();
80 }
81 });
82 thread.setName("Server client listener");
83 thread.setDaemon(true);
84 thread.start();
85 }
86
87 private void acceptClient() throws IOException {
88 Socket client = socket.accept();
89 clients.add(client);
90 Thread thread = new Thread(() -> {
91 try {
92 DataInput input = new DataInputStream(client.getInputStream());
93 while (true) {
94 int packetId;
95 try {
96 packetId = input.readUnsignedByte();
97 } catch (EOFException | SocketException e) {
98 break;
99 }
100 Packet<ServerPacketHandler> packet = PacketRegistry.createC2SPacket(packetId);
101 if (packet == null) {
102 throw new IOException("Received invalid packet id " + packetId);
103 }
104 packet.read(input);
105 runOnThread(() -> packet.handle(new ServerPacketHandler(client, this)));
106 }
107 } catch (IOException e) {
108 kick(client, e.toString());
109 e.printStackTrace();
110 return;
111 }
112 kick(client, "disconnect.disconnected");
113 });
114 thread.setName("Server I/O thread #" + (nextIoId++));
115 thread.setDaemon(true);
116 thread.start();
117 }
118
119 public void stop() {
120 runOnThread(() -> {
121 if (socket != null && !socket.isClosed()) {
122 for (Socket client : clients) {
123 kick(client, "disconnect.server_closed");
124 }
125 try {
126 socket.close();
127 } catch (IOException e) {
128 System.err.println("Failed to close server socket");
129 e.printStackTrace();
130 }
131 }
132 });
133 }
134
135 public void kick(Socket client, String reason) {
136 if (!clients.remove(client)) return;
137
138 sendPacket(client, new KickS2CPacket(reason));
139
140 clientsNeedingConfirmation.values().removeIf(list -> {
141 list.remove(client);
142 return list.isEmpty();
143 });
144 String username = usernames.remove(client);
145 try {
146 client.close();
147 } catch (IOException e) {
148 System.err.println("Failed to close server client socket");
149 e.printStackTrace();
150 }
151
152 if (username != null) {
153 System.out.println("Kicked " + username + " because " + reason);
154 sendMessage(Message.disconnect(username));
155 }
156 sendUsernamePacket();
157 }
158
159 public boolean isUsernameTaken(String username) {
160 return usernames.containsValue(username);
161 }
162
163 public void setUsername(Socket client, String username) {
164 usernames.put(client, username);
165 sendUsernamePacket();
166 }
167
168 private void sendUsernamePacket() {
169 List<String> usernames = new ArrayList<>(this.usernames.values());
170 Collections.sort(usernames);
171 sendToAll(new UserListS2CPacket(usernames));
172 }
173
174 public String getUsername(Socket client) {
175 return usernames.get(client);
176 }
177
178 public void sendPacket(Socket client, Packet<GuiController> packet) {
179 if (!client.isClosed()) {
180 int packetId = PacketRegistry.getS2CId(packet);
181 try {
182 DataOutput output = new DataOutputStream(client.getOutputStream());
183 output.writeByte(packetId);
184 packet.write(output);
185 } catch (IOException e) {
186 if (!(packet instanceof KickS2CPacket)) {
187 kick(client, e.toString());
188 e.printStackTrace();
189 }
190 }
191 }
192 }
193
194 public void sendToAll(Packet<GuiController> packet) {
195 for (Socket client : clients) {
196 sendPacket(client, packet);
197 }
198 }
199
200 public void sendToAllExcept(Socket excluded, Packet<GuiController> packet) {
201 for (Socket client : clients) {
202 if (client != excluded) {
203 sendPacket(client, packet);
204 }
205 }
206 }
207
208 public boolean canModifyEntry(Socket client, Entry<?> entry) {
209 if (unapprovedClients.contains(client)) {
210 return false;
211 }
212
213 Integer syncId = syncIds.get(entry);
214 if (syncId == null) {
215 return true;
216 }
217 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
218 return clients == null || !clients.contains(client);
219 }
220
221 public int lockEntry(Socket exception, Entry<?> entry) {
222 int syncId = nextSyncId;
223 nextSyncId++;
224 // sync id is sent as an unsigned short, can't have more than 65536
225 if (nextSyncId == 65536) {
226 nextSyncId = DUMMY_SYNC_ID + 1;
227 }
228 Integer oldSyncId = syncIds.get(entry);
229 if (oldSyncId != null) {
230 clientsNeedingConfirmation.remove(oldSyncId);
231 }
232 syncIds.put(entry, syncId);
233 inverseSyncIds.put(syncId, entry);
234 Set<Socket> clients = new HashSet<>(this.clients);
235 clients.remove(exception);
236 clientsNeedingConfirmation.put(syncId, clients);
237 return syncId;
238 }
239
240 public void confirmChange(Socket client, int syncId) {
241 if (usernames.containsKey(client)) {
242 unapprovedClients.remove(client);
243 }
244
245 Set<Socket> clients = clientsNeedingConfirmation.get(syncId);
246 if (clients != null) {
247 clients.remove(client);
248 if (clients.isEmpty()) {
249 clientsNeedingConfirmation.remove(syncId);
250 syncIds.remove(inverseSyncIds.remove(syncId));
251 }
252 }
253 }
254
255 public void sendCorrectMapping(Socket client, Entry<?> entry, boolean refreshClassTree) {
256 EntryMapping oldMapping = mappings.getDeobfMapping(entry);
257 String oldName = oldMapping == null ? null : oldMapping.getTargetName();
258 if (oldName == null) {
259 sendPacket(client, new RemoveMappingS2CPacket(DUMMY_SYNC_ID, entry));
260 } else {
261 sendPacket(client, new RenameS2CPacket(0, entry, oldName, refreshClassTree));
262 }
263 }
264
265 protected abstract void runOnThread(Runnable task);
266
267 public void log(String message) {
268 System.out.println(message);
269 }
270
271 protected boolean isRunning() {
272 return !socket.isClosed();
273 }
274
275 public byte[] getJarChecksum() {
276 return jarChecksum;
277 }
278
279 public char[] getPassword() {
280 return password;
281 }
282
283 public EntryRemapper getMappings() {
284 return mappings;
285 }
286
287 public void sendMessage(Message message) {
288 log(String.format("[MSG] %s", message.translate()));
289 sendToAll(new MessageS2CPacket(message));
290 }
291
292}
diff --git a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java b/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java
deleted file mode 100644
index 21c6825..0000000
--- a/src/main/java/cuchaz/enigma/network/IntegratedEnigmaServer.java
+++ /dev/null
@@ -1,16 +0,0 @@
1package cuchaz.enigma.network;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4
5import javax.swing.*;
6
7public class IntegratedEnigmaServer extends EnigmaServer {
8 public IntegratedEnigmaServer(byte[] jarChecksum, char[] password, EntryRemapper mappings, int port) {
9 super(jarChecksum, password, mappings, port);
10 }
11
12 @Override
13 protected void runOnThread(Runnable task) {
14 SwingUtilities.invokeLater(task);
15 }
16}
diff --git a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java b/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java
deleted file mode 100644
index 8618553..0000000
--- a/src/main/java/cuchaz/enigma/network/ServerPacketHandler.java
+++ /dev/null
@@ -1,22 +0,0 @@
1package cuchaz.enigma.network;
2
3import java.net.Socket;
4
5public class ServerPacketHandler {
6
7 private final Socket client;
8 private final EnigmaServer server;
9
10 public ServerPacketHandler(Socket client, EnigmaServer server) {
11 this.client = client;
12 this.server = server;
13 }
14
15 public Socket getClient() {
16 return client;
17 }
18
19 public EnigmaServer getServer() {
20 return server;
21 }
22}
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
deleted file mode 100644
index 4d5d86f..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsC2SPacket.java
+++ /dev/null
@@ -1,59 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.network.ServerPacketHandler;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.utils.Message;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.DataInput;
11import java.io.DataOutput;
12import java.io.IOException;
13
14public class ChangeDocsC2SPacket implements Packet<ServerPacketHandler> {
15 private Entry<?> entry;
16 private String newDocs;
17
18 ChangeDocsC2SPacket() {
19 }
20
21 public ChangeDocsC2SPacket(Entry<?> entry, String newDocs) {
22 this.entry = entry;
23 this.newDocs = newDocs;
24 }
25
26 @Override
27 public void read(DataInput input) throws IOException {
28 this.entry = PacketHelper.readEntry(input);
29 this.newDocs = PacketHelper.readString(input);
30 }
31
32 @Override
33 public void write(DataOutput output) throws IOException {
34 PacketHelper.writeEntry(output, entry);
35 PacketHelper.writeString(output, newDocs);
36 }
37
38 @Override
39 public void handle(ServerPacketHandler handler) {
40 EntryMapping mapping = handler.getServer().getMappings().getDeobfMapping(entry);
41
42 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
43 if (!valid) {
44 String oldDocs = mapping == null ? null : mapping.getJavadoc();
45 handler.getServer().sendPacket(handler.getClient(), new ChangeDocsS2CPacket(EnigmaServer.DUMMY_SYNC_ID, entry, oldDocs == null ? "" : oldDocs));
46 return;
47 }
48
49 if (mapping == null) {
50 mapping = new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName());
51 }
52 handler.getServer().getMappings().mapFromObf(entry, mapping.withDocs(Utils.isBlank(newDocs) ? null : newDocs));
53
54 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
55 handler.getServer().sendToAllExcept(handler.getClient(), new ChangeDocsS2CPacket(syncId, entry, newDocs));
56 handler.getServer().sendMessage(Message.editDocs(handler.getServer().getUsername(handler.getClient()), entry));
57 }
58
59}
diff --git a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
deleted file mode 100644
index bf5b7cb..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/ChangeDocsS2CPacket.java
+++ /dev/null
@@ -1,44 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.gui.GuiController;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class ChangeDocsS2CPacket implements Packet<GuiController> {
12 private int syncId;
13 private Entry<?> entry;
14 private String newDocs;
15
16 ChangeDocsS2CPacket() {
17 }
18
19 public ChangeDocsS2CPacket(int syncId, Entry<?> entry, String newDocs) {
20 this.syncId = syncId;
21 this.entry = entry;
22 this.newDocs = newDocs;
23 }
24
25 @Override
26 public void read(DataInput input) throws IOException {
27 this.syncId = input.readUnsignedShort();
28 this.entry = PacketHelper.readEntry(input);
29 this.newDocs = PacketHelper.readString(input);
30 }
31
32 @Override
33 public void write(DataOutput output) throws IOException {
34 output.writeShort(syncId);
35 PacketHelper.writeEntry(output, entry);
36 PacketHelper.writeString(output, newDocs);
37 }
38
39 @Override
40 public void handle(GuiController controller) {
41 controller.changeDocs(new EntryReference<>(entry, entry.getName()), newDocs, false);
42 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
43 }
44}
diff --git a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java
deleted file mode 100644
index 78ef964..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/ConfirmChangeC2SPacket.java
+++ /dev/null
@@ -1,33 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8
9public class ConfirmChangeC2SPacket implements Packet<ServerPacketHandler> {
10 private int syncId;
11
12 ConfirmChangeC2SPacket() {
13 }
14
15 public ConfirmChangeC2SPacket(int syncId) {
16 this.syncId = syncId;
17 }
18
19 @Override
20 public void read(DataInput input) throws IOException {
21 this.syncId = input.readUnsignedShort();
22 }
23
24 @Override
25 public void write(DataOutput output) throws IOException {
26 output.writeShort(syncId);
27 }
28
29 @Override
30 public void handle(ServerPacketHandler handler) {
31 handler.getServer().confirmChange(handler.getClient(), syncId);
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
deleted file mode 100644
index bd007d3..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/KickS2CPacket.java
+++ /dev/null
@@ -1,33 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
4
5import java.io.DataInput;
6import java.io.DataOutput;
7import java.io.IOException;
8
9public class KickS2CPacket implements Packet<GuiController> {
10 private String reason;
11
12 KickS2CPacket() {
13 }
14
15 public KickS2CPacket(String reason) {
16 this.reason = reason;
17 }
18
19 @Override
20 public void read(DataInput input) throws IOException {
21 this.reason = PacketHelper.readString(input);
22 }
23
24 @Override
25 public void write(DataOutput output) throws IOException {
26 PacketHelper.writeString(output, reason);
27 }
28
29 @Override
30 public void handle(GuiController controller) {
31 controller.disconnectIfConnected(reason);
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java
deleted file mode 100644
index 722cbbf..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/LoginC2SPacket.java
+++ /dev/null
@@ -1,75 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.EnigmaServer;
4import cuchaz.enigma.network.ServerPacketHandler;
5import cuchaz.enigma.utils.Message;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10import java.util.Arrays;
11
12public class LoginC2SPacket implements Packet<ServerPacketHandler> {
13 private byte[] jarChecksum;
14 private char[] password;
15 private String username;
16
17 LoginC2SPacket() {
18 }
19
20 public LoginC2SPacket(byte[] jarChecksum, char[] password, String username) {
21 this.jarChecksum = jarChecksum;
22 this.password = password;
23 this.username = username;
24 }
25
26 @Override
27 public void read(DataInput input) throws IOException {
28 if (input.readUnsignedShort() != EnigmaServer.PROTOCOL_VERSION) {
29 throw new IOException("Mismatching protocol");
30 }
31 this.jarChecksum = new byte[EnigmaServer.CHECKSUM_SIZE];
32 input.readFully(jarChecksum);
33 this.password = new char[input.readUnsignedByte()];
34 for (int i = 0; i < password.length; i++) {
35 password[i] = input.readChar();
36 }
37 this.username = PacketHelper.readString(input);
38 }
39
40 @Override
41 public void write(DataOutput output) throws IOException {
42 output.writeShort(EnigmaServer.PROTOCOL_VERSION);
43 output.write(jarChecksum);
44 output.writeByte(password.length);
45 for (char c : password) {
46 output.writeChar(c);
47 }
48 PacketHelper.writeString(output, username);
49 }
50
51 @Override
52 public void handle(ServerPacketHandler handler) {
53 boolean usernameTaken = handler.getServer().isUsernameTaken(username);
54 handler.getServer().setUsername(handler.getClient(), username);
55 handler.getServer().log(username + " logged in with IP " + handler.getClient().getInetAddress().toString() + ":" + handler.getClient().getPort());
56
57 if (!Arrays.equals(password, handler.getServer().getPassword())) {
58 handler.getServer().kick(handler.getClient(), "disconnect.wrong_password");
59 return;
60 }
61
62 if (usernameTaken) {
63 handler.getServer().kick(handler.getClient(), "disconnect.username_taken");
64 return;
65 }
66
67 if (!Arrays.equals(jarChecksum, handler.getServer().getJarChecksum())) {
68 handler.getServer().kick(handler.getClient(), "disconnect.wrong_jar");
69 return;
70 }
71
72 handler.getServer().sendPacket(handler.getClient(), new SyncMappingsS2CPacket(handler.getServer().getMappings().getObfToDeobf()));
73 handler.getServer().sendMessage(Message.connect(username));
74 }
75}
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
deleted file mode 100644
index 98d20d9..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedC2SPacket.java
+++ /dev/null
@@ -1,48 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.utils.Message;
7
8import java.io.DataInput;
9import java.io.DataOutput;
10import java.io.IOException;
11
12public class MarkDeobfuscatedC2SPacket implements Packet<ServerPacketHandler> {
13 private Entry<?> entry;
14
15 MarkDeobfuscatedC2SPacket() {
16 }
17
18 public MarkDeobfuscatedC2SPacket(Entry<?> entry) {
19 this.entry = entry;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 this.entry = PacketHelper.readEntry(input);
25 }
26
27 @Override
28 public void write(DataOutput output) throws IOException {
29 PacketHelper.writeEntry(output, entry);
30 }
31
32 @Override
33 public void handle(ServerPacketHandler handler) {
34 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
35 if (!valid) {
36 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
37 return;
38 }
39
40 handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(handler.getServer().getMappings().deobfuscate(entry).getName()));
41 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " marked " + entry + " as deobfuscated");
42
43 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
44 handler.getServer().sendToAllExcept(handler.getClient(), new MarkDeobfuscatedS2CPacket(syncId, entry));
45 handler.getServer().sendMessage(Message.markDeobf(handler.getServer().getUsername(handler.getClient()), entry));
46
47 }
48}
diff --git a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
deleted file mode 100644
index b7d6eda..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/MarkDeobfuscatedS2CPacket.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.gui.GuiController;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class MarkDeobfuscatedS2CPacket implements Packet<GuiController> {
12 private int syncId;
13 private Entry<?> entry;
14
15 MarkDeobfuscatedS2CPacket() {
16 }
17
18 public MarkDeobfuscatedS2CPacket(int syncId, Entry<?> entry) {
19 this.syncId = syncId;
20 this.entry = entry;
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.syncId = input.readUnsignedShort();
26 this.entry = PacketHelper.readEntry(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 output.writeShort(syncId);
32 PacketHelper.writeEntry(output, entry);
33 }
34
35 @Override
36 public void handle(GuiController controller) {
37 controller.markAsDeobfuscated(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java
deleted file mode 100644
index b8e0f14..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/MessageC2SPacket.java
+++ /dev/null
@@ -1,39 +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.ServerPacketHandler;
8import cuchaz.enigma.utils.Message;
9
10public class MessageC2SPacket implements Packet<ServerPacketHandler> {
11
12 private String message;
13
14 MessageC2SPacket() {
15 }
16
17 public MessageC2SPacket(String message) {
18 this.message = message;
19 }
20
21 @Override
22 public void read(DataInput input) throws IOException {
23 message = PacketHelper.readString(input);
24 }
25
26 @Override
27 public void write(DataOutput output) throws IOException {
28 PacketHelper.writeString(output, message);
29 }
30
31 @Override
32 public void handle(ServerPacketHandler handler) {
33 String message = this.message.trim();
34 if (!message.isEmpty()) {
35 handler.getServer().sendMessage(Message.chat(handler.getServer().getUsername(handler.getClient()), message));
36 }
37 }
38
39}
diff --git a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java
deleted file mode 100644
index edeaae0..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/MessageS2CPacket.java
+++ /dev/null
@@ -1,36 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7import cuchaz.enigma.gui.GuiController;
8import cuchaz.enigma.utils.Message;
9
10public class MessageS2CPacket implements Packet<GuiController> {
11
12 private Message message;
13
14 MessageS2CPacket() {
15 }
16
17 public MessageS2CPacket(Message message) {
18 this.message = message;
19 }
20
21 @Override
22 public void read(DataInput input) throws IOException {
23 message = Message.read(input);
24 }
25
26 @Override
27 public void write(DataOutput output) throws IOException {
28 message.write(output);
29 }
30
31 @Override
32 public void handle(GuiController handler) {
33 handler.addMessage(message);
34 }
35
36}
diff --git a/src/main/java/cuchaz/enigma/network/packet/Packet.java b/src/main/java/cuchaz/enigma/network/packet/Packet.java
deleted file mode 100644
index 2f16dfb..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/Packet.java
+++ /dev/null
@@ -1,15 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6
7public interface Packet<H> {
8
9 void read(DataInput input) throws IOException;
10
11 void write(DataOutput output) throws IOException;
12
13 void handle(H handler);
14
15}
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java b/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
deleted file mode 100644
index 464606e..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/PacketHelper.java
+++ /dev/null
@@ -1,135 +0,0 @@
1package cuchaz.enigma.network.packet;
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;
12import java.io.DataOutput;
13import java.io.IOException;
14import java.nio.charset.StandardCharsets;
15
16public class PacketHelper {
17
18 private static final int ENTRY_CLASS = 0, ENTRY_FIELD = 1, ENTRY_METHOD = 2, ENTRY_LOCAL_VAR = 3;
19 private static final int MAX_STRING_LENGTH = 65535;
20
21 public static Entry<?> readEntry(DataInput input) throws IOException {
22 return readEntry(input, null, true);
23 }
24
25 public static Entry<?> readEntry(DataInput input, Entry<?> parent, boolean includeParent) throws IOException {
26 int type = input.readUnsignedByte();
27
28 if (includeParent && input.readBoolean()) {
29 parent = readEntry(input, null, true);
30 }
31
32 String name = readString(input);
33
34 String javadocs = null;
35 if (input.readBoolean()) {
36 javadocs = readString(input);
37 }
38
39 switch (type) {
40 case ENTRY_CLASS: {
41 if (parent != null && !(parent instanceof ClassEntry)) {
42 throw new IOException("Class requires class parent");
43 }
44 return new ClassEntry((ClassEntry) parent, name, javadocs);
45 }
46 case ENTRY_FIELD: {
47 if (!(parent instanceof ClassEntry)) {
48 throw new IOException("Field requires class parent");
49 }
50 TypeDescriptor desc = new TypeDescriptor(readString(input));
51 return new FieldEntry((ClassEntry) parent, name, desc, javadocs);
52 }
53 case ENTRY_METHOD: {
54 if (!(parent instanceof ClassEntry)) {
55 throw new IOException("Method requires class parent");
56 }
57 MethodDescriptor desc = new MethodDescriptor(readString(input));
58 return new MethodEntry((ClassEntry) parent, name, desc, javadocs);
59 }
60 case ENTRY_LOCAL_VAR: {
61 if (!(parent instanceof MethodEntry)) {
62 throw new IOException("Local variable requires method parent");
63 }
64 int index = input.readUnsignedShort();
65 boolean parameter = input.readBoolean();
66 return new LocalVariableEntry((MethodEntry) parent, index, name, parameter, javadocs);
67 }
68 default: throw new IOException("Received unknown entry type " + type);
69 }
70 }
71
72 public static void writeEntry(DataOutput output, Entry<?> entry) throws IOException {
73 writeEntry(output, entry, true);
74 }
75
76 public static void writeEntry(DataOutput output, Entry<?> entry, boolean includeParent) throws IOException {
77 // type
78 if (entry instanceof ClassEntry) {
79 output.writeByte(ENTRY_CLASS);
80 } else if (entry instanceof FieldEntry) {
81 output.writeByte(ENTRY_FIELD);
82 } else if (entry instanceof MethodEntry) {
83 output.writeByte(ENTRY_METHOD);
84 } else if (entry instanceof LocalVariableEntry) {
85 output.writeByte(ENTRY_LOCAL_VAR);
86 } else {
87 throw new IOException("Don't know how to serialize entry of type " + entry.getClass().getSimpleName());
88 }
89
90 // parent
91 if (includeParent) {
92 output.writeBoolean(entry.getParent() != null);
93 if (entry.getParent() != null) {
94 writeEntry(output, entry.getParent(), true);
95 }
96 }
97
98 // name
99 writeString(output, entry.getName());
100
101 // javadocs
102 output.writeBoolean(entry.getJavadocs() != null);
103 if (entry.getJavadocs() != null) {
104 writeString(output, entry.getJavadocs());
105 }
106
107 // type-specific stuff
108 if (entry instanceof FieldEntry) {
109 writeString(output, ((FieldEntry) entry).getDesc().toString());
110 } else if (entry instanceof MethodEntry) {
111 writeString(output, ((MethodEntry) entry).getDesc().toString());
112 } else if (entry instanceof LocalVariableEntry) {
113 LocalVariableEntry localVar = (LocalVariableEntry) entry;
114 output.writeShort(localVar.getIndex());
115 output.writeBoolean(localVar.isArgument());
116 }
117 }
118
119 public static String readString(DataInput input) throws IOException {
120 int length = input.readUnsignedShort();
121 byte[] bytes = new byte[length];
122 input.readFully(bytes);
123 return new String(bytes, StandardCharsets.UTF_8);
124 }
125
126 public static void writeString(DataOutput output, String str) throws IOException {
127 byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
128 if (bytes.length > MAX_STRING_LENGTH) {
129 throw new IOException("String too long, was " + bytes.length + " bytes, max " + MAX_STRING_LENGTH + " allowed");
130 }
131 output.writeShort(bytes.length);
132 output.write(bytes);
133 }
134
135}
diff --git a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java b/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
deleted file mode 100644
index ba5d9de..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/PacketRegistry.java
+++ /dev/null
@@ -1,64 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.ServerPacketHandler;
5
6import java.util.HashMap;
7import java.util.Map;
8import java.util.function.Supplier;
9
10public class PacketRegistry {
11
12 private static final Map<Class<? extends Packet<ServerPacketHandler>>, Integer> c2sPacketIds = new HashMap<>();
13 private static final Map<Integer, Supplier<? extends Packet<ServerPacketHandler>>> c2sPacketCreators = new HashMap<>();
14 private static final Map<Class<? extends Packet<GuiController>>, Integer> s2cPacketIds = new HashMap<>();
15 private static final Map<Integer, Supplier<? extends Packet<GuiController>>> s2cPacketCreators = new HashMap<>();
16
17 private static <T extends Packet<ServerPacketHandler>> void registerC2S(int id, Class<T> clazz, Supplier<T> creator) {
18 c2sPacketIds.put(clazz, id);
19 c2sPacketCreators.put(id, creator);
20 }
21
22 private static <T extends Packet<GuiController>> void registerS2C(int id, Class<T> clazz, Supplier<T> creator) {
23 s2cPacketIds.put(clazz, id);
24 s2cPacketCreators.put(id, creator);
25 }
26
27 static {
28 registerC2S(0, LoginC2SPacket.class, LoginC2SPacket::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);
35
36 registerS2C(0, KickS2CPacket.class, KickS2CPacket::new);
37 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);
43 registerS2C(7, UserListS2CPacket.class, UserListS2CPacket::new);
44 }
45
46 public static int getC2SId(Packet<ServerPacketHandler> packet) {
47 return c2sPacketIds.get(packet.getClass());
48 }
49
50 public static Packet<ServerPacketHandler> createC2SPacket(int id) {
51 Supplier<? extends Packet<ServerPacketHandler>> creator = c2sPacketCreators.get(id);
52 return creator == null ? null : creator.get();
53 }
54
55 public static int getS2CId(Packet<GuiController> packet) {
56 return s2cPacketIds.get(packet.getClass());
57 }
58
59 public static Packet<GuiController> createS2CPacket(int id) {
60 Supplier<? extends Packet<GuiController>> creator = s2cPacketCreators.get(id);
61 return creator == null ? null : creator.get();
62 }
63
64}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
deleted file mode 100644
index a3f3d91..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingC2SPacket.java
+++ /dev/null
@@ -1,55 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.throwables.IllegalNameException;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.utils.Message;
7
8import java.io.DataInput;
9import java.io.DataOutput;
10import java.io.IOException;
11
12public class RemoveMappingC2SPacket implements Packet<ServerPacketHandler> {
13 private Entry<?> entry;
14
15 RemoveMappingC2SPacket() {
16 }
17
18 public RemoveMappingC2SPacket(Entry<?> entry) {
19 this.entry = entry;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 this.entry = PacketHelper.readEntry(input);
25 }
26
27 @Override
28 public void write(DataOutput output) throws IOException {
29 PacketHelper.writeEntry(output, entry);
30 }
31
32 @Override
33 public void handle(ServerPacketHandler handler) {
34 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
35
36 if (valid) {
37 try {
38 handler.getServer().getMappings().removeByObf(entry);
39 } catch (IllegalNameException e) {
40 valid = false;
41 }
42 }
43
44 if (!valid) {
45 handler.getServer().sendCorrectMapping(handler.getClient(), entry, true);
46 return;
47 }
48
49 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " removed the mapping for " + entry);
50
51 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
52 handler.getServer().sendToAllExcept(handler.getClient(), new RemoveMappingS2CPacket(syncId, entry));
53 handler.getServer().sendMessage(Message.removeMapping(handler.getServer().getUsername(handler.getClient()), entry));
54 }
55}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
deleted file mode 100644
index 7bb1b00..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/RemoveMappingS2CPacket.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.gui.GuiController;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class RemoveMappingS2CPacket implements Packet<GuiController> {
12 private int syncId;
13 private Entry<?> entry;
14
15 RemoveMappingS2CPacket() {
16 }
17
18 public RemoveMappingS2CPacket(int syncId, Entry<?> entry) {
19 this.syncId = syncId;
20 this.entry = entry;
21 }
22
23 @Override
24 public void read(DataInput input) throws IOException {
25 this.syncId = input.readUnsignedShort();
26 this.entry = PacketHelper.readEntry(input);
27 }
28
29 @Override
30 public void write(DataOutput output) throws IOException {
31 output.writeShort(syncId);
32 PacketHelper.writeEntry(output, entry);
33 }
34
35 @Override
36 public void handle(GuiController controller) {
37 controller.removeMapping(new EntryReference<>(entry, entry.getName()), false);
38 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
deleted file mode 100644
index 03e95d6..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/RenameC2SPacket.java
+++ /dev/null
@@ -1,64 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.network.ServerPacketHandler;
4import cuchaz.enigma.throwables.IllegalNameException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.entry.Entry;
7import cuchaz.enigma.utils.Message;
8
9import java.io.DataInput;
10import java.io.DataOutput;
11import java.io.IOException;
12
13public class RenameC2SPacket implements Packet<ServerPacketHandler> {
14 private Entry<?> entry;
15 private String newName;
16 private boolean refreshClassTree;
17
18 RenameC2SPacket() {
19 }
20
21 public RenameC2SPacket(Entry<?> entry, String newName, boolean refreshClassTree) {
22 this.entry = entry;
23 this.newName = newName;
24 this.refreshClassTree = refreshClassTree;
25 }
26
27 @Override
28 public void read(DataInput input) throws IOException {
29 this.entry = PacketHelper.readEntry(input);
30 this.newName = PacketHelper.readString(input);
31 this.refreshClassTree = input.readBoolean();
32 }
33
34 @Override
35 public void write(DataOutput output) throws IOException {
36 PacketHelper.writeEntry(output, entry);
37 PacketHelper.writeString(output, newName);
38 output.writeBoolean(refreshClassTree);
39 }
40
41 @Override
42 public void handle(ServerPacketHandler handler) {
43 boolean valid = handler.getServer().canModifyEntry(handler.getClient(), entry);
44
45 if (valid) {
46 try {
47 handler.getServer().getMappings().mapFromObf(entry, new EntryMapping(newName));
48 } catch (IllegalNameException e) {
49 valid = false;
50 }
51 }
52
53 if (!valid) {
54 handler.getServer().sendCorrectMapping(handler.getClient(), entry, refreshClassTree);
55 return;
56 }
57
58 handler.getServer().log(handler.getServer().getUsername(handler.getClient()) + " renamed " + entry + " to " + newName);
59
60 int syncId = handler.getServer().lockEntry(handler.getClient(), entry);
61 handler.getServer().sendToAllExcept(handler.getClient(), new RenameS2CPacket(syncId, entry, newName, refreshClassTree));
62 handler.getServer().sendMessage(Message.rename(handler.getServer().getUsername(handler.getClient()), entry, newName));
63 }
64}
diff --git a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
deleted file mode 100644
index 058f0e5..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/RenameS2CPacket.java
+++ /dev/null
@@ -1,48 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.analysis.EntryReference;
4import cuchaz.enigma.gui.GuiController;
5import cuchaz.enigma.translation.representation.entry.Entry;
6
7import java.io.DataInput;
8import java.io.DataOutput;
9import java.io.IOException;
10
11public class RenameS2CPacket implements Packet<GuiController> {
12 private int syncId;
13 private Entry<?> entry;
14 private String newName;
15 private boolean refreshClassTree;
16
17 RenameS2CPacket() {
18 }
19
20 public RenameS2CPacket(int syncId, Entry<?> entry, String newName, boolean refreshClassTree) {
21 this.syncId = syncId;
22 this.entry = entry;
23 this.newName = newName;
24 this.refreshClassTree = refreshClassTree;
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.newName = PacketHelper.readString(input);
32 this.refreshClassTree = input.readBoolean();
33 }
34
35 @Override
36 public void write(DataOutput output) throws IOException {
37 output.writeShort(syncId);
38 PacketHelper.writeEntry(output, entry);
39 PacketHelper.writeString(output, newName);
40 output.writeBoolean(refreshClassTree);
41 }
42
43 @Override
44 public void handle(GuiController controller) {
45 controller.rename(new EntryReference<>(entry, entry.getName()), newName, refreshClassTree, false);
46 controller.sendPacket(new ConfirmChangeC2SPacket(syncId));
47 }
48}
diff --git a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
deleted file mode 100644
index e6378d1..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/SyncMappingsS2CPacket.java
+++ /dev/null
@@ -1,88 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import cuchaz.enigma.gui.GuiController;
4import cuchaz.enigma.network.EnigmaServer;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.tree.EntryTree;
7import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
8import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11import java.io.DataInput;
12import java.io.DataOutput;
13import java.io.IOException;
14import java.util.Collection;
15import java.util.List;
16import java.util.stream.Collectors;
17
18public class SyncMappingsS2CPacket implements Packet<GuiController> {
19 private EntryTree<EntryMapping> mappings;
20
21 SyncMappingsS2CPacket() {
22 }
23
24 public SyncMappingsS2CPacket(EntryTree<EntryMapping> mappings) {
25 this.mappings = mappings;
26 }
27
28 @Override
29 public void read(DataInput input) throws IOException {
30 mappings = new HashEntryTree<>();
31 int size = input.readInt();
32 for (int i = 0; i < size; i++) {
33 readEntryTreeNode(input, null);
34 }
35 }
36
37 private void readEntryTreeNode(DataInput input, Entry<?> parent) throws IOException {
38 Entry<?> entry = PacketHelper.readEntry(input, parent, false);
39 EntryMapping mapping = null;
40 if (input.readBoolean()) {
41 String name = input.readUTF();
42 if (input.readBoolean()) {
43 String javadoc = input.readUTF();
44 mapping = new EntryMapping(name, javadoc);
45 } else {
46 mapping = new EntryMapping(name);
47 }
48 }
49 mappings.insert(entry, mapping);
50 int size = input.readUnsignedShort();
51 for (int i = 0; i < size; i++) {
52 readEntryTreeNode(input, entry);
53 }
54 }
55
56 @Override
57 public void write(DataOutput output) throws IOException {
58 List<EntryTreeNode<EntryMapping>> roots = mappings.getRootNodes().collect(Collectors.toList());
59 output.writeInt(roots.size());
60 for (EntryTreeNode<EntryMapping> node : roots) {
61 writeEntryTreeNode(output, node);
62 }
63 }
64
65 private static void writeEntryTreeNode(DataOutput output, EntryTreeNode<EntryMapping> node) throws IOException {
66 PacketHelper.writeEntry(output, node.getEntry(), false);
67 EntryMapping value = node.getValue();
68 output.writeBoolean(value != null);
69 if (value != null) {
70 PacketHelper.writeString(output, value.getTargetName());
71 output.writeBoolean(value.getJavadoc() != null);
72 if (value.getJavadoc() != null) {
73 PacketHelper.writeString(output, value.getJavadoc());
74 }
75 }
76 Collection<? extends EntryTreeNode<EntryMapping>> children = node.getChildNodes();
77 output.writeShort(children.size());
78 for (EntryTreeNode<EntryMapping> child : children) {
79 writeEntryTreeNode(output, child);
80 }
81 }
82
83 @Override
84 public void handle(GuiController controller) {
85 controller.openMappings(mappings);
86 controller.sendPacket(new ConfirmChangeC2SPacket(EnigmaServer.DUMMY_SYNC_ID));
87 }
88}
diff --git a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java b/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
deleted file mode 100644
index 8904848..0000000
--- a/src/main/java/cuchaz/enigma/network/packet/UserListS2CPacket.java
+++ /dev/null
@@ -1,44 +0,0 @@
1package cuchaz.enigma.network.packet;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6import java.util.ArrayList;
7import java.util.List;
8
9import cuchaz.enigma.gui.GuiController;
10
11public class UserListS2CPacket implements Packet<GuiController> {
12
13 private List<String> users;
14
15 UserListS2CPacket() {
16 }
17
18 public UserListS2CPacket(List<String> users) {
19 this.users = users;
20 }
21
22 @Override
23 public void read(DataInput input) throws IOException {
24 int len = input.readUnsignedShort();
25 users = new ArrayList<>(len);
26 for (int i = 0; i < len; i++) {
27 users.add(input.readUTF());
28 }
29 }
30
31 @Override
32 public void write(DataOutput output) throws IOException {
33 output.writeShort(users.size());
34 for (String user : users) {
35 PacketHelper.writeString(output, user);
36 }
37 }
38
39 @Override
40 public void handle(GuiController handler) {
41 handler.updateUserList(users);
42 }
43
44}
diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java
deleted file mode 100644
index c9666d5..0000000
--- a/src/main/java/cuchaz/enigma/source/Decompiler.java
+++ /dev/null
@@ -1,5 +0,0 @@
1package cuchaz.enigma.source;
2
3public interface Decompiler {
4 Source getSource(String className);
5}
diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java
deleted file mode 100644
index 377ccbc..0000000
--- a/src/main/java/cuchaz/enigma/source/DecompilerService.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.ClassProvider;
4import cuchaz.enigma.api.service.EnigmaService;
5import cuchaz.enigma.api.service.EnigmaServiceType;
6
7public interface DecompilerService extends EnigmaService {
8 EnigmaServiceType<DecompilerService> TYPE = EnigmaServiceType.create("decompiler");
9
10 Decompiler create(ClassProvider classProvider, SourceSettings settings);
11}
diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java
deleted file mode 100644
index 7d154a6..0000000
--- a/src/main/java/cuchaz/enigma/source/Decompilers.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.source.cfr.CfrDecompiler;
4import cuchaz.enigma.source.procyon.ProcyonDecompiler;
5
6public class Decompilers {
7 public static final DecompilerService PROCYON = ProcyonDecompiler::new;
8 public static final DecompilerService CFR = CfrDecompiler::new;
9}
diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java
deleted file mode 100644
index 43c4de0..0000000
--- a/src/main/java/cuchaz/enigma/source/Source.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.source;
2
3import cuchaz.enigma.translation.mapping.EntryRemapper;
4
5public interface Source {
6 String asString();
7
8 Source addJavadocs(EntryRemapper remapper);
9
10 SourceIndex index();
11}
diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java
deleted file mode 100644
index 6a335ec..0000000
--- a/src/main/java/cuchaz/enigma/source/SourceIndex.java
+++ /dev/null
@@ -1,174 +0,0 @@
1package cuchaz.enigma.source;
2
3import com.google.common.collect.HashMultimap;
4import com.google.common.collect.Lists;
5import com.google.common.collect.Maps;
6import com.google.common.collect.Multimap;
7import cuchaz.enigma.analysis.EntryReference;
8import cuchaz.enigma.analysis.Token;
9import cuchaz.enigma.gui.SourceRemapper;
10import cuchaz.enigma.translation.mapping.EntryResolver;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.entry.Entry;
13
14import java.util.Collection;
15import java.util.List;
16import java.util.Map;
17import java.util.TreeMap;
18import java.util.stream.Collectors;
19
20public class SourceIndex {
21 private String source;
22 private List<Integer> lineOffsets;
23 private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference;
24 private final Multimap<EntryReference<Entry<?>, Entry<?>>, Token> referenceToTokens;
25 private final Map<Entry<?>, Token> declarationToToken;
26
27 public SourceIndex() {
28 tokenToReference = new TreeMap<>();
29 referenceToTokens = HashMultimap.create();
30 declarationToToken = Maps.newHashMap();
31 }
32
33 public SourceIndex(String source) {
34 this();
35 setSource(source);
36 }
37
38 public void setSource(String source) {
39 this.source = source;
40 lineOffsets = Lists.newArrayList();
41 lineOffsets.add(0);
42
43 for (int i = 0; i < this.source.length(); i++) {
44 if (this.source.charAt(i) == '\n') {
45 lineOffsets.add(i + 1);
46 }
47 }
48 }
49
50 public String getSource() {
51 return source;
52 }
53
54 public int getLineNumber(int position) {
55 int line = 0;
56
57 for (int offset : lineOffsets) {
58 if (offset > position) {
59 break;
60 }
61
62 line++;
63 }
64
65 return line;
66 }
67
68 public int getColumnNumber(int position) {
69 return position - lineOffsets.get(getLineNumber(position) - 1) + 1;
70 }
71
72 public int getPosition(int line, int column) {
73 return lineOffsets.get(line - 1) + column - 1;
74 }
75
76 public Iterable<Entry<?>> declarations() {
77 return declarationToToken.keySet();
78 }
79
80 public Iterable<Token> declarationTokens() {
81 return declarationToToken.values();
82 }
83
84 public Token getDeclarationToken(Entry<?> entry) {
85 return declarationToToken.get(entry);
86 }
87
88 public void addDeclaration(Token token, Entry<?> deobfEntry) {
89 if (token != null) {
90 EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text);
91 tokenToReference.put(token, reference);
92 referenceToTokens.put(reference, token);
93 declarationToToken.put(deobfEntry, token);
94 }
95 }
96
97 public Iterable<EntryReference<Entry<?>, Entry<?>>> references() {
98 return referenceToTokens.keySet();
99 }
100
101 public EntryReference<Entry<?>, Entry<?>> getReference(Token token) {
102 if (token == null) {
103 return null;
104 }
105
106 return tokenToReference.get(token);
107 }
108
109 public Iterable<Token> referenceTokens() {
110 return tokenToReference.keySet();
111 }
112
113 public Token getReferenceToken(int pos) {
114 Token token = tokenToReference.floorKey(new Token(pos, pos, null));
115
116 if (token != null && token.contains(pos)) {
117 return token;
118 }
119
120 return null;
121 }
122
123 public Collection<Token> getReferenceTokens(EntryReference<Entry<?>, Entry<?>> deobfReference) {
124 return referenceToTokens.get(deobfReference);
125 }
126
127 public void addReference(Token token, Entry<?> deobfEntry, Entry<?> deobfContext) {
128 if (token != null) {
129 EntryReference<Entry<?>, Entry<?>> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext);
130 tokenToReference.put(token, deobfReference);
131 referenceToTokens.put(deobfReference, token);
132 }
133 }
134
135 public void resolveReferences(EntryResolver resolver) {
136 // resolve all the classes in the source references
137 for (Token token : Lists.newArrayList(referenceToTokens.values())) {
138 EntryReference<Entry<?>, Entry<?>> reference = tokenToReference.get(token);
139 EntryReference<Entry<?>, Entry<?>> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST);
140
141 // replace the reference
142 tokenToReference.replace(token, resolvedReference);
143
144 Collection<Token> tokens = referenceToTokens.removeAll(reference);
145 referenceToTokens.putAll(resolvedReference, tokens);
146 }
147 }
148
149 public SourceIndex remapTo(SourceRemapper.Result result) {
150 SourceIndex remapped = new SourceIndex(result.getSource());
151
152 for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) {
153 remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue()));
154 }
155
156 for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.asMap().entrySet()) {
157 EntryReference<Entry<?>, Entry<?>> reference = entry.getKey();
158 Collection<Token> oldTokens = entry.getValue();
159
160 Collection<Token> newTokens = oldTokens
161 .stream()
162 .map(result::getRemappedToken)
163 .collect(Collectors.toList());
164
165 remapped.referenceToTokens.putAll(reference, newTokens);
166 }
167
168 for (Map.Entry<Token, EntryReference<Entry<?>, Entry<?>>> entry : tokenToReference.entrySet()) {
169 remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue());
170 }
171
172 return remapped;
173 }
174}
diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java
deleted file mode 100644
index f6c68e9..0000000
--- a/src/main/java/cuchaz/enigma/source/SourceSettings.java
+++ /dev/null
@@ -1,11 +0,0 @@
1package cuchaz.enigma.source;
2
3public class SourceSettings {
4 public final boolean removeImports;
5 public final boolean removeVariableFinal;
6
7 public SourceSettings(boolean removeImports, boolean removeVariableFinal) {
8 this.removeImports = removeImports;
9 this.removeVariableFinal = removeVariableFinal;
10 }
11}
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java
deleted file mode 100644
index 9e37f16..0000000
--- a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java
+++ /dev/null
@@ -1,108 +0,0 @@
1package cuchaz.enigma.source.cfr;
2
3import com.google.common.io.ByteStreams;
4import cuchaz.enigma.ClassProvider;
5import cuchaz.enigma.source.Decompiler;
6import cuchaz.enigma.source.Source;
7import cuchaz.enigma.source.SourceSettings;
8import org.benf.cfr.reader.apiunreleased.ClassFileSource2;
9import org.benf.cfr.reader.apiunreleased.JarContent;
10import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair;
11import org.benf.cfr.reader.entities.ClassFile;
12import org.benf.cfr.reader.mapping.MappingFactory;
13import org.benf.cfr.reader.mapping.ObfuscationMapping;
14import org.benf.cfr.reader.relationship.MemberNameResolver;
15import org.benf.cfr.reader.state.DCCommonState;
16import org.benf.cfr.reader.state.TypeUsageCollectingDumper;
17import org.benf.cfr.reader.util.AnalysisType;
18import org.benf.cfr.reader.util.CannotLoadClassException;
19import org.benf.cfr.reader.util.collections.ListFactory;
20import org.benf.cfr.reader.util.getopt.Options;
21import org.benf.cfr.reader.util.getopt.OptionsImpl;
22import org.objectweb.asm.ClassWriter;
23import org.objectweb.asm.tree.ClassNode;
24
25import java.io.IOException;
26import java.io.InputStream;
27import java.util.Collection;
28import java.util.HashMap;
29import java.util.Map;
30
31
32public class CfrDecompiler implements Decompiler {
33 private final DCCommonState state;
34
35 public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) {
36 Map<String, String> options = new HashMap<>();
37
38 state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() {
39 @Override
40 public JarContent addJarContent(String s, AnalysisType analysisType) {
41 return null;
42 }
43
44 @Override
45 public void informAnalysisRelativePathDetail(String usePath, String classFilePath) {
46
47 }
48
49 @Override
50 public Collection<String> addJar(String jarPath) {
51 return null;
52 }
53
54 @Override
55 public String getPossiblyRenamedPath(String path) {
56 return path;
57 }
58
59 @Override
60 public Pair<byte[], String> getClassFileContent(String path) {
61 ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.')));
62
63 if (node == null) {
64 try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) {
65 if (classResource != null) {
66 return new Pair<>(ByteStreams.toByteArray(classResource), path);
67 }
68 } catch (IOException ignored) {}
69
70 return null;
71 }
72
73 ClassWriter cw = new ClassWriter(0);
74 node.accept(cw);
75 return new Pair<>(cw.toByteArray(), path);
76 }
77 });
78 }
79
80 @Override
81 public Source getSource(String className) {
82 DCCommonState state = this.state;
83 Options options = state.getOptions();
84
85 ObfuscationMapping mapping = MappingFactory.get(options, state);
86 state = new DCCommonState(state, mapping);
87 ClassFile tree = state.getClassFileMaybePath(className);
88
89 state.configureWith(tree);
90
91 // To make sure we're analysing the cached version
92 try {
93 tree = state.getClassFile(tree.getClassType());
94 } catch (CannotLoadClassException ignored) {}
95
96 if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) {
97 tree.loadInnerClasses(state);
98 }
99
100 if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) {
101 MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes()));
102 }
103
104 TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree);
105 tree.analyseTop(state, typeUsageCollector);
106 return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation());
107 }
108}
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
deleted file mode 100644
index d4f2da6..0000000
--- a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java
+++ /dev/null
@@ -1,38 +0,0 @@
1package cuchaz.enigma.source.cfr;
2
3import cuchaz.enigma.source.Source;
4import cuchaz.enigma.source.SourceIndex;
5import cuchaz.enigma.translation.mapping.EntryRemapper;
6import org.benf.cfr.reader.entities.ClassFile;
7import org.benf.cfr.reader.state.DCCommonState;
8import org.benf.cfr.reader.state.TypeUsageInformation;
9
10public class CfrSource implements Source {
11 private final ClassFile tree;
12 private final SourceIndex index;
13 private final String string;
14
15 public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) {
16 this.tree = tree;
17
18 EnigmaDumper dumper = new EnigmaDumper(typeUsages);
19 tree.dump(state.getObfuscationMapping().wrap(dumper));
20 index = dumper.getIndex();
21 string = dumper.getString();
22 }
23
24 @Override
25 public String asString() {
26 return string;
27 }
28
29 @Override
30 public Source addJavadocs(EntryRemapper remapper) {
31 return this; // TODO
32 }
33
34 @Override
35 public SourceIndex index() {
36 return index;
37 }
38}
diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
deleted file mode 100644
index 09e0a9b..0000000
--- a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java
+++ /dev/null
@@ -1,433 +0,0 @@
1package cuchaz.enigma.source.cfr;
2
3import cuchaz.enigma.analysis.Token;
4import cuchaz.enigma.source.SourceIndex;
5import cuchaz.enigma.translation.representation.MethodDescriptor;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7import cuchaz.enigma.translation.representation.entry.*;
8import org.benf.cfr.reader.bytecode.analysis.types.*;
9import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable;
10import org.benf.cfr.reader.entities.Field;
11import org.benf.cfr.reader.entities.Method;
12import org.benf.cfr.reader.mapping.NullMapping;
13import org.benf.cfr.reader.mapping.ObfuscationMapping;
14import org.benf.cfr.reader.state.TypeUsageInformation;
15import org.benf.cfr.reader.util.collections.SetFactory;
16import org.benf.cfr.reader.util.output.DelegatingDumper;
17import org.benf.cfr.reader.util.output.Dumpable;
18import org.benf.cfr.reader.util.output.Dumper;
19import org.benf.cfr.reader.util.output.TypeContext;
20
21import java.util.Set;
22import java.util.stream.Collectors;
23
24public class EnigmaDumper implements Dumper {
25 private int outputCount = 0;
26 private int indent;
27 private boolean atStart = true;
28 private boolean pendingCR = false;
29 private final StringBuilder sb = new StringBuilder();
30 private final TypeUsageInformation typeUsageInformation;
31 private final Set<JavaTypeInstance> emitted = SetFactory.newSet();
32 private final SourceIndex index = new SourceIndex();
33 private int position;
34
35
36 public EnigmaDumper(TypeUsageInformation typeUsageInformation) {
37 this.typeUsageInformation = typeUsageInformation;
38 }
39
40 private void append(String s) {
41 sb.append(s);
42 position += s.length();
43 }
44
45 private String getDesc(JavaTypeInstance type) {
46 if (!type.isUsableType() && type != RawJavaType.VOID) {
47 throw new IllegalArgumentException(type.toString());
48 }
49
50 if (type instanceof JavaGenericBaseInstance) {
51 return getDesc(type.getDeGenerifiedType());
52 }
53
54 if (type instanceof JavaRefTypeInstance) {
55 return "L" + type.getRawName().replace('.', '/') + ";";
56 }
57
58 if (type instanceof JavaArrayTypeInstance) {
59 return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection());
60 }
61
62 if (type instanceof RawJavaType) {
63 switch ((RawJavaType) type) {
64 case BOOLEAN:
65 return "Z";
66 case BYTE:
67 return "B";
68 case CHAR:
69 return "C";
70 case SHORT:
71 return "S";
72 case INT:
73 return "I";
74 case LONG:
75 return "J";
76 case FLOAT:
77 return "F";
78 case DOUBLE:
79 return "D";
80 case VOID:
81 return "V";
82 default:
83 throw new AssertionError();
84 }
85 }
86
87 throw new AssertionError();
88 }
89
90 private MethodEntry getMethodEntry(MethodPrototype method) {
91 if (method == null || method.getClassType() == null) {
92 return null;
93 }
94
95 MethodDescriptor desc = new MethodDescriptor(
96 method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()),
97 new TypeDescriptor(method.getName().equals("<init>") || method.getName().equals("<clinit>") ? "V" : getDesc(method.getReturnType()))
98 );
99
100 return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc);
101 }
102
103 private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) {
104 int variableIndex = method.isInstanceMethod() ? 1 : 0;
105 for (int i = 0; i < parameterIndex; i++) {
106 variableIndex += method.getArgs().get(i).getStackType().getComputationCategory();
107 }
108
109 return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null);
110 }
111
112 private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) {
113 return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type)));
114 }
115
116 private ClassEntry getClassEntry(JavaTypeInstance type) {
117 return new ClassEntry(type.getRawName().replace('.', '/'));
118 }
119
120 @Override
121 public Dumper beginBlockComment(boolean inline) {
122 print("/*").newln();
123 return this;
124 }
125
126 @Override
127 public Dumper endBlockComment() {
128 print(" */").newln();
129 return this;
130 }
131
132 @Override
133 public Dumper label(String s, boolean inline) {
134 processPendingCR();
135 append(s);
136 append(":");
137 return this;
138 }
139
140 @Override
141 public Dumper comment(String s) {
142 append("// ");
143 append(s);
144 append("\n");
145 return this;
146 }
147
148 @Override
149 public void enqueuePendingCarriageReturn() {
150 pendingCR = true;
151 }
152
153 @Override
154 public Dumper removePendingCarriageReturn() {
155 pendingCR = false;
156 return this;
157 }
158
159 private void processPendingCR() {
160 if (pendingCR) {
161 append("\n");
162 atStart = true;
163 pendingCR = false;
164 }
165 }
166
167 @Override
168 public Dumper identifier(String s, Object ref, boolean defines) {
169 return print(s);
170 }
171
172 @Override
173 public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) {
174 doIndent();
175 Token token = new Token(position, position + name.length(), name);
176 Entry<?> entry = getMethodEntry(method);
177
178 if (entry != null) {
179 if (defines) {
180 index.addDeclaration(token, entry);
181 } else {
182 index.addReference(token, entry, null);
183 }
184 }
185
186 return identifier(name, null, defines);
187 }
188
189 @Override
190 public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) {
191 doIndent();
192 Token token = new Token(position, position + name.length(), name);
193 Entry<?> entry = getParameterEntry(method, index, name);
194
195 if (entry != null) {
196 if (defines) {
197 this.index.addDeclaration(token, entry);
198 } else {
199 this.index.addReference(token, entry, null);
200 }
201 }
202
203 return identifier(name, null, defines);
204 }
205
206 @Override
207 public Dumper variableName(String name, NamedVariable variable, boolean defines) {
208 return identifier(name, null, defines);
209 }
210
211 @Override
212 public Dumper packageName(JavaRefTypeInstance t) {
213 String s = t.getPackageName();
214
215 if (!s.isEmpty()) {
216 keyword("package ").print(s).endCodeln().newln();
217 }
218
219 return this;
220 }
221
222 @Override
223 public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) {
224 doIndent();
225 Token token = new Token(position, position + name.length(), name);
226 Entry<?> entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance());
227
228 if (entry != null) {
229 if (defines) {
230 index.addDeclaration(token, entry);
231 } else {
232 index.addReference(token, entry, null);
233 }
234 }
235
236 identifier(name, null, defines);
237 return this;
238 }
239
240 @Override
241 public Dumper print(String s) {
242 processPendingCR();
243 doIndent();
244 append(s);
245 atStart = s.endsWith("\n");
246 outputCount++;
247 return this;
248 }
249
250 @Override
251 public Dumper print(char c) {
252 return print(String.valueOf(c));
253 }
254
255 @Override
256 public Dumper newln() {
257 append("\n");
258 atStart = true;
259 outputCount++;
260 return this;
261 }
262
263 @Override
264 public Dumper endCodeln() {
265 append(";\n");
266 atStart = true;
267 outputCount++;
268 return this;
269 }
270
271 @Override
272 public Dumper keyword(String s) {
273 print(s);
274 return this;
275 }
276
277 @Override
278 public Dumper operator(String s) {
279 print(s);
280 return this;
281 }
282
283 @Override
284 public Dumper separator(String s) {
285 print(s);
286 return this;
287 }
288
289 @Override
290 public Dumper literal(String s, Object o) {
291 print(s);
292 return this;
293 }
294
295 private void doIndent() {
296 if (!atStart) return;
297 String indents = " ";
298
299 for (int x = 0; x < indent; ++x) {
300 append(indents);
301 }
302
303 atStart = false;
304 }
305
306 @Override
307 public void indent(int diff) {
308 indent += diff;
309 }
310
311 @Override
312 public Dumper dump(Dumpable d) {
313 if (d == null) {
314 keyword("null");
315 return this;
316 }
317
318 d.dump(this);
319 return this;
320 }
321
322 @Override
323 public TypeUsageInformation getTypeUsageInformation() {
324 return typeUsageInformation;
325 }
326
327 @Override
328 public ObfuscationMapping getObfuscationMapping() {
329 return NullMapping.INSTANCE;
330 }
331
332 @Override
333 public String toString() {
334 return sb.toString();
335 }
336
337 @Override
338 public void addSummaryError(Method method, String s) {}
339
340 @Override
341 public void close() {
342 }
343
344 @Override
345 public boolean canEmitClass(JavaTypeInstance type) {
346 return emitted.add(type);
347 }
348
349 @Override
350 public int getOutputCount() {
351 return outputCount;
352 }
353
354 @Override
355 public Dumper dump(JavaTypeInstance type) {
356 return dump(type, TypeContext.None, false);
357 }
358
359 @Override
360 public Dumper dump(JavaTypeInstance type, boolean defines) {
361 return dump(type, TypeContext.None, false);
362 }
363
364 @Override
365 public Dumper dump(JavaTypeInstance type, TypeContext context) {
366 return dump(type, context, false);
367 }
368
369 private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) {
370 doIndent();
371 if (type instanceof JavaRefTypeInstance) {
372 int start = position;
373 type.dumpInto(this, typeUsageInformation, TypeContext.None);
374 int end = position;
375 Token token = new Token(start, end, sb.toString().substring(start, end));
376
377 if (defines) {
378 index.addDeclaration(token, getClassEntry(type));
379 } else {
380 index.addReference(token, getClassEntry(type), null);
381 }
382
383 return this;
384 }
385
386 type.dumpInto(this, typeUsageInformation, context);
387 return this;
388 }
389
390 @Override
391 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
392 return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation);
393 }
394
395 public SourceIndex getIndex() {
396 index.setSource(getString());
397 return index;
398 }
399
400 public String getString() {
401 return sb.toString();
402 }
403
404 public static class WithTypeUsageInformationDumper extends DelegatingDumper {
405 private final TypeUsageInformation typeUsageInformation;
406
407 WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) {
408 super(delegate);
409 this.typeUsageInformation = typeUsageInformation;
410 }
411
412 @Override
413 public TypeUsageInformation getTypeUsageInformation() {
414 return typeUsageInformation;
415 }
416
417 @Override
418 public Dumper dump(JavaTypeInstance javaTypeInstance) {
419 return dump(javaTypeInstance, TypeContext.None);
420 }
421
422 @Override
423 public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) {
424 javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext);
425 return this;
426 }
427
428 @Override
429 public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) {
430 return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation);
431 }
432 }
433}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java
deleted file mode 100644
index 2fae61a..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java
+++ /dev/null
@@ -1,49 +0,0 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.FieldDefinition;
4import com.strobel.assembler.metadata.MethodDefinition;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import cuchaz.enigma.translation.representation.AccessFlags;
8import cuchaz.enigma.translation.representation.MethodDescriptor;
9import cuchaz.enigma.translation.representation.Signature;
10import cuchaz.enigma.translation.representation.TypeDescriptor;
11import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.FieldDefEntry;
14import cuchaz.enigma.translation.representation.entry.MethodDefEntry;
15
16public class EntryParser {
17 public static FieldDefEntry parse(FieldDefinition definition) {
18 ClassEntry owner = parse(definition.getDeclaringType());
19 TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature());
20 Signature signature = Signature.createTypedSignature(definition.getSignature());
21 AccessFlags access = new AccessFlags(definition.getModifiers());
22 return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null);
23 }
24
25 public static ClassDefEntry parse(TypeDefinition def) {
26 String name = def.getInternalName();
27 Signature signature = Signature.createSignature(def.getSignature());
28 AccessFlags access = new AccessFlags(def.getModifiers());
29 ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null;
30 ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new);
31 return new ClassDefEntry(name, signature, access, superClass, interfaces);
32 }
33
34 public static ClassEntry parse(TypeReference typeReference) {
35 return new ClassEntry(typeReference.getInternalName());
36 }
37
38 public static MethodDefEntry parse(MethodDefinition definition) {
39 ClassEntry classEntry = parse(definition.getDeclaringType());
40 MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature());
41 Signature signature = Signature.createSignature(definition.getSignature());
42 AccessFlags access = new AccessFlags(definition.getModifiers());
43 return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null);
44 }
45
46 public static TypeDescriptor parseTypeDescriptor(TypeReference type) {
47 return new TypeDescriptor(type.getErasedSignature());
48 }
49}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java
deleted file mode 100644
index 37bc0c8..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java
+++ /dev/null
@@ -1,81 +0,0 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7import com.strobel.decompiler.DecompilerContext;
8import com.strobel.decompiler.DecompilerSettings;
9import com.strobel.decompiler.languages.java.BraceStyle;
10import com.strobel.decompiler.languages.java.JavaFormattingOptions;
11import com.strobel.decompiler.languages.java.ast.AstBuilder;
12import com.strobel.decompiler.languages.java.ast.CompilationUnit;
13import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor;
14import cuchaz.enigma.ClassProvider;
15import cuchaz.enigma.api.EnigmaPluginContext;
16import cuchaz.enigma.source.Source;
17import cuchaz.enigma.source.Decompiler;
18import cuchaz.enigma.source.SourceSettings;
19import cuchaz.enigma.source.procyon.transformers.*;
20import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader;
21import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem;
22import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader;
23import cuchaz.enigma.utils.Utils;
24
25public class ProcyonDecompiler implements Decompiler {
26 private final SourceSettings settings;
27 private final DecompilerSettings decompilerSettings;
28 private final MetadataSystem metadataSystem;
29
30 public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) {
31 ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider));
32
33 metadataSystem = new NoRetryMetadataSystem(typeLoader);
34 metadataSystem.setEagerMethodLoadingEnabled(true);
35
36 decompilerSettings = DecompilerSettings.javaDefaults();
37 decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true));
38 decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true));
39 decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true));
40 decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false));
41 decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false));
42 decompilerSettings.setTypeLoader(typeLoader);
43
44 JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions();
45 formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine;
46 formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine;
47 formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine;
48
49 this.settings = settings;
50 }
51
52 @Override
53 public Source getSource(String className) {
54 TypeReference type = metadataSystem.lookupType(className);
55 if (type == null) {
56 throw new Error(String.format("Unable to find desc: %s", className));
57 }
58
59 TypeDefinition resolvedType = type.resolve();
60
61 DecompilerContext context = new DecompilerContext();
62 context.setCurrentType(resolvedType);
63 context.setSettings(decompilerSettings);
64
65 AstBuilder builder = new AstBuilder(context);
66 builder.addType(resolvedType);
67 builder.runTransformations(null);
68 CompilationUnit source = builder.getCompilationUnit();
69
70 new ObfuscatedEnumSwitchRewriterTransform(context).run(source);
71 new VarargsFixer(context).run(source);
72 new RemoveObjectCasts(context).run(source);
73 new Java8Generics().run(source);
74 new InvalidIdentifierFix().run(source);
75 if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source);
76 if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source);
77 source.acceptVisitor(new InsertParenthesesVisitor(), null);
78
79 return new ProcyonSource(source, decompilerSettings);
80 }
81}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java
deleted file mode 100644
index 53c8c70..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java
+++ /dev/null
@@ -1,49 +0,0 @@
1package cuchaz.enigma.source.procyon;
2
3import com.strobel.decompiler.DecompilerSettings;
4import com.strobel.decompiler.PlainTextOutput;
5import com.strobel.decompiler.languages.java.JavaOutputVisitor;
6import com.strobel.decompiler.languages.java.ast.CompilationUnit;
7import cuchaz.enigma.source.Source;
8import cuchaz.enigma.source.SourceIndex;
9import cuchaz.enigma.source.procyon.index.SourceIndexVisitor;
10import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform;
11import cuchaz.enigma.translation.mapping.EntryRemapper;
12
13import java.io.StringWriter;
14
15public class ProcyonSource implements Source {
16 private final DecompilerSettings settings;
17 private final CompilationUnit tree;
18 private String string;
19
20 public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) {
21 this.settings = settings;
22 this.tree = tree;
23 }
24
25 @Override
26 public SourceIndex index() {
27 SourceIndex index = new SourceIndex(asString());
28 tree.acceptVisitor(new SourceIndexVisitor(), index);
29 return index;
30 }
31
32 @Override
33 public String asString() {
34 if (string == null) {
35 StringWriter writer = new StringWriter();
36 tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null);
37 string = writer.toString();
38 }
39
40 return string;
41 }
42
43 @Override
44 public Source addJavadocs(EntryRemapper remapper) {
45 CompilationUnit remappedTree = (CompilationUnit) tree.clone();
46 new AddJavadocsAstTransform(remapper).run(remappedTree);
47 return new ProcyonSource(remappedTree, settings);
48 }
49}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java
deleted file mode 100644
index f6eeb15..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java
+++ /dev/null
@@ -1,95 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.FieldDefinition;
15import com.strobel.assembler.metadata.MethodDefinition;
16import com.strobel.assembler.metadata.TypeDefinition;
17import com.strobel.assembler.metadata.TypeReference;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.entry.*;
23
24public class SourceIndexClassVisitor extends SourceIndexVisitor {
25 private ClassDefEntry classEntry;
26
27 public SourceIndexClassVisitor(ClassDefEntry classEntry) {
28 this.classEntry = classEntry;
29 }
30
31 @Override
32 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
33 // is this this class, or a subtype?
34 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
35 ClassDefEntry classEntry = EntryParser.parse(def);
36 if (!classEntry.equals(this.classEntry)) {
37 // it's a subtype, recurse
38 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
39 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
40 }
41
42 return visitChildren(node, index);
43 }
44
45 @Override
46 public Void visitSimpleType(SimpleType node, SourceIndex index) {
47 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
48 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
49 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
50 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry);
51 }
52
53 return visitChildren(node, index);
54 }
55
56 @Override
57 public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) {
58 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
59 MethodDefEntry methodEntry = EntryParser.parse(def);
60 AstNode tokenNode = node.getNameToken();
61 if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) {
62 // for static initializers, check elsewhere for the token node
63 tokenNode = node.getModifiers().firstOrNullObject();
64 }
65 index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry);
66 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
67 }
68
69 @Override
70 public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) {
71 MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION);
72 MethodDefEntry methodEntry = EntryParser.parse(def);
73 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry);
74 return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index);
75 }
76
77 @Override
78 public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) {
79 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
80 FieldDefEntry fieldEntry = EntryParser.parse(def);
81 assert (node.getVariables().size() == 1);
82 VariableInitializer variable = node.getVariables().firstOrNullObject();
83 index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry);
84 return visitChildren(node, index);
85 }
86
87 @Override
88 public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) {
89 // treat enum declarations as field declarations
90 FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION);
91 FieldDefEntry fieldEntry = EntryParser.parse(def);
92 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry);
93 return visitChildren(node, index);
94 }
95}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java
deleted file mode 100644
index 0e8bc51..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java
+++ /dev/null
@@ -1,218 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16import com.strobel.assembler.metadata.*;
17import com.strobel.decompiler.ast.Variable;
18import com.strobel.decompiler.languages.TextLocation;
19import com.strobel.decompiler.languages.java.ast.*;
20import cuchaz.enigma.source.SourceIndex;
21import cuchaz.enigma.source.procyon.EntryParser;
22import cuchaz.enigma.translation.representation.MethodDescriptor;
23import cuchaz.enigma.translation.representation.TypeDescriptor;
24import cuchaz.enigma.translation.representation.entry.*;
25
26import java.lang.Error;
27import java.util.HashMap;
28import java.util.Map;
29
30public class SourceIndexMethodVisitor extends SourceIndexVisitor {
31 private final MethodDefEntry methodEntry;
32
33 private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create();
34 private Map<String, Entry<?>> identifierEntryCache = new HashMap<>();
35
36 public SourceIndexMethodVisitor(MethodDefEntry methodEntry) {
37 this.methodEntry = methodEntry;
38 }
39
40 @Override
41 public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) {
42 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
43
44 // get the behavior entry
45 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
46 MethodEntry methodEntry = null;
47 if (ref instanceof MethodReference) {
48 methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
49 }
50 if (methodEntry != null) {
51 // get the node for the token
52 AstNode tokenNode = null;
53 if (node.getTarget() instanceof MemberReferenceExpression) {
54 tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken();
55 } else if (node.getTarget() instanceof SuperReferenceExpression) {
56 tokenNode = node.getTarget();
57 } else if (node.getTarget() instanceof ThisReferenceExpression) {
58 tokenNode = node.getTarget();
59 }
60 if (tokenNode != null) {
61 index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry);
62 }
63 }
64
65 // Check for identifier
66 node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression)
67 .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index));
68 return visitChildren(node, index);
69 }
70
71 @Override
72 public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) {
73 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
74 if (ref instanceof FieldReference) {
75 // make sure this is actually a field
76 String erasedSignature = ref.getErasedSignature();
77 if (erasedSignature.indexOf('(') >= 0) {
78 throw new Error("Expected a field here! got " + ref);
79 }
80
81 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
82 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature));
83 index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry);
84 }
85
86 return visitChildren(node, index);
87 }
88
89 @Override
90 public Void visitSimpleType(SimpleType node, SourceIndex index) {
91 TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE);
92 if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) {
93 ClassEntry classEntry = new ClassEntry(ref.getInternalName());
94 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry);
95 }
96
97 return visitChildren(node, index);
98 }
99
100 @Override
101 public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) {
102 ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION);
103 int parameterIndex = def.getSlot();
104
105 if (parameterIndex >= 0) {
106 MethodDefEntry ownerMethod = methodEntry;
107 if (def.getMethod() instanceof MethodDefinition) {
108 ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod());
109 }
110
111 TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType());
112 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null);
113 Identifier identifier = node.getNameToken();
114 // cache the argument entry and the identifier
115 identifierEntryCache.put(identifier.getName(), localVariableEntry);
116 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
117 }
118
119 return visitChildren(node, index);
120 }
121
122 @Override
123 public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) {
124 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
125 if (ref != null) {
126 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
127 FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature()));
128 index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry);
129 } else
130 this.checkIdentifier(node, index);
131 return visitChildren(node, index);
132 }
133
134 private void checkIdentifier(IdentifierExpression node, SourceIndex index) {
135 if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token!
136 index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier()));
137 else
138 unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it!
139 }
140
141 private void addDeclarationToUnmatched(String key, SourceIndex index) {
142 Entry<?> entry = identifierEntryCache.get(key);
143
144 // This cannot happened in theory
145 if (entry == null)
146 return;
147 for (Identifier identifier : unmatchedIdentifier.get(key))
148 index.addDeclaration(TokenFactory.createToken(index, identifier), entry);
149 unmatchedIdentifier.removeAll(key);
150 }
151
152 @Override
153 public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) {
154 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
155 if (ref != null && node.getType() instanceof SimpleType) {
156 SimpleType simpleTypeNode = (SimpleType) node.getType();
157 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
158 MethodEntry constructorEntry = new MethodEntry(classEntry, "<init>", new MethodDescriptor(ref.getErasedSignature()));
159 index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry);
160 }
161
162 return visitChildren(node, index);
163 }
164
165 @Override
166 public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) {
167 AstNodeCollection<VariableInitializer> variables = node.getVariables();
168
169 // Single assignation
170 if (variables.size() == 1) {
171 VariableInitializer initializer = variables.firstOrNullObject();
172 if (initializer != null && node.getType() instanceof SimpleType) {
173 Identifier identifier = initializer.getNameToken();
174 Variable variable = initializer.getUserData(Keys.VARIABLE);
175 if (variable != null) {
176 VariableDefinition originalVariable = variable.getOriginalVariable();
177 if (originalVariable != null) {
178 int variableIndex = originalVariable.getSlot();
179 if (variableIndex >= 0) {
180 MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod());
181 TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType());
182 LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null);
183 identifierEntryCache.put(identifier.getName(), localVariableEntry);
184 addDeclarationToUnmatched(identifier.getName(), index);
185 index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry);
186 }
187 }
188 }
189 }
190 }
191 return visitChildren(node, index);
192 }
193
194 @Override
195 public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) {
196 MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE);
197
198 if (ref instanceof MethodReference) {
199 // get the behavior entry
200 ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName());
201 MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature()));
202
203 // get the node for the token
204 AstNode methodNameToken = node.getMethodNameToken();
205 AstNode targetToken = node.getTarget();
206
207 if (methodNameToken != null) {
208 index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry);
209 }
210
211 if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) {
212 index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry);
213 }
214 }
215
216 return visitChildren(node, index);
217 }
218}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java
deleted file mode 100644
index dad505f..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java
+++ /dev/null
@@ -1,40 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.index;
13
14import com.strobel.assembler.metadata.TypeDefinition;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
17import com.strobel.decompiler.languages.java.ast.Keys;
18import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
19import cuchaz.enigma.source.SourceIndex;
20import cuchaz.enigma.source.procyon.EntryParser;
21import cuchaz.enigma.translation.representation.entry.ClassDefEntry;
22
23public class SourceIndexVisitor extends DepthFirstAstVisitor<SourceIndex, Void> {
24 @Override
25 public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) {
26 TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION);
27 ClassDefEntry classEntry = EntryParser.parse(def);
28 index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry);
29
30 return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index);
31 }
32
33 @Override
34 protected Void visitChildren(AstNode node, SourceIndex index) {
35 for (final AstNode child : node.getChildren()) {
36 child.acceptVisitor(this, index);
37 }
38 return null;
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java
deleted file mode 100644
index 62e7c10..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java
+++ /dev/null
@@ -1,46 +0,0 @@
1package cuchaz.enigma.source.procyon.index;
2
3import com.strobel.decompiler.languages.Region;
4import com.strobel.decompiler.languages.java.ast.AstNode;
5import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration;
6import com.strobel.decompiler.languages.java.ast.Identifier;
7import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
8import cuchaz.enigma.analysis.Token;
9import cuchaz.enigma.source.SourceIndex;
10
11import java.util.regex.Pattern;
12
13public class TokenFactory {
14 private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$");
15
16 public static Token createToken(SourceIndex index, AstNode node) {
17 String name = node instanceof Identifier ? ((Identifier) node).getName() : "";
18 Region region = node.getRegion();
19
20 if (region.getBeginLine() == 0) {
21 System.err.println("Got bad region from Procyon for node " + node);
22 return null;
23 }
24
25 int start = index.getPosition(region.getBeginLine(), region.getBeginColumn());
26 int end = index.getPosition(region.getEndLine(), region.getEndColumn());
27 String text = index.getSource().substring(start, end);
28 Token token = new Token(start, end, text);
29
30 boolean isAnonymousInner =
31 node instanceof Identifier &&
32 name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration &&
33 name.lastIndexOf('$') >= 0 &&
34 !ANONYMOUS_INNER.matcher(name).matches();
35
36 if (isAnonymousInner) {
37 TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null;
38 if (type != null) {
39 name = type.getName();
40 token.end = token.start + name.length();
41 }
42 }
43
44 return token;
45 }
46}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
deleted file mode 100644
index 70fc8c6..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java
+++ /dev/null
@@ -1,134 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.google.common.base.Function;
4import com.google.common.base.Strings;
5import com.strobel.assembler.metadata.ParameterDefinition;
6import com.strobel.decompiler.languages.java.ast.*;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8import cuchaz.enigma.source.procyon.EntryParser;
9import cuchaz.enigma.translation.mapping.EntryMapping;
10import cuchaz.enigma.translation.mapping.EntryRemapper;
11import cuchaz.enigma.translation.mapping.ResolutionStrategy;
12import cuchaz.enigma.translation.representation.entry.*;
13
14import java.util.ArrayList;
15import java.util.Collections;
16import java.util.List;
17import java.util.Objects;
18import java.util.stream.Stream;
19
20public final class AddJavadocsAstTransform implements IAstTransform {
21
22 private final EntryRemapper remapper;
23
24 public AddJavadocsAstTransform(EntryRemapper remapper) {
25 this.remapper = remapper;
26 }
27
28 @Override
29 public void run(AstNode compilationUnit) {
30 compilationUnit.acceptVisitor(new Visitor(remapper), null);
31 }
32
33 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
34
35 private final EntryRemapper remapper;
36
37 Visitor(EntryRemapper remapper) {
38 this.remapper = remapper;
39 }
40
41 private <T extends AstNode> void addDoc(T node, Function<T, Entry<?>> retriever) {
42 final Comment[] comments = getComments(node, retriever);
43 if (comments != null) {
44 node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments);
45 }
46 }
47
48 private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) {
49 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
50 final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc());
51 return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st,
52 CommentType.Documentation)).toArray(Comment[]::new);
53 }
54
55 private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) {
56 final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node));
57 final Comment[] ret = getComments(node, retriever);
58 if (ret != null) {
59 final String paramPrefix = "@param " + mapping.getTargetName() + " ";
60 final String indent = Strings.repeat(" ", paramPrefix.length());
61 ret[0].setContent(paramPrefix + ret[0].getContent());
62 for (int i = 1; i < ret.length; i++) {
63 ret[i].setContent(indent + ret[i].getContent());
64 }
65 }
66 return ret;
67 }
68
69 private void visitMethod(AstNode node) {
70 final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION));
71 final Comment[] baseComments = getComments(node, $ -> methodDefEntry);
72 List<Comment> comments = new ArrayList<>();
73 if (baseComments != null)
74 Collections.addAll(comments, baseComments);
75
76 for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) {
77 ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION);
78 final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(),
79 true,
80 EntryParser.parseTypeDescriptor(def.getParameterType()), null));
81 if (paramComments != null)
82 Collections.addAll(comments, paramComments);
83 }
84
85 if (!comments.isEmpty()) {
86 if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) {
87 comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation));
88 }
89 final AstNode oldFirst = node.getFirstChild();
90 for (Comment comment : comments) {
91 node.insertChildBefore(oldFirst, comment, Roles.COMMENT);
92 }
93 }
94 }
95
96 @Override
97 protected Void visitChildren(AstNode node, Void data) {
98 for (final AstNode child : node.getChildren()) {
99 child.acceptVisitor(this, data);
100 }
101 return null;
102 }
103
104 @Override
105 public Void visitMethodDeclaration(MethodDeclaration node, Void data) {
106 visitMethod(node);
107 return super.visitMethodDeclaration(node, data);
108 }
109
110 @Override
111 public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) {
112 visitMethod(node);
113 return super.visitConstructorDeclaration(node, data);
114 }
115
116 @Override
117 public Void visitFieldDeclaration(FieldDeclaration node, Void data) {
118 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
119 return super.visitFieldDeclaration(node, data);
120 }
121
122 @Override
123 public Void visitTypeDeclaration(TypeDeclaration node, Void data) {
124 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION)));
125 return super.visitTypeDeclaration(node, data);
126 }
127
128 @Override
129 public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) {
130 addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION)));
131 return super.visitEnumValueDeclaration(node, data);
132 }
133 }
134}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java
deleted file mode 100644
index 39e599d..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java
+++ /dev/null
@@ -1,33 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.ImportDeclaration;
6import com.strobel.decompiler.languages.java.ast.PackageDeclaration;
7import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
8
9public final class DropImportAstTransform implements IAstTransform {
10 public static final DropImportAstTransform INSTANCE = new DropImportAstTransform();
11
12 private DropImportAstTransform() {
13 }
14
15 @Override
16 public void run(AstNode compilationUnit) {
17 compilationUnit.acceptVisitor(new Visitor(), null);
18 }
19
20 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
21 @Override
22 public Void visitPackageDeclaration(PackageDeclaration node, Void data) {
23 node.remove();
24 return null;
25 }
26
27 @Override
28 public Void visitImportDeclaration(ImportDeclaration node, Void data) {
29 node.remove();
30 return null;
31 }
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java
deleted file mode 100644
index b8c087b..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java
+++ /dev/null
@@ -1,37 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.*;
4import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
5
6import javax.lang.model.element.Modifier;
7
8public final class DropVarModifiersAstTransform implements IAstTransform {
9 public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform();
10
11 private DropVarModifiersAstTransform() {
12 }
13
14 @Override
15 public void run(AstNode compilationUnit) {
16 compilationUnit.acceptVisitor(new Visitor(), null);
17 }
18
19 static class Visitor extends DepthFirstAstVisitor<Void, Void> {
20 @Override
21 public Void visitParameterDeclaration(ParameterDeclaration node, Void data) {
22 for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) {
23 if (modifierToken.getModifier() == Modifier.FINAL) {
24 modifierToken.remove();
25 }
26 }
27
28 return null;
29 }
30
31 @Override
32 public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) {
33 node.removeModifier(Modifier.FINAL);
34 return null;
35 }
36 }
37}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java
deleted file mode 100644
index 34d95fa..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java
+++ /dev/null
@@ -1,29 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.decompiler.languages.java.ast.AstNode;
4import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
5import com.strobel.decompiler.languages.java.ast.Identifier;
6import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
7
8/**
9 * Created by Thiakil on 13/07/2018.
10 */
11public class InvalidIdentifierFix implements IAstTransform {
12 @Override
13 public void run(AstNode compilationUnit) {
14 compilationUnit.acceptVisitor(new Visitor(), null);
15 }
16
17 class Visitor extends DepthFirstAstVisitor<Void,Void>{
18 @Override
19 public Void visitIdentifier(Identifier node, Void data) {
20 super.visitIdentifier(node, data);
21 if (node.getName().equals("do") || node.getName().equals("if")){
22 Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation());
23 newIdentifier.copyUserDataFrom(node);
24 node.replaceWith(newIdentifier);
25 }
26 return null;
27 }
28 }
29}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java
deleted file mode 100644
index 8accfc7..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java
+++ /dev/null
@@ -1,107 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.assembler.metadata.CommonTypeReferences;
5import com.strobel.assembler.metadata.Flags;
6import com.strobel.assembler.metadata.IGenericInstance;
7import com.strobel.assembler.metadata.IMemberDefinition;
8import com.strobel.assembler.metadata.JvmType;
9import com.strobel.assembler.metadata.MemberReference;
10import com.strobel.assembler.metadata.MethodDefinition;
11import com.strobel.assembler.metadata.TypeDefinition;
12import com.strobel.assembler.metadata.TypeReference;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.AstNode;
15import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
16import com.strobel.decompiler.languages.java.ast.AstType;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ComposedType;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.Identifier;
22import com.strobel.decompiler.languages.java.ast.InvocationExpression;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.Roles;
27import com.strobel.decompiler.languages.java.ast.SimpleType;
28import com.strobel.decompiler.languages.java.ast.WildcardType;
29import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
30
31/**
32 * Created by Thiakil on 12/07/2018.
33 */
34public class Java8Generics implements IAstTransform {
35
36 @Override
37 public void run(AstNode compilationUnit) {
38 compilationUnit.acceptVisitor(new Visitor(), null);
39 }
40
41 static class Visitor extends DepthFirstAstVisitor<Void,Void>{
42
43 @Override
44 public Void visitInvocationExpression(InvocationExpression node, Void data) {
45 super.visitInvocationExpression(node, data);
46 if (node.getTarget() instanceof MemberReferenceExpression){
47 MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget();
48 if (referenceExpression.getTypeArguments().stream().map(t->{
49 TypeReference tr = t.toTypeReference();
50 if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below
51 TypeReference resolved = tr.resolve();
52 if (resolved != null)
53 return resolved;
54 }
55 return tr;
56 }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) {
57 //these are invalid for invocations, let the compiler work it out
58 referenceExpression.getTypeArguments().clear();
59 } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){
60 //all are <Object>, thereby redundant and/or bad
61 referenceExpression.getTypeArguments().clear();
62 }
63 }
64 return null;
65 }
66
67 @Override
68 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
69 super.visitObjectCreationExpression(node, data);
70 AstType type = node.getType();
71 if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){
72 SimpleType simpleType = (SimpleType) type;
73 AstNodeCollection<AstType> typeArguments = simpleType.getTypeArguments();
74 if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){
75 //all are <Object>, thereby redundant and/or bad
76 typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create(""));
77 }
78 }
79 return null;
80 }
81
82 @Override
83 public Void visitCastExpression(CastExpression node, Void data) {
84 boolean doReplace = false;
85 TypeReference typeReference = node.getType().toTypeReference();
86 if (typeReference.isArray() && typeReference.getElementType().isGenericType()){
87 doReplace = true;
88 } else if (typeReference.isGenericType()) {
89 Expression target = node.getExpression();
90 if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){
91 doReplace = true;
92 } else if (target instanceof InvocationExpression) {
93 InvocationExpression invocationExpression = (InvocationExpression)target;
94 if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) {
95 ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear();
96 doReplace = true;
97 }
98 }
99 }
100 super.visitCastExpression(node, data);
101 if (doReplace){
102 node.replaceWith(node.getExpression());
103 }
104 return null;
105 }
106 }
107}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java
deleted file mode 100644
index 32bb72f..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java
+++ /dev/null
@@ -1,414 +0,0 @@
1/*
2 * Originally:
3 * EnumSwitchRewriterTransform.java
4 *
5 * Copyright (c) 2013 Mike Strobel
6 *
7 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
8 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
9 *
10 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
11 * A copy of the license can be found in the License.html file at the root of this distribution.
12 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
13 * Apache License, Version 2.0.
14 *
15 * You must not remove this notice, or any other, from this software.
16 */
17
18package cuchaz.enigma.source.procyon.transformers;
19
20import com.strobel.assembler.metadata.BuiltinTypes;
21import com.strobel.assembler.metadata.FieldDefinition;
22import com.strobel.assembler.metadata.MethodDefinition;
23import com.strobel.assembler.metadata.TypeDefinition;
24import com.strobel.assembler.metadata.TypeReference;
25import com.strobel.core.SafeCloseable;
26import com.strobel.core.VerifyArgument;
27import com.strobel.decompiler.DecompilerContext;
28import com.strobel.decompiler.languages.java.ast.AssignmentExpression;
29import com.strobel.decompiler.languages.java.ast.AstBuilder;
30import com.strobel.decompiler.languages.java.ast.AstNode;
31import com.strobel.decompiler.languages.java.ast.CaseLabel;
32import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
33import com.strobel.decompiler.languages.java.ast.Expression;
34import com.strobel.decompiler.languages.java.ast.IdentifierExpression;
35import com.strobel.decompiler.languages.java.ast.IndexerExpression;
36import com.strobel.decompiler.languages.java.ast.InvocationExpression;
37import com.strobel.decompiler.languages.java.ast.Keys;
38import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
39import com.strobel.decompiler.languages.java.ast.PrimitiveExpression;
40import com.strobel.decompiler.languages.java.ast.SwitchSection;
41import com.strobel.decompiler.languages.java.ast.SwitchStatement;
42import com.strobel.decompiler.languages.java.ast.TypeDeclaration;
43import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression;
44import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
45
46import java.util.ArrayList;
47import java.util.IdentityHashMap;
48import java.util.LinkedHashMap;
49import java.util.List;
50import java.util.Map;
51
52/**
53 * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to:
54 * - Not rely on a field containing "$SwitchMap$" (Proguard strips it)
55 * - Ignore classes *with* SwitchMap$ names (so the original can handle it)
56 * - Ignores inner synthetics that are not package private
57 */
58@SuppressWarnings("Duplicates")
59public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform {
60 private final DecompilerContext _context;
61
62 public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) {
63 _context = VerifyArgument.notNull(context, "context");
64 }
65
66 @Override
67 public void run(final AstNode compilationUnit) {
68 compilationUnit.acceptVisitor(new Visitor(_context), null);
69 }
70
71 private final static class Visitor extends ContextTrackingVisitor<Void> {
72 private final static class SwitchMapInfo {
73 final String enclosingType;
74 final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>();
75 final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>();
76
77 TypeDeclaration enclosingTypeDeclaration;
78
79 SwitchMapInfo(final String enclosingType) {
80 this.enclosingType = enclosingType;
81 }
82 }
83
84 private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>();
85 private boolean _isSwitchMapWrapper;
86
87 protected Visitor(final DecompilerContext context) {
88 super(context);
89 }
90
91 @Override
92 public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) {
93 final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper;
94 final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION);
95 final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition);
96
97 if (isSwitchMapWrapper) {
98 final String internalName = typeDefinition.getInternalName();
99
100 SwitchMapInfo info = _switchMaps.get(internalName);
101
102 if (info == null) {
103 _switchMaps.put(internalName, info = new SwitchMapInfo(internalName));
104 }
105
106 info.enclosingTypeDeclaration = typeDeclaration;
107 }
108
109 _isSwitchMapWrapper = isSwitchMapWrapper;
110
111 try {
112 super.visitTypeDeclaration(typeDeclaration, p);
113 }
114 finally {
115 _isSwitchMapWrapper = oldIsSwitchMapWrapper;
116 }
117
118 rewrite();
119
120 return null;
121 }
122
123 @Override
124 public Void visitSwitchStatement(final SwitchStatement node, final Void data) {
125 final Expression test = node.getExpression();
126
127 if (test instanceof IndexerExpression) {
128 final IndexerExpression indexer = (IndexerExpression) test;
129 final Expression array = indexer.getTarget();
130 final Expression argument = indexer.getArgument();
131
132 if (!(array instanceof MemberReferenceExpression)) {
133 return super.visitSwitchStatement(node, data);
134 }
135
136 final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array;
137 final Expression arrayOwner = arrayAccess.getTarget();
138 final String mapName = arrayAccess.getMemberName();
139
140 if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) {
141 return super.visitSwitchStatement(node, data);
142 }
143
144 final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner;
145 final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE);
146
147 if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) {
148 return super.visitSwitchStatement(node, data);
149 }
150
151 final InvocationExpression invocation = (InvocationExpression) argument;
152 final Expression invocationTarget = invocation.getTarget();
153
154 if (!(invocationTarget instanceof MemberReferenceExpression)) {
155 return super.visitSwitchStatement(node, data);
156 }
157
158 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
159
160 if (!"ordinal".equals(memberReference.getMemberName())) {
161 return super.visitSwitchStatement(node, data);
162 }
163
164 final String enclosingTypeName = enclosingType.getInternalName();
165
166 SwitchMapInfo info = _switchMaps.get(enclosingTypeName);
167
168 if (info == null) {
169 _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName));
170
171 final TypeDefinition resolvedType = enclosingType.resolve();
172
173 if (resolvedType != null) {
174 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
175
176 if (astBuilder == null) {
177 astBuilder = new AstBuilder(context);
178 }
179
180 try (final SafeCloseable importSuppression = astBuilder.suppressImports()) {
181 final TypeDeclaration declaration = astBuilder.createType(resolvedType);
182
183 declaration.acceptVisitor(this, data);
184 }
185 }
186 }
187
188 List<SwitchStatement> switches = info.switches.get(mapName);
189
190 if (switches == null) {
191 info.switches.put(mapName, switches = new ArrayList<>());
192 }
193
194 switches.add(node);
195 }
196
197 return super.visitSwitchStatement(node, data);
198 }
199
200 @Override
201 public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) {
202 final TypeDefinition currentType = context.getCurrentType();
203 final MethodDefinition currentMethod = context.getCurrentMethod();
204
205 if (_isSwitchMapWrapper &&
206 currentType != null &&
207 currentMethod != null &&
208 currentMethod.isTypeInitializer()) {
209
210 final Expression left = node.getLeft();
211 final Expression right = node.getRight();
212
213 if (left instanceof IndexerExpression &&
214 right instanceof PrimitiveExpression) {
215
216 String mapName = null;
217
218 final Expression array = ((IndexerExpression) left).getTarget();
219 final Expression argument = ((IndexerExpression) left).getArgument();
220
221 if (array instanceof MemberReferenceExpression) {
222 mapName = ((MemberReferenceExpression) array).getMemberName();
223 }
224 else if (array instanceof IdentifierExpression) {
225 mapName = ((IdentifierExpression) array).getIdentifier();
226 }
227
228 if (mapName == null || mapName.startsWith("$SwitchMap$")) {
229 return super.visitAssignmentExpression(node, data);
230 }
231
232 if (!(argument instanceof InvocationExpression)) {
233 return super.visitAssignmentExpression(node, data);
234 }
235
236 final InvocationExpression invocation = (InvocationExpression) argument;
237 final Expression invocationTarget = invocation.getTarget();
238
239 if (!(invocationTarget instanceof MemberReferenceExpression)) {
240 return super.visitAssignmentExpression(node, data);
241 }
242
243 final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget;
244 final Expression memberTarget = memberReference.getTarget();
245
246 if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) {
247 return super.visitAssignmentExpression(node, data);
248 }
249
250 final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget;
251 final Expression outerMemberTarget = outerMemberReference.getTarget();
252
253 if (!(outerMemberTarget instanceof TypeReferenceExpression)) {
254 return super.visitAssignmentExpression(node, data);
255 }
256
257 final String enclosingType = currentType.getInternalName();
258
259 SwitchMapInfo info = _switchMaps.get(enclosingType);
260
261 if (info == null) {
262 _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType));
263
264 AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER);
265
266 if (astBuilder == null) {
267 astBuilder = new AstBuilder(context);
268 }
269
270 info.enclosingTypeDeclaration = astBuilder.createType(currentType);
271 }
272
273 final PrimitiveExpression value = (PrimitiveExpression) right;
274
275 assert value.getValue() instanceof Integer;
276
277 Map<Integer, Expression> mapping = info.mappings.get(mapName);
278
279 if (mapping == null) {
280 info.mappings.put(mapName, mapping = new LinkedHashMap<>());
281 }
282
283 final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName());
284
285 enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE));
286
287 mapping.put(((Number) value.getValue()).intValue(), enumValue);
288 }
289 }
290
291 return super.visitAssignmentExpression(node, data);
292 }
293
294 private void rewrite() {
295 if (_switchMaps.isEmpty()) {
296 return;
297 }
298
299 for (final SwitchMapInfo info : _switchMaps.values()) {
300 rewrite(info);
301 }
302
303 //
304 // Remove switch map type wrappers that are no longer referenced.
305 //
306
307 outer:
308 for (final SwitchMapInfo info : _switchMaps.values()) {
309 for (final String mapName : info.switches.keySet()) {
310 final List<SwitchStatement> switches = info.switches.get(mapName);
311
312 if (switches != null && !switches.isEmpty()) {
313 continue outer;
314 }
315 }
316
317 final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration;
318
319 if (enclosingTypeDeclaration != null) {
320 enclosingTypeDeclaration.remove();
321 }
322 }
323 }
324
325 private void rewrite(final SwitchMapInfo info) {
326 if (info.switches.isEmpty()) {
327 return;
328 }
329
330 for (final String mapName : info.switches.keySet()) {
331 final List<SwitchStatement> switches = info.switches.get(mapName);
332 final Map<Integer, Expression> mappings = info.mappings.get(mapName);
333
334 if (switches != null && mappings != null) {
335 for (int i = 0; i < switches.size(); i++) {
336 if (rewriteSwitch(switches.get(i), mappings)) {
337 switches.remove(i--);
338 }
339 }
340 }
341 }
342 }
343
344 private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) {
345 final Map<Expression, Expression> replacements = new IdentityHashMap<>();
346
347 for (final SwitchSection section : s.getSwitchSections()) {
348 for (final CaseLabel caseLabel : section.getCaseLabels()) {
349 final Expression expression = caseLabel.getExpression();
350
351 if (expression.isNull()) {
352 continue;
353 }
354
355 if (expression instanceof PrimitiveExpression) {
356 final Object value = ((PrimitiveExpression) expression).getValue();
357
358 if (value instanceof Integer) {
359 final Expression replacement = mappings.get(value);
360
361 if (replacement != null) {
362 replacements.put(expression, replacement);
363 continue;
364 }
365 }
366 }
367
368 //
369 // If we can't rewrite all cases, we abort.
370 //
371
372 return false;
373 }
374 }
375
376 final IndexerExpression indexer = (IndexerExpression) s.getExpression();
377 final InvocationExpression argument = (InvocationExpression) indexer.getArgument();
378 final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget();
379 final Expression newTest = memberReference.getTarget();
380
381 newTest.remove();
382 indexer.replaceWith(newTest);
383
384 for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) {
385 entry.getKey().replaceWith(entry.getValue().clone());
386 }
387
388 return true;
389 }
390
391 private static boolean isSwitchMapWrapper(final TypeReference type) {
392 if (type == null) {
393 return false;
394 }
395
396 final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type
397 : type.resolve();
398
399 if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) {
400 return false;
401 }
402
403 for (final FieldDefinition field : definition.getDeclaredFields()) {
404 if (!field.getName().startsWith("$SwitchMap$") &&
405 BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) {
406
407 return true;
408 }
409 }
410
411 return false;
412 }
413 }
414} \ No newline at end of file
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java
deleted file mode 100644
index cf0376f..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java
+++ /dev/null
@@ -1,39 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.BuiltinTypes;
4import com.strobel.decompiler.DecompilerContext;
5import com.strobel.decompiler.languages.java.ast.AstNode;
6import com.strobel.decompiler.languages.java.ast.CastExpression;
7import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
8import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
9
10/**
11 * Created by Thiakil on 11/07/2018.
12 */
13public class RemoveObjectCasts implements IAstTransform {
14 private final DecompilerContext _context;
15
16 public RemoveObjectCasts(DecompilerContext context) {
17 _context = context;
18 }
19
20 @Override
21 public void run(AstNode compilationUnit) {
22 compilationUnit.acceptVisitor(new Visitor(_context), null);
23 }
24
25 private final static class Visitor extends ContextTrackingVisitor<Void>{
26
27 protected Visitor(DecompilerContext context) {
28 super(context);
29 }
30
31 @Override
32 public Void visitCastExpression(CastExpression node, Void data) {
33 if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){
34 node.replaceWith(node.getExpression());
35 }
36 return super.visitCastExpression(node, data);
37 }
38 }
39}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java
deleted file mode 100644
index d3ddaab..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java
+++ /dev/null
@@ -1,197 +0,0 @@
1package cuchaz.enigma.source.procyon.transformers;
2
3import com.strobel.assembler.metadata.MemberReference;
4import com.strobel.assembler.metadata.MetadataFilters;
5import com.strobel.assembler.metadata.MetadataHelper;
6import com.strobel.assembler.metadata.MethodBinder;
7import com.strobel.assembler.metadata.MethodDefinition;
8import com.strobel.assembler.metadata.MethodReference;
9import com.strobel.assembler.metadata.TypeReference;
10import com.strobel.core.StringUtilities;
11import com.strobel.core.VerifyArgument;
12import com.strobel.decompiler.DecompilerContext;
13import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression;
14import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression;
15import com.strobel.decompiler.languages.java.ast.AstNode;
16import com.strobel.decompiler.languages.java.ast.AstNodeCollection;
17import com.strobel.decompiler.languages.java.ast.CastExpression;
18import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor;
19import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor;
20import com.strobel.decompiler.languages.java.ast.Expression;
21import com.strobel.decompiler.languages.java.ast.InvocationExpression;
22import com.strobel.decompiler.languages.java.ast.JavaResolver;
23import com.strobel.decompiler.languages.java.ast.Keys;
24import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression;
25import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression;
26import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform;
27import com.strobel.decompiler.semantics.ResolveResult;
28
29import java.util.ArrayList;
30import java.util.List;
31
32/**
33 * Created by Thiakil on 12/07/2018.
34 */
35public class VarargsFixer implements IAstTransform {
36 private final DecompilerContext _context;
37
38 public VarargsFixer(final DecompilerContext context) {
39 _context = VerifyArgument.notNull(context, "context");
40 }
41
42 @Override
43 public void run(AstNode compilationUnit) {
44 compilationUnit.acceptVisitor(new Visitor(_context), null);
45 }
46
47 class Visitor extends ContextTrackingVisitor<Void> {
48 private final JavaResolver _resolver;
49 protected Visitor(DecompilerContext context) {
50 super(context);
51 _resolver = new JavaResolver(context);
52 }
53
54 //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them
55 @Override
56 public Void visitInvocationExpression(InvocationExpression node, Void data) {
57 super.visitInvocationExpression(node, data);
58 MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE);
59 if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){
60 AstNodeCollection<Expression> arguments = node.getArguments();
61 Expression lastParam = arguments.lastOrNullObject();
62 if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){
63 ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam;
64 if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){
65 lastParam.remove();
66 } else {
67 for (Expression e : varargArray.getInitializer().getElements()){
68 arguments.insertBefore(varargArray, e.clone());
69 }
70 varargArray.remove();
71 }
72 }
73 }
74 return null;
75 }
76
77 //applies the vararg transform to object creation
78 @Override
79 public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) {
80 super.visitObjectCreationExpression(node, data);
81 final AstNodeCollection<Expression> arguments = node.getArguments();
82 final Expression lastArgument = arguments.lastOrNullObject();
83
84 Expression arrayArg = lastArgument;
85
86 if (arrayArg instanceof CastExpression)
87 arrayArg = ((CastExpression) arrayArg).getExpression();
88
89 if (arrayArg == null ||
90 arrayArg.isNull() ||
91 !(arrayArg instanceof ArrayCreationExpression &&
92 node.getTarget() instanceof MemberReferenceExpression)) {
93
94 return null;
95 }
96
97 final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg;
98 final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget();
99
100 if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) {
101 return null;
102 }
103
104 final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE);
105
106 if (method == null) {
107 return null;
108 }
109
110 final MethodDefinition resolved = method.resolve();
111
112 if (resolved == null || !resolved.isVarArgs()) {
113 return null;
114 }
115
116 final List<MethodReference> candidates;
117 final Expression invocationTarget = target.getTarget();
118
119 if (invocationTarget == null || invocationTarget.isNull()) {
120 candidates = MetadataHelper.findMethods(
121 context.getCurrentType(),
122 MetadataFilters.matchName(resolved.getName())
123 );
124 }
125 else {
126 final ResolveResult targetResult = _resolver.apply(invocationTarget);
127
128 if (targetResult == null || targetResult.getType() == null) {
129 return null;
130 }
131
132 candidates = MetadataHelper.findMethods(
133 targetResult.getType(),
134 MetadataFilters.matchName(resolved.getName())
135 );
136 }
137
138 final List<TypeReference> argTypes = new ArrayList<>();
139
140 for (final Expression argument : arguments) {
141 final ResolveResult argResult = _resolver.apply(argument);
142
143 if (argResult == null || argResult.getType() == null) {
144 return null;
145 }
146
147 argTypes.add(argResult.getType());
148 }
149
150 final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes);
151
152 if (c1.isFailure() || c1.isAmbiguous()) {
153 return null;
154 }
155
156 argTypes.remove(argTypes.size() - 1);
157
158 final ArrayInitializerExpression initializer = newArray.getInitializer();
159 final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty();
160
161 if (hasElements) {
162 for (final Expression argument : initializer.getElements()) {
163 final ResolveResult argResult = _resolver.apply(argument);
164
165 if (argResult == null || argResult.getType() == null) {
166 return null;
167 }
168
169 argTypes.add(argResult.getType());
170 }
171 }
172
173 final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes);
174
175 if (c2.isFailure() ||
176 c2.isAmbiguous() ||
177 !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) {
178
179 return null;
180 }
181
182 lastArgument.remove();
183
184 if (!hasElements) {
185 lastArgument.remove();
186 return null;
187 }
188
189 for (final Expression newArg : initializer.getElements()) {
190 newArg.remove();
191 arguments.add(newArg);
192 }
193
194 return null;
195 }
196 }
197}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java
deleted file mode 100644
index e702956..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java
+++ /dev/null
@@ -1,33 +0,0 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ClasspathTypeLoader;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7/**
8 * Caching version of {@link ClasspathTypeLoader}
9 */
10public class CachingClasspathTypeLoader extends CachingTypeLoader {
11 private static ITypeLoader extraClassPathLoader = null;
12
13 public static void setExtraClassPathLoader(ITypeLoader loader){
14 extraClassPathLoader = loader;
15 }
16
17 private final ITypeLoader classpathLoader = new ClasspathTypeLoader();
18
19 @Override
20 protected byte[] doLoad(String className) {
21 Buffer parentBuf = new Buffer();
22 if (classpathLoader.tryLoadType(className, parentBuf)) {
23 return parentBuf.array();
24 }
25 if (extraClassPathLoader != null){
26 parentBuf.reset();
27 if (extraClassPathLoader.tryLoadType(className, parentBuf)){
28 return parentBuf.array();
29 }
30 }
31 return EMPTY_ARRAY;//need to return *something* as null means no store
32 }
33}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java
deleted file mode 100644
index 5be5ddd..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java
+++ /dev/null
@@ -1,38 +0,0 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.google.common.collect.Maps;
4import com.strobel.assembler.metadata.Buffer;
5import com.strobel.assembler.metadata.ITypeLoader;
6
7import java.util.Map;
8
9/**
10 * Common cache functions
11 */
12public abstract class CachingTypeLoader implements ITypeLoader {
13 protected static final byte[] EMPTY_ARRAY = {};
14
15 private final Map<String, byte[]> cache = Maps.newHashMap();
16
17 protected abstract byte[] doLoad(String className);
18
19 @Override
20 public boolean tryLoadType(String className, Buffer out) {
21
22 // check the cache
23 byte[] data = this.cache.computeIfAbsent(className, this::doLoad);
24
25 if (data == EMPTY_ARRAY) {
26 return false;
27 }
28
29 out.reset(data.length);
30 System.arraycopy(data, 0, out.array(), out.position(), data.length);
31 out.position(0);
32 return true;
33 }
34
35 public void clearCache() {
36 this.cache.clear();
37 }
38}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java
deleted file mode 100644
index e703d3b..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java
+++ /dev/null
@@ -1,140 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.source.procyon.typeloader;
13
14import com.google.common.collect.Lists;
15import com.strobel.assembler.metadata.Buffer;
16import com.strobel.assembler.metadata.ITypeLoader;
17import cuchaz.enigma.ClassProvider;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import org.objectweb.asm.ClassVisitor;
20import org.objectweb.asm.ClassWriter;
21import org.objectweb.asm.Opcodes;
22import org.objectweb.asm.tree.AbstractInsnNode;
23import org.objectweb.asm.tree.ClassNode;
24import org.objectweb.asm.tree.MethodInsnNode;
25import org.objectweb.asm.tree.MethodNode;
26
27import java.util.Collection;
28import java.util.LinkedList;
29import java.util.List;
30import java.util.function.Function;
31
32public class CompiledSourceTypeLoader extends CachingTypeLoader {
33 //Store one instance as the classpath shouldn't change during load
34 private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader();
35
36 private final ClassProvider compiledSource;
37 private final LinkedList<Function<ClassVisitor, ClassVisitor>> visitors = new LinkedList<>();
38
39 public CompiledSourceTypeLoader(ClassProvider compiledSource) {
40 this.compiledSource = compiledSource;
41 }
42
43 public void addVisitor(Function<ClassVisitor, ClassVisitor> visitor) {
44 this.visitors.addFirst(visitor);
45 }
46
47 @Override
48 protected byte[] doLoad(String className) {
49 byte[] data = loadType(className);
50 if (data == null) {
51 return loadClasspath(className);
52 }
53
54 return data;
55 }
56
57 private byte[] loadClasspath(String name) {
58 Buffer parentBuf = new Buffer();
59 if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) {
60 return parentBuf.array();
61 }
62 return EMPTY_ARRAY;
63 }
64
65 private byte[] loadType(String className) {
66 ClassEntry entry = new ClassEntry(className);
67
68 // find the class in the jar
69 ClassNode node = findClassNode(entry);
70 if (node == null) {
71 // couldn't find it
72 return null;
73 }
74
75 removeRedundantClassCalls(node);
76
77 ClassWriter writer = new ClassWriter(0);
78
79 ClassVisitor visitor = writer;
80 for (Function<ClassVisitor, ClassVisitor> visitorFunction : this.visitors) {
81 visitor = visitorFunction.apply(visitor);
82 }
83
84 node.accept(visitor);
85
86 // we have a transformed class!
87 return writer.toByteArray();
88 }
89
90 private void removeRedundantClassCalls(ClassNode node) {
91 // remove <obj>.getClass() calls that are seemingly injected
92 // DUP
93 // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class;
94 // POP
95 for (MethodNode methodNode : node.methods) {
96 AbstractInsnNode insnNode = methodNode.instructions.getFirst();
97 while (insnNode != null) {
98 if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) {
99 MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
100 if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) {
101 AbstractInsnNode previous = methodInsnNode.getPrevious();
102 AbstractInsnNode next = methodInsnNode.getNext();
103 if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) {
104 insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction
105 methodNode.instructions.remove(previous);
106 methodNode.instructions.remove(methodInsnNode);
107 methodNode.instructions.remove(next);
108 }
109 }
110 }
111 insnNode = insnNode.getNext();
112 }
113 }
114 }
115
116 private ClassNode findClassNode(ClassEntry entry) {
117 // try to find the class in the jar
118 for (String className : getClassNamesToTry(entry)) {
119 ClassNode node = compiledSource.getClassNode(className);
120 if (node != null) {
121 return node;
122 }
123 }
124
125 // didn't find it ;_;
126 return null;
127 }
128
129 private Collection<String> getClassNamesToTry(ClassEntry entry) {
130 List<String> classNamesToTry = Lists.newArrayList();
131 classNamesToTry.add(entry.getFullName());
132
133 ClassEntry outerClass = entry.getOuterClass();
134 if (outerClass != null) {
135 classNamesToTry.addAll(getClassNamesToTry(outerClass));
136 }
137
138 return classNamesToTry;
139 }
140}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java
deleted file mode 100644
index c4732b0..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java
+++ /dev/null
@@ -1,38 +0,0 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.ITypeLoader;
4import com.strobel.assembler.metadata.MetadataSystem;
5import com.strobel.assembler.metadata.TypeDefinition;
6import com.strobel.assembler.metadata.TypeReference;
7
8import java.util.Collections;
9import java.util.Set;
10import java.util.concurrent.ConcurrentHashMap;
11
12public final class NoRetryMetadataSystem extends MetadataSystem {
13 private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>());
14
15 public NoRetryMetadataSystem(final ITypeLoader typeLoader) {
16 super(typeLoader);
17 }
18
19 @Override
20 protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) {
21 if (failedTypes.contains(descriptor)) {
22 return null;
23 }
24
25 final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive);
26
27 if (result == null) {
28 failedTypes.add(descriptor);
29 }
30
31 return result;
32 }
33
34 @Override
35 public synchronized TypeDefinition resolve(final TypeReference type) {
36 return super.resolve(type);
37 }
38}
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java
deleted file mode 100644
index 86c6ecc..0000000
--- a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java
+++ /dev/null
@@ -1,20 +0,0 @@
1package cuchaz.enigma.source.procyon.typeloader;
2
3import com.strobel.assembler.metadata.Buffer;
4import com.strobel.assembler.metadata.ITypeLoader;
5
6/**
7 * Typeloader with synchronized tryLoadType method
8 */
9public class SynchronizedTypeLoader implements ITypeLoader {
10 private final ITypeLoader delegate;
11
12 public SynchronizedTypeLoader(ITypeLoader delegate) {
13 this.delegate = delegate;
14 }
15
16 @Override
17 public synchronized boolean tryLoadType(String internalName, Buffer buffer) {
18 return delegate.tryLoadType(internalName, buffer);
19 }
20}
diff --git a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java b/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java
deleted file mode 100644
index 0155ad2..0000000
--- a/src/main/java/cuchaz/enigma/throwables/IllegalNameException.java
+++ /dev/null
@@ -1,39 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.throwables;
13
14public class IllegalNameException extends RuntimeException {
15
16 private String name;
17 private String reason;
18
19 public IllegalNameException(String name, String reason) {
20 this.name = name;
21 this.reason = reason;
22 }
23
24 public String getReason() {
25 return this.reason;
26 }
27
28 @Override
29 public String getMessage() {
30 StringBuilder buf = new StringBuilder();
31 buf.append("Illegal name: ");
32 buf.append(this.name);
33 if (this.reason != null) {
34 buf.append(" because ");
35 buf.append(this.reason);
36 }
37 return buf.toString();
38 }
39}
diff --git a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java b/src/main/java/cuchaz/enigma/throwables/MappingConflict.java
deleted file mode 100644
index 95cd449..0000000
--- a/src/main/java/cuchaz/enigma/throwables/MappingConflict.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.throwables;
2
3public class MappingConflict extends Exception {
4 public MappingConflict(String clazz, String name, String nameExisting) {
5 super(String.format("Conflicting mappings found for %s. The mapping file is %s and the second is %s", clazz, name, nameExisting));
6 }
7}
diff --git a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java b/src/main/java/cuchaz/enigma/throwables/MappingParseException.java
deleted file mode 100644
index b7e6d42..0000000
--- a/src/main/java/cuchaz/enigma/throwables/MappingParseException.java
+++ /dev/null
@@ -1,39 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.throwables;
13
14import java.io.File;
15import java.util.function.Supplier;
16
17public class MappingParseException extends Exception {
18
19 private int line;
20 private String message;
21 private String filePath;
22
23 public MappingParseException(File file, int line, String message) {
24 this.line = line;
25 this.message = message;
26 filePath = file.getAbsolutePath();
27 }
28
29 public MappingParseException(Supplier<String> filenameProvider, int line, String message) {
30 this.line = line;
31 this.message = message;
32 filePath = filenameProvider.get();
33 }
34
35 @Override
36 public String getMessage() {
37 return "Line " + line + ": " + message + " in file " + filePath;
38 }
39}
diff --git a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java b/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
deleted file mode 100644
index 18c966c..0000000
--- a/src/main/java/cuchaz/enigma/translation/LocalNameGenerator.java
+++ /dev/null
@@ -1,44 +0,0 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.NameValidator;
4import cuchaz.enigma.translation.representation.TypeDescriptor;
5
6import java.util.Collection;
7import java.util.Locale;
8
9public class LocalNameGenerator {
10 public static String generateArgumentName(int index, TypeDescriptor desc, Collection<TypeDescriptor> arguments) {
11 boolean uniqueType = arguments.stream().filter(desc::equals).count() <= 1;
12 String translatedName;
13 int nameIndex = index + 1;
14 StringBuilder nameBuilder = new StringBuilder(getTypeName(desc));
15 if (!uniqueType || NameValidator.isReserved(nameBuilder.toString())) {
16 nameBuilder.append(nameIndex);
17 }
18 translatedName = nameBuilder.toString();
19 return translatedName;
20 }
21
22 public static String generateLocalVariableName(int index, TypeDescriptor desc) {
23 int nameIndex = index + 1;
24 return getTypeName(desc) + nameIndex;
25 }
26
27 private static String getTypeName(TypeDescriptor desc) {
28 // Unfortunately each of these have different name getters, so they have different code paths
29 if (desc.isPrimitive()) {
30 TypeDescriptor.Primitive argCls = desc.getPrimitive();
31 return argCls.name().toLowerCase(Locale.ROOT);
32 } else if (desc.isArray()) {
33 // List types would require this whole block again, so just go with aListx
34 return "arr";
35 } else if (desc.isType()) {
36 String typeName = desc.getTypeEntry().getSimpleName().replace("$", "");
37 typeName = typeName.substring(0, 1).toLowerCase(Locale.ROOT) + typeName.substring(1);
38 return typeName;
39 } else {
40 System.err.println("Encountered invalid argument type descriptor " + desc.toString());
41 return "var";
42 }
43 }
44}
diff --git a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java b/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
deleted file mode 100644
index 529d0ed..0000000
--- a/src/main/java/cuchaz/enigma/translation/MappingTranslator.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public class MappingTranslator implements Translator {
8 private final EntryMap<EntryMapping> mappings;
9 private final EntryResolver resolver;
10
11 public MappingTranslator(EntryMap<EntryMapping> mappings, EntryResolver resolver) {
12 this.mappings = mappings;
13 this.resolver = resolver;
14 }
15
16 @SuppressWarnings("unchecked")
17 @Override
18 public <T extends Translatable> T translate(T translatable) {
19 if (translatable == null) {
20 return null;
21 }
22 return (T) translatable.translate(this, resolver, mappings);
23 }
24}
diff --git a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java b/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
deleted file mode 100644
index 3783053..0000000
--- a/src/main/java/cuchaz/enigma/translation/SignatureUpdater.java
+++ /dev/null
@@ -1,92 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14import com.google.common.collect.Lists;
15
16import java.io.IOException;
17import java.io.StringReader;
18import java.util.List;
19
20public class SignatureUpdater {
21
22 public static String update(String signature, ClassNameUpdater updater) {
23 try {
24 StringBuilder buf = new StringBuilder();
25
26 // read the signature character-by-character
27 StringReader reader = new StringReader(signature);
28 int i;
29 while ((i = reader.read()) != -1) {
30 char c = (char) i;
31
32 // does this character start a class name?
33 if (c == 'L') {
34 // update the class name and add it to the buffer
35 buf.append('L');
36 String className = readClass(reader);
37 if (className == null) {
38 throw new IllegalArgumentException("Malformed signature: " + signature);
39 }
40 buf.append(updater.update(className));
41 buf.append(';');
42 } else {
43 // copy the character into the buffer
44 buf.append(c);
45 }
46 }
47
48 return buf.toString();
49 } catch (IOException ex) {
50 // I'm pretty sure a StringReader will never throw one of these
51 throw new Error(ex);
52 }
53 }
54
55 private static String readClass(StringReader reader) throws IOException {
56 // read all the characters in the buffer until we hit a ';'
57 // remember to treat generics correctly
58 StringBuilder buf = new StringBuilder();
59 int depth = 0;
60 int i;
61 while ((i = reader.read()) != -1) {
62 char c = (char) i;
63
64 if (c == '<') {
65 depth++;
66 } else if (c == '>') {
67 depth--;
68 } else if (depth == 0) {
69 if (c == ';') {
70 return buf.toString();
71 } else {
72 buf.append(c);
73 }
74 }
75 }
76
77 return null;
78 }
79
80 public static List<String> getClasses(String signature) {
81 final List<String> classNames = Lists.newArrayList();
82 update(signature, className -> {
83 classNames.add(className);
84 return className;
85 });
86 return classNames;
87 }
88
89 public interface ClassNameUpdater {
90 String update(String className);
91 }
92}
diff --git a/src/main/java/cuchaz/enigma/translation/Translatable.java b/src/main/java/cuchaz/enigma/translation/Translatable.java
deleted file mode 100644
index 0370ef1..0000000
--- a/src/main/java/cuchaz/enigma/translation/Translatable.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.translation;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.EntryResolver;
5import cuchaz.enigma.translation.mapping.EntryMap;
6
7public interface Translatable {
8 Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
9}
diff --git a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java b/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
deleted file mode 100644
index 2ecb30b..0000000
--- a/src/main/java/cuchaz/enigma/translation/TranslationDirection.java
+++ /dev/null
@@ -1,36 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14public enum TranslationDirection {
15
16 DEOBFUSCATING {
17 @Override
18 public <T> T choose(T deobfChoice, T obfChoice) {
19 if (deobfChoice == null) {
20 return obfChoice;
21 }
22 return deobfChoice;
23 }
24 },
25 OBFUSCATING {
26 @Override
27 public <T> T choose(T deobfChoice, T obfChoice) {
28 if (obfChoice == null) {
29 return deobfChoice;
30 }
31 return obfChoice;
32 }
33 };
34
35 public abstract <T> T choose(T deobfChoice, T obfChoice);
36}
diff --git a/src/main/java/cuchaz/enigma/translation/Translator.java b/src/main/java/cuchaz/enigma/translation/Translator.java
deleted file mode 100644
index c70141f..0000000
--- a/src/main/java/cuchaz/enigma/translation/Translator.java
+++ /dev/null
@@ -1,61 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation;
13
14import com.google.common.collect.HashMultimap;
15import com.google.common.collect.Multimap;
16
17import java.util.Collection;
18import java.util.HashMap;
19import java.util.Map;
20import java.util.Set;
21import java.util.stream.Collectors;
22
23public interface Translator {
24 <T extends Translatable> T translate(T translatable);
25
26 default <T extends Translatable> Collection<T> translate(Collection<T> translatable) {
27 return translatable.stream()
28 .map(this::translate)
29 .collect(Collectors.toList());
30 }
31
32 default <T extends Translatable> Set<T> translate(Set<T> translatable) {
33 return translatable.stream()
34 .map(this::translate)
35 .collect(Collectors.toSet());
36 }
37
38 default <T extends Translatable, V> Map<T, V> translateKeys(Map<T, V> translatable) {
39 Map<T, V> result = new HashMap<>(translatable.size());
40 for (Map.Entry<T, V> entry : translatable.entrySet()) {
41 result.put(translate(entry.getKey()), entry.getValue());
42 }
43 return result;
44 }
45
46 default <K extends Translatable, V extends Translatable> Map<K, V> translate(Map<K, V> translatable) {
47 Map<K, V> result = new HashMap<>(translatable.size());
48 for (Map.Entry<K, V> entry : translatable.entrySet()) {
49 result.put(translate(entry.getKey()), translate(entry.getValue()));
50 }
51 return result;
52 }
53
54 default <K extends Translatable, V extends Translatable> Multimap<K, V> translate(Multimap<K, V> translatable) {
55 Multimap<K, V> result = HashMultimap.create(translatable.size(), 1);
56 for (Map.Entry<K, Collection<V>> entry : translatable.asMap().entrySet()) {
57 result.putAll(translate(entry.getKey()), translate(entry.getValue()));
58 }
59 return result;
60 }
61}
diff --git a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java b/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
deleted file mode 100644
index c010833..0000000
--- a/src/main/java/cuchaz/enigma/translation/VoidTranslator.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.translation;
2
3public enum VoidTranslator implements Translator {
4 INSTANCE;
5
6 @Override
7 public <T extends Translatable> T translate(T translatable) {
8 return translatable;
9 }
10}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java b/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
deleted file mode 100644
index 5b79b79..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/AccessModifier.java
+++ /dev/null
@@ -1,25 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public enum AccessModifier {
6 UNCHANGED, PUBLIC, PROTECTED, PRIVATE;
7
8 public String getFormattedName() {
9 return "ACC:" + super.toString();
10 }
11
12 public AccessFlags transform(AccessFlags access) {
13 switch (this) {
14 case PUBLIC:
15 return access.setPublic();
16 case PROTECTED:
17 return access.setProtected();
18 case PRIVATE:
19 return access.setPrivate();
20 case UNCHANGED:
21 default:
22 return access;
23 }
24 }
25}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
deleted file mode 100644
index e1a3253..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMap.java
+++ /dev/null
@@ -1,24 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.stream.Stream;
7
8public interface EntryMap<T> {
9 void insert(Entry<?> entry, T value);
10
11 @Nullable
12 T remove(Entry<?> entry);
13
14 @Nullable
15 T get(Entry<?> entry);
16
17 default boolean contains(Entry<?> entry) {
18 return get(entry) != null;
19 }
20
21 Stream<Entry<?>> getAllEntries();
22
23 boolean isEmpty();
24}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
deleted file mode 100644
index c607817..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryMapping.java
+++ /dev/null
@@ -1,75 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import javax.annotation.Nonnull;
4import javax.annotation.Nullable;
5
6public class EntryMapping {
7 private final String targetName;
8 private final AccessModifier accessModifier;
9 private final @Nullable String javadoc;
10
11 public EntryMapping(@Nonnull String targetName) {
12 this(targetName, AccessModifier.UNCHANGED);
13 }
14
15 public EntryMapping(@Nonnull String targetName, @Nullable String javadoc) {
16 this(targetName, AccessModifier.UNCHANGED, javadoc);
17 }
18
19 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier) {
20 this(targetName, accessModifier, null);
21 }
22
23 public EntryMapping(@Nonnull String targetName, AccessModifier accessModifier, @Nullable String javadoc) {
24 this.targetName = targetName;
25 this.accessModifier = accessModifier;
26 this.javadoc = javadoc;
27 }
28
29 @Nonnull
30 public String getTargetName() {
31 return targetName;
32 }
33
34 @Nonnull
35 public AccessModifier getAccessModifier() {
36 if (accessModifier == null) {
37 return AccessModifier.UNCHANGED;
38 }
39 return accessModifier;
40 }
41
42 @Nullable
43 public String getJavadoc() {
44 return javadoc;
45 }
46
47 public EntryMapping withName(String newName) {
48 return new EntryMapping(newName, accessModifier, javadoc);
49 }
50
51 public EntryMapping withModifier(AccessModifier newModifier) {
52 return new EntryMapping(targetName, newModifier, javadoc);
53 }
54
55 public EntryMapping withDocs(String newDocs) {
56 return new EntryMapping(targetName, accessModifier, newDocs);
57 }
58
59 @Override
60 public boolean equals(Object obj) {
61 if (obj == this) return true;
62
63 if (obj instanceof EntryMapping) {
64 EntryMapping mapping = (EntryMapping) obj;
65 return mapping.targetName.equals(targetName) && mapping.accessModifier.equals(accessModifier);
66 }
67
68 return false;
69 }
70
71 @Override
72 public int hashCode() {
73 return targetName.hashCode() + accessModifier.hashCode() * 31;
74 }
75}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
deleted file mode 100644
index ad36c97..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryRemapper.java
+++ /dev/null
@@ -1,105 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.JarIndex;
4import cuchaz.enigma.translation.MappingTranslator;
5import cuchaz.enigma.translation.Translatable;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.entry.Entry;
11
12import javax.annotation.Nullable;
13import java.util.Collection;
14import java.util.function.UnaryOperator;
15import java.util.stream.Stream;
16
17public class EntryRemapper {
18 private final DeltaTrackingTree<EntryMapping> obfToDeobf;
19
20 private final EntryResolver obfResolver;
21 private final Translator deobfuscator;
22
23 private final MappingValidator validator;
24
25 private EntryRemapper(JarIndex jarIndex, EntryTree<EntryMapping> obfToDeobf) {
26 this.obfToDeobf = new DeltaTrackingTree<>(obfToDeobf);
27
28 this.obfResolver = jarIndex.getEntryResolver();
29
30 this.deobfuscator = new MappingTranslator(obfToDeobf, obfResolver);
31
32 this.validator = new MappingValidator(obfToDeobf, deobfuscator, jarIndex);
33 }
34
35 public static EntryRemapper mapped(JarIndex index, EntryTree<EntryMapping> obfToDeobf) {
36 return new EntryRemapper(index, obfToDeobf);
37 }
38
39 public static EntryRemapper empty(JarIndex index) {
40 return new EntryRemapper(index, new HashEntryTree<>());
41 }
42
43 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping) {
44 mapFromObf(obfuscatedEntry, deobfMapping, true);
45 }
46
47 public <E extends Entry<?>> void mapFromObf(E obfuscatedEntry, @Nullable EntryMapping deobfMapping, boolean renaming) {
48 Collection<E> resolvedEntries = obfResolver.resolveEntry(obfuscatedEntry, renaming ? ResolutionStrategy.RESOLVE_ROOT : ResolutionStrategy.RESOLVE_CLOSEST);
49
50 if (renaming && deobfMapping != null) {
51 for (E resolvedEntry : resolvedEntries) {
52 validator.validateRename(resolvedEntry, deobfMapping.getTargetName());
53 }
54 }
55
56 for (E resolvedEntry : resolvedEntries) {
57 obfToDeobf.insert(resolvedEntry, deobfMapping);
58 }
59 }
60
61 public void removeByObf(Entry<?> obfuscatedEntry) {
62 mapFromObf(obfuscatedEntry, null);
63 }
64
65 @Nullable
66 public EntryMapping getDeobfMapping(Entry<?> entry) {
67 return obfToDeobf.get(entry);
68 }
69
70 public boolean hasDeobfMapping(Entry<?> obfEntry) {
71 return obfToDeobf.contains(obfEntry);
72 }
73
74 public <T extends Translatable> T deobfuscate(T translatable) {
75 return deobfuscator.translate(translatable);
76 }
77
78 public Translator getDeobfuscator() {
79 return deobfuscator;
80 }
81
82 public Stream<Entry<?>> getObfEntries() {
83 return obfToDeobf.getAllEntries();
84 }
85
86 public Collection<Entry<?>> getObfChildren(Entry<?> obfuscatedEntry) {
87 return obfToDeobf.getChildren(obfuscatedEntry);
88 }
89
90 public DeltaTrackingTree<EntryMapping> getObfToDeobf() {
91 return obfToDeobf;
92 }
93
94 public MappingDelta<EntryMapping> takeMappingDelta() {
95 return obfToDeobf.takeDelta();
96 }
97
98 public boolean isDirty() {
99 return obfToDeobf.isDirty();
100 }
101
102 public EntryResolver getObfResolver() {
103 return obfResolver;
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
deleted file mode 100644
index 521f72d..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/EntryResolver.java
+++ /dev/null
@@ -1,41 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Streams;
4import cuchaz.enigma.analysis.EntryReference;
5import cuchaz.enigma.translation.representation.entry.Entry;
6import cuchaz.enigma.translation.representation.entry.MethodEntry;
7
8import java.util.Collection;
9import java.util.Set;
10import java.util.stream.Collectors;
11
12public interface EntryResolver {
13 <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy);
14
15 default <E extends Entry<?>> E resolveFirstEntry(E entry, ResolutionStrategy strategy) {
16 return resolveEntry(entry, strategy).stream().findFirst().orElse(entry);
17 }
18
19 default <E extends Entry<?>, C extends Entry<?>> Collection<EntryReference<E, C>> resolveReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
20 Collection<E> entry = resolveEntry(reference.entry, strategy);
21 if (reference.context != null) {
22 Collection<C> context = resolveEntry(reference.context, strategy);
23 return Streams.zip(entry.stream(), context.stream(), (e, c) -> new EntryReference<>(e, c, reference))
24 .collect(Collectors.toList());
25 } else {
26 return entry.stream()
27 .map(e -> new EntryReference<>(e, null, reference))
28 .collect(Collectors.toList());
29 }
30 }
31
32 default <E extends Entry<?>, C extends Entry<?>> EntryReference<E, C> resolveFirstReference(EntryReference<E, C> reference, ResolutionStrategy strategy) {
33 E entry = resolveFirstEntry(reference.entry, strategy);
34 C context = resolveFirstEntry(reference.context, strategy);
35 return new EntryReference<>(entry, context, reference);
36 }
37
38 Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry);
39
40 Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry);
41}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
deleted file mode 100644
index 78231dd..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/IndexEntryResolver.java
+++ /dev/null
@@ -1,227 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.common.collect.Sets;
4import cuchaz.enigma.analysis.IndexTreeBuilder;
5import cuchaz.enigma.analysis.MethodImplementationsTreeNode;
6import cuchaz.enigma.analysis.MethodInheritanceTreeNode;
7import cuchaz.enigma.analysis.index.BridgeMethodIndex;
8import cuchaz.enigma.analysis.index.EntryIndex;
9import cuchaz.enigma.analysis.index.InheritanceIndex;
10import cuchaz.enigma.analysis.index.JarIndex;
11import cuchaz.enigma.translation.VoidTranslator;
12import cuchaz.enigma.translation.representation.AccessFlags;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16
17import javax.annotation.Nullable;
18import java.util.*;
19import java.util.stream.Collectors;
20
21public class IndexEntryResolver implements EntryResolver {
22 private final EntryIndex entryIndex;
23 private final InheritanceIndex inheritanceIndex;
24 private final BridgeMethodIndex bridgeMethodIndex;
25
26 private final IndexTreeBuilder treeBuilder;
27
28 public IndexEntryResolver(JarIndex index) {
29 this.entryIndex = index.getEntryIndex();
30 this.inheritanceIndex = index.getInheritanceIndex();
31 this.bridgeMethodIndex = index.getBridgeMethodIndex();
32
33 this.treeBuilder = new IndexTreeBuilder(index);
34 }
35
36 @Override
37 @SuppressWarnings("unchecked")
38 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
39 if (entry == null) {
40 return Collections.emptySet();
41 }
42
43 Entry<ClassEntry> classChild = getClassChild(entry);
44 if (classChild != null && !(classChild instanceof ClassEntry)) {
45 AccessFlags access = entryIndex.getEntryAccess(classChild);
46
47 // If we're looking for the closest and this entry exists, we're done looking
48 if (strategy == ResolutionStrategy.RESOLVE_CLOSEST && access != null) {
49 return Collections.singleton(entry);
50 }
51
52 if (access == null || !access.isPrivate()) {
53 Collection<Entry<ClassEntry>> resolvedChildren = resolveChildEntry(classChild, strategy);
54 if (!resolvedChildren.isEmpty()) {
55 return resolvedChildren.stream()
56 .map(resolvedChild -> (E) entry.replaceAncestor(classChild, resolvedChild))
57 .collect(Collectors.toList());
58 }
59 }
60 }
61
62 return Collections.singleton(entry);
63 }
64
65 @Nullable
66 private Entry<ClassEntry> getClassChild(Entry<?> entry) {
67 if (entry instanceof ClassEntry) {
68 return null;
69 }
70
71 // get the entry in the hierarchy that is the child of a class
72 List<Entry<?>> ancestry = entry.getAncestry();
73 for (int i = ancestry.size() - 1; i > 0; i--) {
74 Entry<?> child = ancestry.get(i);
75 Entry<ClassEntry> cast = child.castParent(ClassEntry.class);
76 if (cast != null && !(cast instanceof ClassEntry)) {
77 // we found the entry which is a child of a class, we are now able to resolve the owner of this entry
78 return cast;
79 }
80 }
81
82 return null;
83 }
84
85 private Set<Entry<ClassEntry>> resolveChildEntry(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
86 ClassEntry ownerClass = entry.getParent();
87
88 if (entry instanceof MethodEntry) {
89 MethodEntry bridgeMethod = bridgeMethodIndex.getBridgeFromSpecialized((MethodEntry) entry);
90 if (bridgeMethod != null && ownerClass.equals(bridgeMethod.getParent())) {
91 Set<Entry<ClassEntry>> resolvedBridge = resolveChildEntry(bridgeMethod, strategy);
92 if (!resolvedBridge.isEmpty()) {
93 return resolvedBridge;
94 } else {
95 return Collections.singleton(bridgeMethod);
96 }
97 }
98 }
99
100 Set<Entry<ClassEntry>> resolvedEntries = new HashSet<>();
101
102 for (ClassEntry parentClass : inheritanceIndex.getParents(ownerClass)) {
103 Entry<ClassEntry> parentEntry = entry.withParent(parentClass);
104
105 if (strategy == ResolutionStrategy.RESOLVE_ROOT) {
106 resolvedEntries.addAll(resolveRoot(parentEntry, strategy));
107 } else {
108 resolvedEntries.addAll(resolveClosest(parentEntry, strategy));
109 }
110 }
111
112 return resolvedEntries;
113 }
114
115 private Collection<Entry<ClassEntry>> resolveRoot(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
116 // When resolving root, we want to first look for the lowest entry before returning ourselves
117 Set<Entry<ClassEntry>> parentResolution = resolveChildEntry(entry, strategy);
118
119 if (parentResolution.isEmpty()) {
120 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
121 if (parentAccess != null && !parentAccess.isPrivate()) {
122 return Collections.singleton(entry);
123 }
124 }
125
126 return parentResolution;
127 }
128
129 private Collection<Entry<ClassEntry>> resolveClosest(Entry<ClassEntry> entry, ResolutionStrategy strategy) {
130 // When resolving closest, we want to first check if we exist before looking further down
131 AccessFlags parentAccess = entryIndex.getEntryAccess(entry);
132 if (parentAccess != null && !parentAccess.isPrivate()) {
133 return Collections.singleton(entry);
134 } else {
135 return resolveChildEntry(entry, strategy);
136 }
137 }
138
139 @Override
140 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
141 MethodEntry relevantMethod = entry.findAncestor(MethodEntry.class);
142 if (relevantMethod == null || !entryIndex.hasMethod(relevantMethod)) {
143 return Collections.singleton(entry);
144 }
145
146 Set<MethodEntry> equivalentMethods = resolveEquivalentMethods(relevantMethod);
147 Set<Entry<?>> equivalentEntries = new HashSet<>(equivalentMethods.size());
148
149 for (MethodEntry equivalentMethod : equivalentMethods) {
150 Entry<?> equivalentEntry = entry.replaceAncestor(relevantMethod, equivalentMethod);
151 equivalentEntries.add(equivalentEntry);
152 }
153
154 return equivalentEntries;
155 }
156
157 @Override
158 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
159 AccessFlags access = entryIndex.getMethodAccess(methodEntry);
160 if (access == null) {
161 throw new IllegalArgumentException("Could not find method " + methodEntry);
162 }
163
164 if (!canInherit(methodEntry, access)) {
165 return Collections.singleton(methodEntry);
166 }
167
168 Set<MethodEntry> methodEntries = Sets.newHashSet();
169 resolveEquivalentMethods(methodEntries, treeBuilder.buildMethodInheritance(VoidTranslator.INSTANCE, methodEntry));
170 return methodEntries;
171 }
172
173 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodInheritanceTreeNode node) {
174 MethodEntry methodEntry = node.getMethodEntry();
175 if (methodEntries.contains(methodEntry)) {
176 return;
177 }
178
179 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
180 if (flags != null && canInherit(methodEntry, flags)) {
181 // collect the entry
182 methodEntries.add(methodEntry);
183 }
184
185 // look at bridge methods!
186 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
187 while (bridgedMethod != null) {
188 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
189 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
190 }
191
192 // look at interface methods too
193 for (MethodImplementationsTreeNode implementationsNode : treeBuilder.buildMethodImplementations(VoidTranslator.INSTANCE, methodEntry)) {
194 resolveEquivalentMethods(methodEntries, implementationsNode);
195 }
196
197 // recurse
198 for (int i = 0; i < node.getChildCount(); i++) {
199 resolveEquivalentMethods(methodEntries, (MethodInheritanceTreeNode) node.getChildAt(i));
200 }
201 }
202
203 private void resolveEquivalentMethods(Set<MethodEntry> methodEntries, MethodImplementationsTreeNode node) {
204 MethodEntry methodEntry = node.getMethodEntry();
205 AccessFlags flags = entryIndex.getMethodAccess(methodEntry);
206 if (flags != null && !flags.isPrivate() && !flags.isStatic()) {
207 // collect the entry
208 methodEntries.add(methodEntry);
209 }
210
211 // look at bridge methods!
212 MethodEntry bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(methodEntry);
213 while (bridgedMethod != null) {
214 methodEntries.addAll(resolveEquivalentMethods(bridgedMethod));
215 bridgedMethod = bridgeMethodIndex.getBridgeFromSpecialized(bridgedMethod);
216 }
217
218 // recurse
219 for (int i = 0; i < node.getChildCount(); i++) {
220 resolveEquivalentMethods(methodEntries, (MethodImplementationsTreeNode) node.getChildAt(i));
221 }
222 }
223
224 private boolean canInherit(MethodEntry entry, AccessFlags access) {
225 return !entry.isConstructor() && !access.isPrivate() && !access.isStatic() && !access.isFinal();
226 }
227}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
deleted file mode 100644
index 1407bb6..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingDelta.java
+++ /dev/null
@@ -1,54 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
7import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import java.util.stream.Stream;
11
12public class MappingDelta<T> implements Translatable {
13 public static final Object PLACEHOLDER = new Object();
14
15 private final EntryTree<T> baseMappings;
16
17 private final EntryTree<Object> changes;
18
19 public MappingDelta(EntryTree<T> baseMappings, EntryTree<Object> changes) {
20 this.baseMappings = baseMappings;
21 this.changes = changes;
22 }
23
24 public MappingDelta(EntryTree<T> baseMappings) {
25 this(baseMappings, new HashEntryTree<>());
26 }
27
28 public static <T> MappingDelta<T> added(EntryTree<T> mappings) {
29 EntryTree<Object> changes = new HashEntryTree<>();
30 mappings.getAllEntries().forEach(entry -> changes.insert(entry, PLACEHOLDER));
31
32 return new MappingDelta<>(new HashEntryTree<>(), changes);
33 }
34
35 public EntryTree<T> getBaseMappings() {
36 return baseMappings;
37 }
38
39 public EntryTree<?> getChanges() {
40 return changes;
41 }
42
43 public Stream<Entry<?>> getChangedRoots() {
44 return changes.getRootNodes().map(EntryTreeNode::getEntry);
45 }
46
47 @Override
48 public MappingDelta<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
49 return new MappingDelta<>(
50 translator.translate(baseMappings),
51 translator.translate(changes)
52 );
53 }
54}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java
deleted file mode 100644
index e40bfe7..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingFileNameFormat.java
+++ /dev/null
@@ -1,10 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.gson.annotations.SerializedName;
4
5public enum MappingFileNameFormat {
6 @SerializedName("by_obf")
7 BY_OBF,
8 @SerializedName("by_deobf")
9 BY_DEOBF
10}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
deleted file mode 100644
index 5d39e3d..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingPair.java
+++ /dev/null
@@ -1,32 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6
7public class MappingPair<E extends Entry<?>, M> {
8 private final E entry;
9 private M mapping;
10
11 public MappingPair(E entry, @Nullable M mapping) {
12 this.entry = entry;
13 this.mapping = mapping;
14 }
15
16 public MappingPair(E entry) {
17 this(entry, null);
18 }
19
20 public E getEntry() {
21 return entry;
22 }
23
24 @Nullable
25 public M getMapping() {
26 return mapping;
27 }
28
29 public void setMapping(M mapping) {
30 this.mapping = mapping;
31 }
32}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java
deleted file mode 100644
index 07065d6..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingSaveParameters.java
+++ /dev/null
@@ -1,16 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import com.google.gson.annotations.SerializedName;
4
5public class MappingSaveParameters {
6 @SerializedName("file_name_format")
7 private final MappingFileNameFormat fileNameFormat;
8
9 public MappingSaveParameters(MappingFileNameFormat fileNameFormat) {
10 this.fileNameFormat = fileNameFormat;
11 }
12
13 public MappingFileNameFormat getFileNameFormat() {
14 return fileNameFormat;
15 }
16}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
deleted file mode 100644
index dffcb0c..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingValidator.java
+++ /dev/null
@@ -1,76 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.analysis.index.InheritanceIndex;
4import cuchaz.enigma.analysis.index.JarIndex;
5import cuchaz.enigma.throwables.IllegalNameException;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.translation.representation.entry.ClassEntry;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.stream.Collectors;
14
15public class MappingValidator {
16 private final EntryTree<EntryMapping> obfToDeobf;
17 private final Translator deobfuscator;
18 private final JarIndex index;
19
20 public MappingValidator(EntryTree<EntryMapping> obfToDeobf, Translator deobfuscator, JarIndex index) {
21 this.obfToDeobf = obfToDeobf;
22 this.deobfuscator = deobfuscator;
23 this.index = index;
24 }
25
26 public void validateRename(Entry<?> entry, String name) throws IllegalNameException {
27 Collection<Entry<?>> equivalentEntries = index.getEntryResolver().resolveEquivalentEntries(entry);
28 for (Entry<?> equivalentEntry : equivalentEntries) {
29 equivalentEntry.validateName(name);
30 validateUnique(equivalentEntry, name);
31 }
32 }
33
34 private void validateUnique(Entry<?> entry, String name) {
35 ClassEntry containingClass = entry.getContainingClass();
36 Collection<ClassEntry> relatedClasses = getRelatedClasses(containingClass);
37
38 for (ClassEntry relatedClass : relatedClasses) {
39 Entry<?> relatedEntry = entry.replaceAncestor(containingClass, relatedClass);
40 Entry<?> translatedEntry = deobfuscator.translate(relatedEntry);
41
42 Collection<Entry<?>> translatedSiblings = obfToDeobf.getSiblings(relatedEntry).stream()
43 .map(deobfuscator::translate)
44 .collect(Collectors.toList());
45
46 if (!isUnique(translatedEntry, translatedSiblings, name)) {
47 Entry<?> parent = translatedEntry.getParent();
48 if (parent != null) {
49 throw new IllegalNameException(name, "Name is not unique in " + parent + "!");
50 } else {
51 throw new IllegalNameException(name, "Name is not unique!");
52 }
53 }
54 }
55 }
56
57 private Collection<ClassEntry> getRelatedClasses(ClassEntry classEntry) {
58 InheritanceIndex inheritanceIndex = index.getInheritanceIndex();
59
60 Collection<ClassEntry> relatedClasses = new HashSet<>();
61 relatedClasses.add(classEntry);
62 relatedClasses.addAll(inheritanceIndex.getChildren(classEntry));
63 relatedClasses.addAll(inheritanceIndex.getAncestors(classEntry));
64
65 return relatedClasses;
66 }
67
68 private boolean isUnique(Entry<?> entry, Collection<Entry<?>> siblings, String name) {
69 for (Entry<?> sibling : siblings) {
70 if (entry.canConflictWith(sibling) && sibling.getName().equals(name)) {
71 return false;
72 }
73 }
74 return true;
75 }
76}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java b/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
deleted file mode 100644
index 5d9794f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/MappingsChecker.java
+++ /dev/null
@@ -1,99 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.ProgressListener;
15import cuchaz.enigma.analysis.index.JarIndex;
16import cuchaz.enigma.translation.mapping.tree.EntryTree;
17import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19import cuchaz.enigma.translation.representation.entry.Entry;
20import cuchaz.enigma.translation.representation.entry.FieldEntry;
21import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
22import cuchaz.enigma.translation.representation.entry.MethodEntry;
23
24import java.util.Collection;
25import java.util.HashMap;
26import java.util.Map;
27import java.util.stream.Collectors;
28
29public class MappingsChecker {
30 private final JarIndex index;
31 private final EntryTree<EntryMapping> mappings;
32
33 public MappingsChecker(JarIndex index, EntryTree<EntryMapping> mappings) {
34 this.index = index;
35 this.mappings = mappings;
36 }
37
38 public Dropped dropBrokenMappings(ProgressListener progress) {
39 Dropped dropped = new Dropped();
40
41 Collection<Entry<?>> obfEntries = mappings.getAllEntries()
42 .filter(e -> e instanceof ClassEntry || e instanceof MethodEntry || e instanceof FieldEntry || e instanceof LocalVariableEntry)
43 .collect(Collectors.toList());
44
45 progress.init(obfEntries.size(), "Checking for dropped mappings");
46
47 int steps = 0;
48 for (Entry<?> entry : obfEntries) {
49 progress.step(steps++, entry.toString());
50 tryDropEntry(dropped, entry);
51 }
52
53 dropped.apply(mappings);
54
55 return dropped;
56 }
57
58 private void tryDropEntry(Dropped dropped, Entry<?> entry) {
59 if (shouldDropEntry(entry)) {
60 EntryMapping mapping = mappings.get(entry);
61 if (mapping != null) {
62 dropped.drop(entry, mapping);
63 }
64 }
65 }
66
67 private boolean shouldDropEntry(Entry<?> entry) {
68 if (!index.getEntryIndex().hasEntry(entry)) {
69 return true;
70 }
71 Collection<Entry<?>> resolvedEntries = index.getEntryResolver().resolveEntry(entry, ResolutionStrategy.RESOLVE_ROOT);
72 return !resolvedEntries.contains(entry);
73 }
74
75 public static class Dropped {
76 private final Map<Entry<?>, String> droppedMappings = new HashMap<>();
77
78 public void drop(Entry<?> entry, EntryMapping mapping) {
79 droppedMappings.put(entry, mapping.getTargetName());
80 }
81
82 void apply(EntryTree<EntryMapping> mappings) {
83 for (Entry<?> entry : droppedMappings.keySet()) {
84 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
85 if (node == null) {
86 continue;
87 }
88
89 for (Entry<?> childEntry : node.getChildrenRecursively()) {
90 mappings.remove(childEntry);
91 }
92 }
93 }
94
95 public Map<Entry<?>, String> getDroppedMappings() {
96 return droppedMappings;
97 }
98 }
99}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java b/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
deleted file mode 100644
index 5bc2f67..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/NameValidator.java
+++ /dev/null
@@ -1,53 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.representation.entry.ClassEntry;
16
17import java.util.Arrays;
18import java.util.List;
19import java.util.regex.Pattern;
20
21public class NameValidator {
22 private static final Pattern IDENTIFIER_PATTERN;
23 private static final Pattern CLASS_PATTERN;
24 private static final List<String> ILLEGAL_IDENTIFIERS = Arrays.asList(
25 "abstract", "continue", "for", "new", "switch", "assert", "default", "goto", "package", "synchronized",
26 "boolean", "do", "if", "private", "this", "break", "double", "implements", "protected", "throw", "byte",
27 "else", "import", "public", "throws", "case", "enum", "instanceof", "return", "transient", "catch",
28 "extends", "int", "short", "try", "char", "final", "interface", "static", "void", "class", "finally",
29 "long", "strictfp", "volatile", "const", "float", "native", "super", "while", "_"
30 );
31
32 static {
33 String identifierRegex = "[A-Za-z_<][A-Za-z0-9_>]*";
34 IDENTIFIER_PATTERN = Pattern.compile(identifierRegex);
35 CLASS_PATTERN = Pattern.compile(String.format("^(%s(\\.|/))*(%s)$", identifierRegex, identifierRegex));
36 }
37
38 public static void validateClassName(String name) {
39 if (!CLASS_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
40 throw new IllegalNameException(name, "This doesn't look like a legal class name");
41 }
42 }
43
44 public static void validateIdentifier(String name) {
45 if (!IDENTIFIER_PATTERN.matcher(name).matches() || ILLEGAL_IDENTIFIERS.contains(name)) {
46 throw new IllegalNameException(name, "This doesn't look like a legal identifier");
47 }
48 }
49
50 public static boolean isReserved(String name) {
51 return ILLEGAL_IDENTIFIERS.contains(name);
52 }
53}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java b/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
deleted file mode 100644
index 1c28e02..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/ResolutionStrategy.java
+++ /dev/null
@@ -1,6 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3public enum ResolutionStrategy {
4 RESOLVE_ROOT,
5 RESOLVE_CLOSEST
6}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java b/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
deleted file mode 100644
index 2eab55f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/VoidEntryResolver.java
+++ /dev/null
@@ -1,27 +0,0 @@
1package cuchaz.enigma.translation.mapping;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4import cuchaz.enigma.translation.representation.entry.MethodEntry;
5
6import java.util.Collection;
7import java.util.Collections;
8import java.util.Set;
9
10public enum VoidEntryResolver implements EntryResolver {
11 INSTANCE;
12
13 @Override
14 public <E extends Entry<?>> Collection<E> resolveEntry(E entry, ResolutionStrategy strategy) {
15 return Collections.singleton(entry);
16 }
17
18 @Override
19 public Set<Entry<?>> resolveEquivalentEntries(Entry<?> entry) {
20 return Collections.singleton(entry);
21 }
22
23 @Override
24 public Set<MethodEntry> resolveEquivalentMethods(MethodEntry methodEntry) {
25 return Collections.singleton(methodEntry);
26 }
27}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java
deleted file mode 100644
index af92ffb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaFormat.java
+++ /dev/null
@@ -1,9 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3public class EnigmaFormat {
4 public static final String COMMENT = "COMMENT";
5 public static final String CLASS = "CLASS";
6 public static final String FIELD = "FIELD";
7 public static final String METHOD = "METHOD";
8 public static final String PARAMETER = "ARG";
9}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
deleted file mode 100644
index 53bbaa3..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsReader.java
+++ /dev/null
@@ -1,319 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.AccessModifier;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingPair;
9import cuchaz.enigma.translation.mapping.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
12import cuchaz.enigma.translation.representation.MethodDescriptor;
13import cuchaz.enigma.translation.representation.TypeDescriptor;
14import cuchaz.enigma.translation.representation.entry.*;
15import cuchaz.enigma.utils.I18n;
16
17import javax.annotation.Nullable;
18import java.io.IOException;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.ArrayDeque;
24import java.util.Arrays;
25import java.util.Deque;
26import java.util.List;
27import java.util.Locale;
28import java.util.stream.Collectors;
29
30public enum EnigmaMappingsReader implements MappingsReader {
31 FILE {
32 @Override
33 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
34 progress.init(1, I18n.translate("progress.mappings.enigma_file.loading"));
35
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37 readFile(path, mappings);
38
39 progress.step(1, I18n.translate("progress.mappings.enigma_file.done"));
40
41 return mappings;
42 }
43 },
44 DIRECTORY {
45 @Override
46 public EntryTree<EntryMapping> read(Path root, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
47 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
48
49 List<Path> files = Files.walk(root)
50 .filter(f -> !Files.isDirectory(f))
51 .filter(f -> f.toString().endsWith(".mapping"))
52 .collect(Collectors.toList());
53
54 progress.init(files.size(), I18n.translate("progress.mappings.enigma_directory.loading"));
55 int step = 0;
56
57 for (Path file : files) {
58 progress.step(step++, root.relativize(file).toString());
59 if (Files.isHidden(file)) {
60 continue;
61 }
62 readFile(file, mappings);
63 }
64
65 return mappings;
66 }
67 },
68 ZIP {
69 @Override
70 public EntryTree<EntryMapping> read(Path zip, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
71 try (FileSystem fs = FileSystems.newFileSystem(zip, (ClassLoader) null)) {
72 return DIRECTORY.read(fs.getPath("/"), progress, saveParameters);
73 }
74 }
75 };
76
77 protected void readFile(Path path, EntryTree<EntryMapping> mappings) throws IOException, MappingParseException {
78 List<String> lines = Files.readAllLines(path, Charsets.UTF_8);
79 Deque<MappingPair<?, RawEntryMapping>> mappingStack = new ArrayDeque<>();
80
81 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
82 String line = lines.get(lineNumber);
83 int indentation = countIndentation(line);
84
85 line = formatLine(line);
86 if (line == null) {
87 continue;
88 }
89
90 cleanMappingStack(indentation, mappingStack, mappings);
91
92 try {
93 MappingPair<?, RawEntryMapping> pair = parseLine(mappingStack.peek(), line);
94 if (pair != null) {
95 mappingStack.push(pair);
96 if (pair.getMapping() != null) {
97
98 }
99 }
100 } catch (Throwable t) {
101 t.printStackTrace();
102 throw new MappingParseException(path::toString, lineNumber, t.toString());
103 }
104 }
105
106 // Clean up rest
107 cleanMappingStack(0, mappingStack, mappings);
108 }
109
110 private void cleanMappingStack(int indentation, Deque<MappingPair<?, RawEntryMapping>> mappingStack, EntryTree<EntryMapping> mappings) {
111 while (indentation < mappingStack.size()) {
112 MappingPair<?, RawEntryMapping> pair = mappingStack.pop();
113 if (pair.getMapping() != null) {
114 mappings.insert(pair.getEntry(), pair.getMapping().bake());
115 }
116 }
117 }
118
119 @Nullable
120 private String formatLine(String line) {
121 line = stripComment(line);
122 line = line.trim();
123
124 if (line.isEmpty()) {
125 return null;
126 }
127
128 return line;
129 }
130
131 private String stripComment(String line) {
132 //Dont support comments on javadoc lines
133 if (line.trim().startsWith(EnigmaFormat.COMMENT)) {
134 return line;
135 }
136
137 int commentPos = line.indexOf('#');
138 if (commentPos >= 0) {
139 return line.substring(0, commentPos);
140 }
141 return line;
142 }
143
144 private int countIndentation(String line) {
145 int indent = 0;
146 for (int i = 0; i < line.length(); i++) {
147 if (line.charAt(i) != '\t') {
148 break;
149 }
150 indent++;
151 }
152 return indent;
153 }
154
155 private MappingPair<?, RawEntryMapping> parseLine(@Nullable MappingPair<?, RawEntryMapping> parent, String line) {
156 String[] tokens = line.trim().split("\\s");
157 String keyToken = tokens[0].toUpperCase(Locale.ROOT);
158 Entry<?> parentEntry = parent == null ? null : parent.getEntry();
159
160 switch (keyToken) {
161 case EnigmaFormat.CLASS:
162 return parseClass(parentEntry, tokens);
163 case EnigmaFormat.FIELD:
164 return parseField(parentEntry, tokens);
165 case EnigmaFormat.METHOD:
166 return parseMethod(parentEntry, tokens);
167 case EnigmaFormat.PARAMETER:
168 return parseArgument(parentEntry, tokens);
169 case EnigmaFormat.COMMENT:
170 readJavadoc(parent, tokens);
171 return null;
172 default:
173 throw new RuntimeException("Unknown token '" + keyToken + "'");
174 }
175 }
176
177 private void readJavadoc(MappingPair<?, RawEntryMapping> parent, String[] tokens) {
178 if (parent == null)
179 throw new IllegalStateException("Javadoc has no parent!");
180 // Empty string to concat
181 String jdLine = tokens.length > 1 ? String.join(" ", Arrays.copyOfRange(tokens,1,tokens.length)) : "";
182 if (parent.getMapping() == null) {
183 parent.setMapping(new RawEntryMapping(parent.getEntry().getName(), AccessModifier.UNCHANGED));
184 }
185 parent.getMapping().addJavadocLine(MappingHelper.unescape(jdLine));
186 }
187
188 private MappingPair<ClassEntry, RawEntryMapping> parseClass(@Nullable Entry<?> parent, String[] tokens) {
189 String obfuscatedName = ClassEntry.getInnerName(tokens[1]);
190 ClassEntry obfuscatedEntry;
191 if (parent instanceof ClassEntry) {
192 obfuscatedEntry = new ClassEntry((ClassEntry) parent, obfuscatedName);
193 } else {
194 obfuscatedEntry = new ClassEntry(obfuscatedName);
195 }
196
197 String mapping = null;
198 AccessModifier modifier = AccessModifier.UNCHANGED;
199
200 if (tokens.length == 3) {
201 AccessModifier parsedModifier = parseModifier(tokens[2]);
202 if (parsedModifier != null) {
203 modifier = parsedModifier;
204 mapping = obfuscatedName;
205 } else {
206 mapping = tokens[2];
207 }
208 } else if (tokens.length == 4) {
209 mapping = tokens[2];
210 modifier = parseModifier(tokens[3]);
211 }
212
213 if (mapping != null) {
214 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
215 } else {
216 return new MappingPair<>(obfuscatedEntry);
217 }
218 }
219
220 private MappingPair<FieldEntry, RawEntryMapping> parseField(@Nullable Entry<?> parent, String[] tokens) {
221 if (!(parent instanceof ClassEntry)) {
222 throw new RuntimeException("Field must be a child of a class!");
223 }
224
225 ClassEntry ownerEntry = (ClassEntry) parent;
226
227 String obfuscatedName = tokens[1];
228 String mapping = obfuscatedName;
229 AccessModifier modifier = AccessModifier.UNCHANGED;
230 TypeDescriptor descriptor;
231
232 if (tokens.length == 3) {
233 mapping = tokens[1];
234 descriptor = new TypeDescriptor(tokens[2]);
235 } else if (tokens.length == 4) {
236 AccessModifier parsedModifier = parseModifier(tokens[3]);
237 if (parsedModifier != null) {
238 descriptor = new TypeDescriptor(tokens[2]);
239 modifier = parsedModifier;
240 } else {
241 mapping = tokens[2];
242 descriptor = new TypeDescriptor(tokens[3]);
243 }
244 } else if (tokens.length == 5) {
245 descriptor = new TypeDescriptor(tokens[3]);
246 mapping = tokens[2];
247 modifier = parseModifier(tokens[4]);
248 } else {
249 throw new RuntimeException("Invalid field declaration");
250 }
251
252 FieldEntry obfuscatedEntry = new FieldEntry(ownerEntry, obfuscatedName, descriptor);
253 if (mapping != null) {
254 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
255 } else {
256 return new MappingPair<>(obfuscatedEntry);
257 }
258 }
259
260 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(@Nullable Entry<?> parent, String[] tokens) {
261 if (!(parent instanceof ClassEntry)) {
262 throw new RuntimeException("Method must be a child of a class!");
263 }
264
265 ClassEntry ownerEntry = (ClassEntry) parent;
266
267 String obfuscatedName = tokens[1];
268 String mapping = null;
269 AccessModifier modifier = AccessModifier.UNCHANGED;
270 MethodDescriptor descriptor;
271
272 if (tokens.length == 3) {
273 descriptor = new MethodDescriptor(tokens[2]);
274 } else if (tokens.length == 4) {
275 AccessModifier parsedModifier = parseModifier(tokens[3]);
276 if (parsedModifier != null) {
277 modifier = parsedModifier;
278 mapping = obfuscatedName;
279 descriptor = new MethodDescriptor(tokens[2]);
280 } else {
281 mapping = tokens[2];
282 descriptor = new MethodDescriptor(tokens[3]);
283 }
284 } else if (tokens.length == 5) {
285 mapping = tokens[2];
286 modifier = parseModifier(tokens[4]);
287 descriptor = new MethodDescriptor(tokens[3]);
288 } else {
289 throw new RuntimeException("Invalid method declaration");
290 }
291
292 MethodEntry obfuscatedEntry = new MethodEntry(ownerEntry, obfuscatedName, descriptor);
293 if (mapping != null) {
294 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping, modifier));
295 } else {
296 return new MappingPair<>(obfuscatedEntry);
297 }
298 }
299
300 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(@Nullable Entry<?> parent, String[] tokens) {
301 if (!(parent instanceof MethodEntry)) {
302 throw new RuntimeException("Method arg must be a child of a method!");
303 }
304
305 MethodEntry ownerEntry = (MethodEntry) parent;
306 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerEntry, Integer.parseInt(tokens[1]), "", true, null);
307 String mapping = tokens[2];
308
309 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
310 }
311
312 @Nullable
313 private AccessModifier parseModifier(String token) {
314 if (token.startsWith("ACC:")) {
315 return AccessModifier.valueOf(token.substring(4));
316 }
317 return null;
318 }
319}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
deleted file mode 100644
index be0fceb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/EnigmaMappingsWriter.java
+++ /dev/null
@@ -1,316 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 *
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.mapping.serde;
13
14import java.io.IOException;
15import java.io.PrintWriter;
16import java.net.URI;
17import java.net.URISyntaxException;
18import java.nio.file.DirectoryStream;
19import java.nio.file.FileSystem;
20import java.nio.file.FileSystems;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Collections;
27import java.util.Objects;
28import java.util.concurrent.atomic.AtomicInteger;
29import java.util.stream.Collectors;
30import java.util.stream.Stream;
31
32import cuchaz.enigma.ProgressListener;
33import cuchaz.enigma.translation.MappingTranslator;
34import cuchaz.enigma.translation.Translator;
35import cuchaz.enigma.translation.mapping.AccessModifier;
36import cuchaz.enigma.translation.mapping.EntryMapping;
37import cuchaz.enigma.translation.mapping.MappingDelta;
38import cuchaz.enigma.translation.mapping.MappingFileNameFormat;
39import cuchaz.enigma.translation.mapping.MappingSaveParameters;
40import cuchaz.enigma.translation.mapping.VoidEntryResolver;
41import cuchaz.enigma.translation.mapping.tree.EntryTree;
42import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
43import cuchaz.enigma.translation.representation.entry.ClassEntry;
44import cuchaz.enigma.translation.representation.entry.Entry;
45import cuchaz.enigma.translation.representation.entry.FieldEntry;
46import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
47import cuchaz.enigma.translation.representation.entry.MethodEntry;
48import cuchaz.enigma.utils.I18n;
49import cuchaz.enigma.utils.LFPrintWriter;
50
51public enum EnigmaMappingsWriter implements MappingsWriter {
52 FILE {
53 @Override
54 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
55 Collection<ClassEntry> classes = mappings.getRootNodes()
56 .filter(entry -> entry instanceof ClassEntry)
57 .map(entry -> (ClassEntry) entry)
58 .collect(Collectors.toList());
59
60 progress.init(classes.size(), I18n.translate("progress.mappings.enigma_file.writing"));
61
62 int steps = 0;
63 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
64 for (ClassEntry classEntry : classes) {
65 progress.step(steps++, classEntry.getFullName());
66 writeRoot(writer, mappings, classEntry);
67 }
68 } catch (IOException e) {
69 e.printStackTrace();
70 }
71 }
72 },
73 DIRECTORY {
74 @Override
75 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
76 Collection<ClassEntry> changedClasses = delta.getChangedRoots()
77 .filter(entry -> entry instanceof ClassEntry)
78 .map(entry -> (ClassEntry) entry)
79 .collect(Collectors.toList());
80
81 applyDeletions(path, changedClasses, mappings, delta.getBaseMappings(), saveParameters.getFileNameFormat());
82
83 progress.init(changedClasses.size(), I18n.translate("progress.mappings.enigma_directory.writing"));
84
85 AtomicInteger steps = new AtomicInteger();
86
87 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
88 changedClasses.parallelStream().forEach(classEntry -> {
89 progress.step(steps.getAndIncrement(), classEntry.getFullName());
90
91 try {
92 ClassEntry fileEntry = classEntry;
93 if (saveParameters.getFileNameFormat() == MappingFileNameFormat.BY_DEOBF) {
94 fileEntry = translator.translate(fileEntry);
95 }
96
97 Path classPath = resolve(path, fileEntry);
98 Files.createDirectories(classPath.getParent());
99 Files.deleteIfExists(classPath);
100
101 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(classPath))) {
102 writeRoot(writer, mappings, classEntry);
103 }
104 } catch (Throwable t) {
105 System.err.println("Failed to write class '" + classEntry.getFullName() + "'");
106 t.printStackTrace();
107 }
108 });
109 }
110
111 private void applyDeletions(Path root, Collection<ClassEntry> changedClasses, EntryTree<EntryMapping> mappings, EntryTree<EntryMapping> oldMappings, MappingFileNameFormat fileNameFormat) {
112 Translator oldMappingTranslator = new MappingTranslator(oldMappings, VoidEntryResolver.INSTANCE);
113
114 Stream<ClassEntry> deletedClassStream = changedClasses.stream()
115 .filter(e -> !Objects.equals(oldMappings.get(e), mappings.get(e)));
116
117 if (fileNameFormat == MappingFileNameFormat.BY_DEOBF) {
118 deletedClassStream = deletedClassStream.map(oldMappingTranslator::translate);
119 }
120
121 Collection<ClassEntry> deletedClasses = deletedClassStream.collect(Collectors.toList());
122
123 for (ClassEntry classEntry : deletedClasses) {
124 try {
125 Files.deleteIfExists(resolve(root, classEntry));
126 } catch (IOException e) {
127 System.err.println("Failed to delete deleted class '" + classEntry + "'");
128 e.printStackTrace();
129 }
130 }
131
132 for (ClassEntry classEntry : deletedClasses) {
133 String packageName = classEntry.getPackageName();
134 if (packageName != null) {
135 Path packagePath = Paths.get(packageName);
136 try {
137 deleteDeadPackages(root, packagePath);
138 } catch (IOException e) {
139 System.err.println("Failed to delete dead package '" + packageName + "'");
140 e.printStackTrace();
141 }
142 }
143 }
144 }
145
146 private void deleteDeadPackages(Path root, Path packagePath) throws IOException {
147 for (int i = packagePath.getNameCount() - 1; i >= 0; i--) {
148 Path subPath = packagePath.subpath(0, i + 1);
149 Path packagePart = root.resolve(subPath);
150 if (isEmpty(packagePart)) {
151 Files.deleteIfExists(packagePart);
152 }
153 }
154 }
155
156 private boolean isEmpty(Path path) {
157 try (DirectoryStream<Path> stream = Files.newDirectoryStream(path)) {
158 return !stream.iterator().hasNext();
159 } catch (IOException e) {
160 return false;
161 }
162 }
163
164 private Path resolve(Path root, ClassEntry classEntry) {
165 return root.resolve(classEntry.getFullName() + ".mapping");
166 }
167 },
168 ZIP {
169 @Override
170 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path zip, ProgressListener progress, MappingSaveParameters saveParameters) {
171 try (FileSystem fs = FileSystems.newFileSystem(new URI("jar:file", null, zip.toUri().getPath(), ""), Collections.singletonMap("create", "true"))) {
172 DIRECTORY.write(mappings, delta, fs.getPath("/"), progress, saveParameters);
173 } catch (IOException e) {
174 e.printStackTrace();
175 } catch (URISyntaxException e) {
176 throw new RuntimeException("Unexpected error creating URI for " + zip, e);
177 }
178 }
179 };
180
181 protected void writeRoot(PrintWriter writer, EntryTree<EntryMapping> mappings, ClassEntry classEntry) {
182 Collection<Entry<?>> children = groupChildren(mappings.getChildren(classEntry));
183
184 EntryMapping classEntryMapping = mappings.get(classEntry);
185
186 writer.println(writeClass(classEntry, classEntryMapping).trim());
187 if (classEntryMapping != null && classEntryMapping.getJavadoc() != null) {
188 writeDocs(writer, classEntryMapping, 0);
189 }
190
191 for (Entry<?> child : children) {
192 writeEntry(writer, mappings, child, 1);
193 }
194
195 }
196
197 private void writeDocs(PrintWriter writer, EntryMapping mapping, int depth) {
198 String jd = mapping.getJavadoc();
199 if (jd != null) {
200 for (String line : jd.split("\\R")) {
201 writer.println(indent(EnigmaFormat.COMMENT + " " + MappingHelper.escape(line), depth + 1));
202 }
203 }
204 }
205
206 protected void writeEntry(PrintWriter writer, EntryTree<EntryMapping> mappings, Entry<?> entry, int depth) {
207 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
208 if (node == null) {
209 return;
210 }
211
212 EntryMapping mapping = node.getValue();
213
214 if (entry instanceof ClassEntry) {
215 String line = writeClass((ClassEntry) entry, mapping);
216 writer.println(indent(line, depth));
217 } else if (entry instanceof MethodEntry) {
218 String line = writeMethod((MethodEntry) entry, mapping);
219 writer.println(indent(line, depth));
220 } else if (entry instanceof FieldEntry) {
221 String line = writeField((FieldEntry) entry, mapping);
222 writer.println(indent(line, depth));
223 } else if (entry instanceof LocalVariableEntry && mapping != null) {
224 String line = writeArgument((LocalVariableEntry) entry, mapping);
225 writer.println(indent(line, depth));
226 }
227 if (mapping != null && mapping.getJavadoc() != null) {
228 writeDocs(writer, mapping, depth);
229 }
230
231 Collection<Entry<?>> children = groupChildren(node.getChildren());
232 for (Entry<?> child : children) {
233 writeEntry(writer, mappings, child, depth + 1);
234 }
235 }
236
237 private Collection<Entry<?>> groupChildren(Collection<Entry<?>> children) {
238 Collection<Entry<?>> result = new ArrayList<>(children.size());
239
240 children.stream().filter(e -> e instanceof FieldEntry)
241 .map(e -> (FieldEntry) e)
242 .sorted()
243 .forEach(result::add);
244
245 children.stream().filter(e -> e instanceof MethodEntry)
246 .map(e -> (MethodEntry) e)
247 .sorted()
248 .forEach(result::add);
249
250 children.stream().filter(e -> e instanceof LocalVariableEntry)
251 .map(e -> (LocalVariableEntry) e)
252 .sorted()
253 .forEach(result::add);
254
255 children.stream().filter(e -> e instanceof ClassEntry)
256 .map(e -> (ClassEntry) e)
257 .sorted()
258 .forEach(result::add);
259
260 return result;
261 }
262
263 protected String writeClass(ClassEntry entry, EntryMapping mapping) {
264 StringBuilder builder = new StringBuilder(EnigmaFormat.CLASS +" ");
265 builder.append(entry.getName()).append(' ');
266 writeMapping(builder, mapping);
267
268 return builder.toString();
269 }
270
271 protected String writeMethod(MethodEntry entry, EntryMapping mapping) {
272 StringBuilder builder = new StringBuilder(EnigmaFormat.METHOD + " ");
273 builder.append(entry.getName()).append(' ');
274 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
275 writeMapping(builder, mapping);
276 }
277
278 builder.append(entry.getDesc().toString());
279
280 return builder.toString();
281 }
282
283 protected String writeField(FieldEntry entry, EntryMapping mapping) {
284 StringBuilder builder = new StringBuilder(EnigmaFormat.FIELD + " ");
285 builder.append(entry.getName()).append(' ');
286 if (mapping != null && !mapping.getTargetName().equals(entry.getName())) {
287 writeMapping(builder, mapping);
288 }
289
290 builder.append(entry.getDesc().toString());
291
292 return builder.toString();
293 }
294
295 protected String writeArgument(LocalVariableEntry entry, EntryMapping mapping) {
296 return EnigmaFormat.PARAMETER + " " + entry.getIndex() + ' ' + mapping.getTargetName();
297 }
298
299 private void writeMapping(StringBuilder builder, EntryMapping mapping) {
300 if (mapping != null) {
301 builder.append(mapping.getTargetName()).append(' ');
302 if (mapping.getAccessModifier() != AccessModifier.UNCHANGED) {
303 builder.append(mapping.getAccessModifier().getFormattedName()).append(' ');
304 }
305 }
306 }
307
308 private String indent(String line, int depth) {
309 StringBuilder builder = new StringBuilder();
310 for (int i = 0; i < depth; i++) {
311 builder.append("\t");
312 }
313 builder.append(line.trim());
314 return builder.toString();
315 }
316}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
deleted file mode 100644
index 6c8c343..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingFormat.java
+++ /dev/null
@@ -1,59 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingDelta;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9
10import javax.annotation.Nullable;
11import java.io.IOException;
12import java.nio.file.Path;
13
14public enum MappingFormat {
15 ENIGMA_FILE(EnigmaMappingsWriter.FILE, EnigmaMappingsReader.FILE),
16 ENIGMA_DIRECTORY(EnigmaMappingsWriter.DIRECTORY, EnigmaMappingsReader.DIRECTORY),
17 ENIGMA_ZIP(EnigmaMappingsWriter.ZIP, EnigmaMappingsReader.ZIP),
18 TINY_V2(new TinyV2Writer("intermediary", "named"), new TinyV2Reader()),
19 TINY_FILE(TinyMappingsWriter.INSTANCE, TinyMappingsReader.INSTANCE),
20 SRG_FILE(SrgMappingsWriter.INSTANCE, null),
21 PROGUARD(null, ProguardMappingsReader.INSTANCE);
22
23
24 private final MappingsWriter writer;
25 private final MappingsReader reader;
26
27 MappingFormat(MappingsWriter writer, MappingsReader reader) {
28 this.writer = writer;
29 this.reader = reader;
30 }
31
32 public void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
33 write(mappings, MappingDelta.added(mappings), path, progressListener, saveParameters);
34 }
35
36 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) {
37 if (writer == null) {
38 throw new IllegalStateException(name() + " does not support writing");
39 }
40 writer.write(mappings, delta, path, progressListener, saveParameters);
41 }
42
43 public EntryTree<EntryMapping> read(Path path, ProgressListener progressListener, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
44 if (reader == null) {
45 throw new IllegalStateException(name() + " does not support reading");
46 }
47 return reader.read(path, progressListener, saveParameters);
48 }
49
50 @Nullable
51 public MappingsWriter getWriter() {
52 return writer;
53 }
54
55 @Nullable
56 public MappingsReader getReader() {
57 return reader;
58 }
59}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
deleted file mode 100644
index 7c8f6cc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingHelper.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3public final class MappingHelper {
4 private static final String TO_ESCAPE = "\\\n\r\0\t";
5 private static final String ESCAPED = "\\nr0t";
6
7 public static String escape(String raw) {
8 StringBuilder builder = new StringBuilder(raw.length() + 1);
9 for (int i = 0; i < raw.length(); i++) {
10 final char c = raw.charAt(i);
11 final int r = TO_ESCAPE.indexOf(c);
12 if (r < 0) {
13 builder.append(c);
14 } else {
15 builder.append('\\').append(ESCAPED.charAt(r));
16 }
17 }
18 return builder.toString();
19 }
20
21 public static String unescape(String str) {
22 int pos = str.indexOf('\\');
23 if (pos < 0) return str;
24
25 StringBuilder ret = new StringBuilder(str.length() - 1);
26 int start = 0;
27
28 do {
29 ret.append(str, start, pos);
30 pos++;
31 int type;
32
33 if (pos >= str.length()) {
34 throw new RuntimeException("incomplete escape sequence at the end");
35 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
36 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
37 } else {
38 ret.append(TO_ESCAPE.charAt(type));
39 }
40
41 start = pos + 1;
42 } while ((pos = str.indexOf('\\', start)) >= 0);
43
44 ret.append(str, start, str.length());
45
46 return ret.toString();
47 }
48
49 private MappingHelper() {
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
deleted file mode 100644
index 4c60787..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsReader.java
+++ /dev/null
@@ -1,14 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import java.io.IOException;
10import java.nio.file.Path;
11
12public interface MappingsReader {
13 EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException;
14}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
deleted file mode 100644
index 8815986..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/MappingsWriter.java
+++ /dev/null
@@ -1,17 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.MappingDelta;
6import cuchaz.enigma.translation.mapping.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import java.nio.file.Path;
10
11public interface MappingsWriter {
12 void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters);
13
14 default void write(EntryTree<EntryMapping> mappings, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
15 write(mappings, MappingDelta.added(mappings), path, progress, saveParameters);
16 }
17}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java
deleted file mode 100644
index b5ede39..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/ProguardMappingsReader.java
+++ /dev/null
@@ -1,134 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.command.MappingCommandsUtil;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.translation.representation.entry.MethodEntry;
15
16import java.io.IOException;
17import java.nio.charset.StandardCharsets;
18import java.nio.file.Files;
19import java.nio.file.Path;
20import java.util.regex.Matcher;
21import java.util.regex.Pattern;
22
23public class ProguardMappingsReader implements MappingsReader {
24 public static final ProguardMappingsReader INSTANCE = new ProguardMappingsReader();
25 private static final String NAME = "[a-zA-Z0-9_\\-.$<>]+";
26 private static final String TYPE = NAME + "(?:\\[])*";
27 private static final String TYPE_LIST = "|(?:(?:" + TYPE + ",)*" + TYPE + ")";
28 private static final Pattern CLASS = Pattern.compile("(" + NAME + ") -> (" + NAME + "):");
29 private static final Pattern FIELD = Pattern.compile(" {4}(" + TYPE + ") (" + NAME + ") -> (" + NAME + ")");
30 private static final Pattern METHOD = Pattern.compile(" {4}(?:[0-9]+:[0-9]+:)?(" + TYPE + ") (" + NAME + ")\\((" + TYPE_LIST + ")\\) -> (" + NAME + ")");
31
32 public ProguardMappingsReader() {}
33
34 @Override
35 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
36 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
37
38 int lineNumber = 0;
39 ClassEntry currentClass = null;
40 for (String line : Files.readAllLines(path, StandardCharsets.UTF_8)) {
41 lineNumber++;
42
43 if (line.startsWith("#") || line.isEmpty()) {
44 continue;
45 }
46
47 Matcher classMatcher = CLASS.matcher(line);
48 Matcher fieldMatcher = FIELD.matcher(line);
49 Matcher methodMatcher = METHOD.matcher(line);
50
51 if (classMatcher.matches()) {
52 String name = classMatcher.group(1);
53 String targetName = classMatcher.group(2);
54
55 mappings.insert(currentClass = new ClassEntry(name.replace('.', '/')), new EntryMapping(ClassEntry.getInnerName(targetName.replace('.', '/'))));
56 } else if (fieldMatcher.matches()) {
57 String type = fieldMatcher.group(1);
58 String name = fieldMatcher.group(2);
59 String targetName = fieldMatcher.group(3);
60
61 if (currentClass == null) {
62 throw new MappingParseException(path::toString, lineNumber, "field mapping not inside class: " + line);
63 }
64
65 mappings.insert(new FieldEntry(currentClass, name, new TypeDescriptor(getDescriptor(type))), new EntryMapping(targetName));
66 } else if (methodMatcher.matches()) {
67 String returnType = methodMatcher.group(1);
68 String name = methodMatcher.group(2);
69 String[] parameterTypes = methodMatcher.group(3).isEmpty() ? new String[0] : methodMatcher.group(3).split(",");
70 String targetName = methodMatcher.group(4);
71
72 if (currentClass == null) {
73 throw new MappingParseException(path::toString, lineNumber, "method mapping not inside class: " + line);
74 }
75
76 mappings.insert(new MethodEntry(currentClass, name, new MethodDescriptor(getDescriptor(returnType, parameterTypes))), new EntryMapping(targetName));
77 } else {
78 throw new MappingParseException(path::toString, lineNumber, "invalid mapping line: " + line);
79 }
80 }
81
82 return MappingCommandsUtil.invert(mappings);
83 }
84
85 private String getDescriptor(String type) {
86 StringBuilder descriptor = new StringBuilder();
87
88 while (type.endsWith("[]")) {
89 descriptor.append("[");
90 type = type.substring(0, type.length() - 2);
91 }
92
93 switch (type) {
94 case "byte":
95 return descriptor + "B";
96 case "char":
97 return descriptor + "C";
98 case "short":
99 return descriptor + "S";
100 case "int":
101 return descriptor + "I";
102 case "long":
103 return descriptor + "J";
104 case "float":
105 return descriptor + "F";
106 case "double":
107 return descriptor + "D";
108 case "boolean":
109 return descriptor + "Z";
110 case "void":
111 return descriptor + "V";
112 }
113
114 descriptor.append("L");
115 descriptor.append(type.replace('.', '/'));
116 descriptor.append(";");
117
118 return descriptor.toString();
119 }
120
121 private String getDescriptor(String returnType, String[] parameterTypes) {
122 StringBuilder descriptor = new StringBuilder();
123 descriptor.append('(');
124
125 for (String parameterType : parameterTypes) {
126 descriptor.append(getDescriptor(parameterType));
127 }
128
129 descriptor.append(')');
130 descriptor.append(getDescriptor(returnType));
131
132 return descriptor.toString();
133 }
134}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
deleted file mode 100644
index afb40e9..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/RawEntryMapping.java
+++ /dev/null
@@ -1,30 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.translation.mapping.AccessModifier;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5
6import java.util.ArrayList;
7import java.util.List;
8
9final class RawEntryMapping {
10 private final String targetName;
11 private final AccessModifier access;
12 private List<String> javadocs = new ArrayList<>();
13
14 RawEntryMapping(String targetName) {
15 this(targetName, null);
16 }
17
18 RawEntryMapping(String targetName, AccessModifier access) {
19 this.access = access;
20 this.targetName = targetName;
21 }
22
23 void addJavadocLine(String line) {
24 javadocs.add(line);
25 }
26
27 EntryMapping bake() {
28 return new EntryMapping(targetName, access, javadocs.isEmpty() ? null : String.join("\n", javadocs));
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
deleted file mode 100644
index f67f8fc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/SrgMappingsWriter.java
+++ /dev/null
@@ -1,118 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.collect.Lists;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.MappingTranslator;
6import cuchaz.enigma.translation.Translator;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.MappingDelta;
9import cuchaz.enigma.translation.mapping.MappingSaveParameters;
10import cuchaz.enigma.translation.mapping.VoidEntryResolver;
11import cuchaz.enigma.translation.mapping.tree.EntryTree;
12import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.Entry;
15import cuchaz.enigma.translation.representation.entry.FieldEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18import cuchaz.enigma.utils.LFPrintWriter;
19
20import java.io.IOException;
21import java.io.PrintWriter;
22import java.nio.file.Files;
23import java.nio.file.Path;
24import java.util.ArrayList;
25import java.util.Collection;
26import java.util.Comparator;
27import java.util.List;
28import java.util.stream.Collectors;
29
30public enum SrgMappingsWriter implements MappingsWriter {
31 INSTANCE;
32
33 @Override
34 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
35 try {
36 Files.deleteIfExists(path);
37 Files.createFile(path);
38 } catch (IOException e) {
39 e.printStackTrace();
40 }
41
42 List<String> classLines = new ArrayList<>();
43 List<String> fieldLines = new ArrayList<>();
44 List<String> methodLines = new ArrayList<>();
45
46 Collection<Entry<?>> rootEntries = Lists.newArrayList(mappings).stream()
47 .map(EntryTreeNode::getEntry)
48 .collect(Collectors.toList());
49 progress.init(rootEntries.size(), I18n.translate("progress.mappings.srg_file.generating"));
50
51 int steps = 0;
52 for (Entry<?> entry : sorted(rootEntries)) {
53 progress.step(steps++, entry.getName());
54 writeEntry(classLines, fieldLines, methodLines, mappings, entry);
55 }
56
57 progress.init(3, I18n.translate("progress.mappings.srg_file.writing"));
58 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
59 progress.step(0, I18n.translate("type.classes"));
60 classLines.forEach(writer::println);
61 progress.step(1, I18n.translate("type.fields"));
62 fieldLines.forEach(writer::println);
63 progress.step(2, I18n.translate("type.methods"));
64 methodLines.forEach(writer::println);
65 } catch (IOException e) {
66 e.printStackTrace();
67 }
68 }
69
70 private void writeEntry(List<String> classes, List<String> fields, List<String> methods, EntryTree<EntryMapping> mappings, Entry<?> entry) {
71 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
72 if (node == null) {
73 return;
74 }
75
76 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
77 if (entry instanceof ClassEntry) {
78 classes.add(generateClassLine((ClassEntry) entry, translator));
79 } else if (entry instanceof FieldEntry) {
80 fields.add(generateFieldLine((FieldEntry) entry, translator));
81 } else if (entry instanceof MethodEntry) {
82 methods.add(generateMethodLine((MethodEntry) entry, translator));
83 }
84
85 for (Entry<?> child : sorted(node.getChildren())) {
86 writeEntry(classes, fields, methods, mappings, child);
87 }
88 }
89
90 private String generateClassLine(ClassEntry sourceEntry, Translator translator) {
91 ClassEntry targetEntry = translator.translate(sourceEntry);
92 return "CL: " + sourceEntry.getFullName() + " " + targetEntry.getFullName();
93 }
94
95 private String generateMethodLine(MethodEntry sourceEntry, Translator translator) {
96 MethodEntry targetEntry = translator.translate(sourceEntry);
97 return "MD: " + describeMethod(sourceEntry) + " " + describeMethod(targetEntry);
98 }
99
100 private String describeMethod(MethodEntry entry) {
101 return entry.getParent().getFullName() + "/" + entry.getName() + " " + entry.getDesc();
102 }
103
104 private String generateFieldLine(FieldEntry sourceEntry, Translator translator) {
105 FieldEntry targetEntry = translator.translate(sourceEntry);
106 return "FD: " + describeField(sourceEntry) + " " + describeField(targetEntry);
107 }
108
109 private String describeField(FieldEntry entry) {
110 return entry.getParent().getFullName() + "/" + entry.getName();
111 }
112
113 private Collection<Entry<?>> sorted(Iterable<Entry<?>> iterable) {
114 ArrayList<Entry<?>> sorted = Lists.newArrayList(iterable);
115 sorted.sort(Comparator.comparing(Entry::getName));
116 return sorted;
117 }
118}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
deleted file mode 100644
index 773c95e..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsReader.java
+++ /dev/null
@@ -1,115 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Charsets;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.throwables.MappingParseException;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingPair;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
11import cuchaz.enigma.translation.representation.MethodDescriptor;
12import cuchaz.enigma.translation.representation.TypeDescriptor;
13import cuchaz.enigma.translation.representation.entry.ClassEntry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18
19import java.io.IOException;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.List;
23
24public enum TinyMappingsReader implements MappingsReader {
25 INSTANCE;
26
27 @Override
28 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
29 return read(path, Files.readAllLines(path, Charsets.UTF_8), progress);
30 }
31
32 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
33 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
34 lines.remove(0);
35
36 progress.init(lines.size(), I18n.translate("progress.mappings.tiny_file.loading"));
37
38 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
39 progress.step(lineNumber, "");
40
41 String line = lines.get(lineNumber);
42
43 if (line.trim().startsWith("#")) {
44 continue;
45 }
46
47 try {
48 MappingPair<?, EntryMapping> mapping = parseLine(line);
49 mappings.insert(mapping.getEntry(), mapping.getMapping());
50 } catch (Throwable t) {
51 t.printStackTrace();
52 throw new MappingParseException(path::toString, lineNumber, t.toString());
53 }
54 }
55
56 return mappings;
57 }
58
59 private MappingPair<?, EntryMapping> parseLine(String line) {
60 String[] tokens = line.split("\t");
61
62 String key = tokens[0];
63 switch (key) {
64 case "CLASS":
65 return parseClass(tokens);
66 case "FIELD":
67 return parseField(tokens);
68 case "METHOD":
69 return parseMethod(tokens);
70 case "MTH-ARG":
71 return parseArgument(tokens);
72 default:
73 throw new RuntimeException("Unknown token '" + key + "'!");
74 }
75 }
76
77 private MappingPair<ClassEntry, EntryMapping> parseClass(String[] tokens) {
78 ClassEntry obfuscatedEntry = new ClassEntry(tokens[1]);
79 String mapping = tokens[2];
80 if (mapping.indexOf('$') > 0) {
81 // inner classes should map to only the final part
82 mapping = mapping.substring(mapping.lastIndexOf('$') + 1);
83 }
84 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
85 }
86
87 private MappingPair<FieldEntry, EntryMapping> parseField(String[] tokens) {
88 ClassEntry ownerClass = new ClassEntry(tokens[1]);
89 TypeDescriptor descriptor = new TypeDescriptor(tokens[2]);
90
91 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, tokens[3], descriptor);
92 String mapping = tokens[4];
93 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
94 }
95
96 private MappingPair<MethodEntry, EntryMapping> parseMethod(String[] tokens) {
97 ClassEntry ownerClass = new ClassEntry(tokens[1]);
98 MethodDescriptor descriptor = new MethodDescriptor(tokens[2]);
99
100 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, tokens[3], descriptor);
101 String mapping = tokens[4];
102 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
103 }
104
105 private MappingPair<LocalVariableEntry, EntryMapping> parseArgument(String[] tokens) {
106 ClassEntry ownerClass = new ClassEntry(tokens[1]);
107 MethodDescriptor ownerDescriptor = new MethodDescriptor(tokens[2]);
108 MethodEntry ownerMethod = new MethodEntry(ownerClass, tokens[3], ownerDescriptor);
109 int variableIndex = Integer.parseInt(tokens[4]);
110
111 String mapping = tokens[5];
112 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
113 return new MappingPair<>(obfuscatedEntry, new EntryMapping(mapping));
114 }
115}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java
deleted file mode 100644
index c82f262..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyMappingsWriter.java
+++ /dev/null
@@ -1,148 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Joiner;
4import com.google.common.collect.Lists;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.translation.MappingTranslator;
7import cuchaz.enigma.translation.Translator;
8import cuchaz.enigma.translation.mapping.EntryMapping;
9import cuchaz.enigma.translation.mapping.MappingDelta;
10import cuchaz.enigma.translation.mapping.MappingSaveParameters;
11import cuchaz.enigma.translation.mapping.VoidEntryResolver;
12import cuchaz.enigma.translation.mapping.tree.EntryTree;
13import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
14import cuchaz.enigma.translation.representation.entry.ClassEntry;
15import cuchaz.enigma.translation.representation.entry.Entry;
16import cuchaz.enigma.translation.representation.entry.FieldEntry;
17import cuchaz.enigma.translation.representation.entry.MethodEntry;
18
19import java.io.BufferedWriter;
20import java.io.IOException;
21import java.io.Writer;
22import java.nio.charset.StandardCharsets;
23import java.nio.file.Files;
24import java.nio.file.Path;
25import java.util.Comparator;
26import java.util.HashSet;
27import java.util.Set;
28
29public class TinyMappingsWriter implements MappingsWriter {
30 private static final String VERSION_CONSTANT = "v1";
31 private static final Joiner TAB_JOINER = Joiner.on('\t');
32
33 //Possibly add a gui or a way to select the namespaces when exporting from the gui
34 public static final TinyMappingsWriter INSTANCE = new TinyMappingsWriter("intermediary", "named");
35
36 // HACK: as of enigma 0.13.1, some fields seem to appear duplicated?
37 private final Set<String> writtenLines = new HashSet<>();
38 private final String nameObf;
39 private final String nameDeobf;
40
41 public TinyMappingsWriter(String nameObf, String nameDeobf) {
42 this.nameObf = nameObf;
43 this.nameDeobf = nameDeobf;
44 }
45
46 @Override
47 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters saveParameters) {
48 try {
49 Files.deleteIfExists(path);
50 Files.createFile(path);
51 } catch (IOException e) {
52 e.printStackTrace();
53 }
54
55 try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
56 writeLine(writer, new String[]{VERSION_CONSTANT, nameObf, nameDeobf});
57
58 Lists.newArrayList(mappings).stream()
59 .map(EntryTreeNode::getEntry).sorted(Comparator.comparing(Object::toString))
60 .forEach(entry -> writeEntry(writer, mappings, entry));
61 } catch (IOException e) {
62 e.printStackTrace();
63 }
64 }
65
66 private void writeEntry(Writer writer, EntryTree<EntryMapping> mappings, Entry<?> entry) {
67 EntryTreeNode<EntryMapping> node = mappings.findNode(entry);
68 if (node == null) {
69 return;
70 }
71
72 Translator translator = new MappingTranslator(mappings, VoidEntryResolver.INSTANCE);
73
74 EntryMapping mapping = mappings.get(entry);
75 if (mapping != null && !entry.getName().equals(mapping.getTargetName())) {
76 if (entry instanceof ClassEntry) {
77 writeClass(writer, (ClassEntry) entry, translator);
78 } else if (entry instanceof FieldEntry) {
79 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
80 } else if (entry instanceof MethodEntry) {
81 writeLine(writer, serializeEntry(entry, mapping.getTargetName()));
82 }
83 }
84
85 writeChildren(writer, mappings, node);
86 }
87
88 private void writeChildren(Writer writer, EntryTree<EntryMapping> mappings, EntryTreeNode<EntryMapping> node) {
89 node.getChildren().stream()
90 .filter(e -> e instanceof FieldEntry).sorted()
91 .forEach(child -> writeEntry(writer, mappings, child));
92
93 node.getChildren().stream()
94 .filter(e -> e instanceof MethodEntry).sorted()
95 .forEach(child -> writeEntry(writer, mappings, child));
96
97 node.getChildren().stream()
98 .filter(e -> e instanceof ClassEntry).sorted()
99 .forEach(child -> writeEntry(writer, mappings, child));
100 }
101
102 private void writeClass(Writer writer, ClassEntry entry, Translator translator) {
103 ClassEntry translatedEntry = translator.translate(entry);
104
105 String obfClassName = entry.getFullName();
106 String deobfClassName = translatedEntry.getFullName();
107 writeLine(writer, new String[]{"CLASS", obfClassName, deobfClassName});
108 }
109
110 private void writeLine(Writer writer, String[] data) {
111 try {
112 String line = TAB_JOINER.join(data) + "\n";
113 if (writtenLines.add(line)) {
114 writer.write(line);
115 }
116 } catch (IOException e) {
117 throw new RuntimeException(e);
118 }
119 }
120
121 private String[] serializeEntry(Entry<?> entry, String... extraFields) {
122 String[] data = null;
123
124 if (entry instanceof FieldEntry) {
125 data = new String[4 + extraFields.length];
126 data[0] = "FIELD";
127 data[1] = entry.getContainingClass().getFullName();
128 data[2] = ((FieldEntry) entry).getDesc().toString();
129 data[3] = entry.getName();
130 } else if (entry instanceof MethodEntry) {
131 data = new String[4 + extraFields.length];
132 data[0] = "METHOD";
133 data[1] = entry.getContainingClass().getFullName();
134 data[2] = ((MethodEntry) entry).getDesc().toString();
135 data[3] = entry.getName();
136 } else if (entry instanceof ClassEntry) {
137 data = new String[2 + extraFields.length];
138 data[0] = "CLASS";
139 data[1] = ((ClassEntry) entry).getFullName();
140 }
141
142 if (data != null) {
143 System.arraycopy(extraFields, 0, data, data.length - extraFields.length, extraFields.length);
144 }
145
146 return data;
147 }
148}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java
deleted file mode 100644
index d81cbdb..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Reader.java
+++ /dev/null
@@ -1,295 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.throwables.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.MappingPair;
7import cuchaz.enigma.translation.mapping.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
10import cuchaz.enigma.translation.representation.MethodDescriptor;
11import cuchaz.enigma.translation.representation.TypeDescriptor;
12import cuchaz.enigma.translation.representation.entry.ClassEntry;
13import cuchaz.enigma.translation.representation.entry.Entry;
14import cuchaz.enigma.translation.representation.entry.FieldEntry;
15import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.I18n;
18
19import java.io.IOException;
20import java.nio.charset.StandardCharsets;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.util.BitSet;
24import java.util.List;
25
26final class TinyV2Reader implements MappingsReader {
27
28 private static final String MINOR_VERSION = "0";
29 // 0 indent
30 private static final int IN_HEADER = 0;
31 private static final int IN_CLASS = IN_HEADER + 1;
32 // 1 indent
33 private static final int IN_METHOD = IN_CLASS + 1;
34 private static final int IN_FIELD = IN_METHOD + 1;
35 // 2 indent
36 private static final int IN_PARAMETER = IN_FIELD + 1;
37 // general properties
38 private static final int STATE_SIZE = IN_PARAMETER + 1;
39 private static final int[] INDENT_CLEAR_START = {IN_HEADER, IN_METHOD, IN_PARAMETER, STATE_SIZE};
40
41 @Override
42 public EntryTree<EntryMapping> read(Path path, ProgressListener progress, MappingSaveParameters saveParameters) throws IOException, MappingParseException {
43 return read(path, Files.readAllLines(path, StandardCharsets.UTF_8), progress);
44 }
45
46 private EntryTree<EntryMapping> read(Path path, List<String> lines, ProgressListener progress) throws MappingParseException {
47 EntryTree<EntryMapping> mappings = new HashEntryTree<>();
48
49 progress.init(lines.size(), I18n.translate("progress.mappings.tiny_v2.loading"));
50
51 BitSet state = new BitSet(STATE_SIZE);
52 @SuppressWarnings({"unchecked", "rawtypes"})
53 MappingPair<? extends Entry<?>, RawEntryMapping>[] holds = new MappingPair[STATE_SIZE];
54 boolean escapeNames = false;
55
56 for (int lineNumber = 0; lineNumber < lines.size(); lineNumber++) {
57 try {
58 progress.step(lineNumber, "");
59 String line = lines.get(lineNumber);
60
61 int indent = 0;
62 while (line.charAt(indent) == '\t')
63 indent++;
64
65 String[] parts = line.substring(indent).split("\t", -1);
66 if (parts.length == 0 || indent >= INDENT_CLEAR_START.length)
67 throw new IllegalArgumentException("Invalid format");
68
69 // clean and register stuff in stack
70 for (int i = INDENT_CLEAR_START[indent]; i < STATE_SIZE; i++) {
71 state.clear(i);
72 if (holds[i] != null) {
73 RawEntryMapping mapping = holds[i].getMapping();
74 if (mapping != null) {
75 EntryMapping baked = mapping.bake();
76 if (baked != null) {
77 mappings.insert(holds[i].getEntry(), baked);
78 }
79 }
80 holds[i] = null;
81 }
82 }
83
84 switch (indent) {
85 case 0:
86 switch (parts[0]) {
87 case "tiny": // header
88 if (lineNumber != 0) {
89 throw new IllegalArgumentException("Header can only be on the first line");
90 }
91 if (parts.length < 5) {
92 throw new IllegalArgumentException("Not enough header columns, needs at least 5");
93 }
94 if (!"2".equals(parts[1]) || !MINOR_VERSION.equals(parts[2])) {
95 throw new IllegalArgumentException("Unsupported TinyV2 version, requires major " + "2" + " and minor " + MINOR_VERSION + "");
96 }
97 state.set(IN_HEADER);
98 break;
99 case "c": // class
100 state.set(IN_CLASS);
101 holds[IN_CLASS] = parseClass(parts, escapeNames);
102 break;
103 default:
104 unsupportKey(parts);
105 }
106
107 break;
108 case 1:
109 if (state.get(IN_HEADER)) {
110 if (parts[0].equals("esacpe-names")) {
111 escapeNames = true;
112 }
113
114 break;
115 }
116
117 if (state.get(IN_CLASS)) {
118 switch (parts[0]) {
119 case "m": // method
120 state.set(IN_METHOD);
121 holds[IN_METHOD] = parseMethod(holds[IN_CLASS], parts, escapeNames);
122 break;
123 case "f": // field
124 state.set(IN_FIELD);
125 holds[IN_FIELD] = parseField(holds[IN_CLASS], parts, escapeNames);
126 break;
127 case "c": // class javadoc
128 addJavadoc(holds[IN_CLASS], parts);
129 break;
130 default:
131 unsupportKey(parts);
132 }
133 break;
134 }
135
136 unsupportKey(parts);
137 case 2:
138 if (state.get(IN_METHOD)) {
139 switch (parts[0]) {
140 case "p": // parameter
141 state.set(IN_PARAMETER);
142 holds[IN_PARAMETER] = parseArgument(holds[IN_METHOD], parts, escapeNames);
143 break;
144 case "v": // local variable
145 // TODO add local var mapping
146 break;
147 case "c": // method javadoc
148 addJavadoc(holds[IN_METHOD], parts);
149 break;
150 default:
151 unsupportKey(parts);
152 }
153 break;
154 }
155
156 if (state.get(IN_FIELD)) {
157 switch (parts[0]) {
158 case "c": // field javadoc
159 addJavadoc(holds[IN_FIELD], parts);
160 break;
161 default:
162 unsupportKey(parts);
163 }
164 break;
165 }
166 unsupportKey(parts);
167 case 3:
168 if (state.get(IN_PARAMETER)) {
169 switch (parts[0]) {
170 case "c":
171 addJavadoc(holds[IN_PARAMETER], parts);
172 break;
173 default:
174 unsupportKey(parts);
175 }
176 break;
177 }
178 unsupportKey(parts);
179 default:
180 unsupportKey(parts);
181 }
182
183 } catch (Throwable t) {
184 t.printStackTrace();
185 throw new MappingParseException(path::toString, lineNumber + 1, t.toString());
186 }
187 }
188
189 return mappings;
190 }
191
192 private void unsupportKey(String[] parts) {
193 throw new IllegalArgumentException("Unsupported key " + parts[0]);
194 }
195
196 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String[] parts) {
197 if (parts.length != 2) {
198 throw new IllegalArgumentException("Invalid javadoc declaration");
199 }
200
201 addJavadoc(pair, parts[1]);
202 }
203
204 private MappingPair<ClassEntry, RawEntryMapping> parseClass(String[] tokens, boolean escapeNames) {
205 ClassEntry obfuscatedEntry = new ClassEntry(unescapeOpt(tokens[1], escapeNames));
206 if (tokens.length <= 2)
207 return new MappingPair<>(obfuscatedEntry);
208 String token2 = unescapeOpt(tokens[2], escapeNames);
209 String mapping = token2.substring(token2.lastIndexOf('$') + 1);
210 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
211 }
212
213 private MappingPair<FieldEntry, RawEntryMapping> parseField(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
214 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
215 TypeDescriptor descriptor = new TypeDescriptor(unescapeOpt(tokens[1], escapeNames));
216
217 FieldEntry obfuscatedEntry = new FieldEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
218 if (tokens.length <= 3)
219 return new MappingPair<>(obfuscatedEntry);
220 String mapping = unescapeOpt(tokens[3], escapeNames);
221 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
222 }
223
224 private MappingPair<MethodEntry, RawEntryMapping> parseMethod(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
225 ClassEntry ownerClass = (ClassEntry) parent.getEntry();
226 MethodDescriptor descriptor = new MethodDescriptor(unescapeOpt(tokens[1], escapeNames));
227
228 MethodEntry obfuscatedEntry = new MethodEntry(ownerClass, unescapeOpt(tokens[2], escapeNames), descriptor);
229 if (tokens.length <= 3)
230 return new MappingPair<>(obfuscatedEntry);
231 String mapping = unescapeOpt(tokens[3], escapeNames);
232 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
233 }
234
235
236
237 private void addJavadoc(MappingPair<? extends Entry, RawEntryMapping> pair, String javadoc) {
238 RawEntryMapping mapping = pair.getMapping();
239 if (mapping == null) {
240 throw new IllegalArgumentException("Javadoc requires a mapping in enigma!");
241 }
242 mapping.addJavadocLine(unescape(javadoc));
243 }
244
245
246
247 private MappingPair<LocalVariableEntry, RawEntryMapping> parseArgument(MappingPair<? extends Entry, RawEntryMapping> parent, String[] tokens, boolean escapeNames) {
248 MethodEntry ownerMethod = (MethodEntry) parent.getEntry();
249 int variableIndex = Integer.parseInt(tokens[1]);
250
251 // tokens[2] is the useless obf name
252
253 LocalVariableEntry obfuscatedEntry = new LocalVariableEntry(ownerMethod, variableIndex, "", true, null);
254 if (tokens.length <= 3)
255 return new MappingPair<>(obfuscatedEntry);
256 String mapping = unescapeOpt(tokens[3], escapeNames);
257 return new MappingPair<>(obfuscatedEntry, new RawEntryMapping(mapping));
258 }
259
260 private static final String TO_ESCAPE = "\\\n\r\0\t";
261 private static final String ESCAPED = "\\nr0t";
262
263 private static String unescapeOpt(String raw, boolean escapedStrings) {
264 return escapedStrings ? unescape(raw) : raw;
265 }
266
267 private static String unescape(String str) {
268 // copied from matcher, lazy!
269 int pos = str.indexOf('\\');
270 if (pos < 0) return str;
271
272 StringBuilder ret = new StringBuilder(str.length() - 1);
273 int start = 0;
274
275 do {
276 ret.append(str, start, pos);
277 pos++;
278 int type;
279
280 if (pos >= str.length()) {
281 throw new RuntimeException("incomplete escape sequence at the end");
282 } else if ((type = ESCAPED.indexOf(str.charAt(pos))) < 0) {
283 throw new RuntimeException("invalid escape character: \\" + str.charAt(pos));
284 } else {
285 ret.append(TO_ESCAPE.charAt(type));
286 }
287
288 start = pos + 1;
289 } while ((pos = str.indexOf('\\', start)) >= 0);
290
291 ret.append(str, start, str.length());
292
293 return ret.toString();
294 }
295}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java b/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java
deleted file mode 100644
index 95e04c3..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/serde/TinyV2Writer.java
+++ /dev/null
@@ -1,169 +0,0 @@
1package cuchaz.enigma.translation.mapping.serde;
2
3import com.google.common.base.Strings;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.mapping.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12import cuchaz.enigma.translation.representation.entry.Entry;
13import cuchaz.enigma.translation.representation.entry.FieldEntry;
14import cuchaz.enigma.translation.representation.entry.LocalVariableEntry;
15import cuchaz.enigma.translation.representation.entry.MethodEntry;
16import cuchaz.enigma.utils.LFPrintWriter;
17
18import java.io.IOException;
19import java.io.PrintWriter;
20import java.nio.file.Files;
21import java.nio.file.Path;
22import java.util.Deque;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.stream.Collectors;
26import java.util.stream.StreamSupport;
27
28public final class TinyV2Writer implements MappingsWriter {
29
30 private static final String MINOR_VERSION = "0";
31 private final String obfHeader;
32 private final String deobfHeader;
33
34 public TinyV2Writer(String obfHeader, String deobfHeader) {
35 this.obfHeader = obfHeader;
36 this.deobfHeader = deobfHeader;
37 }
38
39 @Override
40 public void write(EntryTree<EntryMapping> mappings, MappingDelta<EntryMapping> delta, Path path, ProgressListener progress, MappingSaveParameters parameters) {
41 List<EntryTreeNode<EntryMapping>> classes = StreamSupport.stream(mappings.spliterator(), false).filter(node -> node.getEntry() instanceof ClassEntry).collect(Collectors.toList());
42
43 try (PrintWriter writer = new LFPrintWriter(Files.newBufferedWriter(path))) {
44 writer.println("tiny\t2\t" + MINOR_VERSION + "\t" + obfHeader + "\t" + deobfHeader);
45
46 // no escape names
47
48 for (EntryTreeNode<EntryMapping> node : classes) {
49 writeClass(writer, node, mappings);
50 }
51 } catch (IOException ex) {
52 ex.printStackTrace(); // TODO add some better logging system
53 }
54 }
55
56 private void writeClass(PrintWriter writer, EntryTreeNode<EntryMapping> node, EntryMap<EntryMapping> tree) {
57 writer.print("c\t");
58 ClassEntry classEntry = (ClassEntry) node.getEntry();
59 String fullName = classEntry.getFullName();
60 writer.print(fullName);
61 Deque<String> parts = new LinkedList<>();
62 do {
63 EntryMapping mapping = tree.get(classEntry);
64 if (mapping != null) {
65 parts.addFirst(mapping.getTargetName());
66 } else {
67 parts.addFirst(classEntry.getName());
68 }
69 classEntry = classEntry.getOuterClass();
70 } while (classEntry != null);
71
72 String mappedName = String.join("$", parts);
73
74 writer.print("\t");
75
76 writer.print(mappedName); // todo escaping when we have v2 fixed later
77
78 writer.println();
79
80 writeComment(writer, node.getValue(), 1);
81
82 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
83 Entry entry = child.getEntry();
84 if (entry instanceof FieldEntry) {
85 writeField(writer, child);
86 } else if (entry instanceof MethodEntry) {
87 writeMethod(writer, child);
88 }
89 }
90 }
91
92 private void writeMethod(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
93 writer.print(indent(1));
94 writer.print("m\t");
95 writer.print(((MethodEntry) node.getEntry()).getDesc().toString());
96 writer.print("\t");
97 writer.print(node.getEntry().getName());
98 writer.print("\t");
99 EntryMapping mapping = node.getValue();
100 if (mapping == null) {
101 writer.println(node.getEntry().getName()); // todo fix v2 name inference
102 } else {
103 writer.println(mapping.getTargetName());
104
105 writeComment(writer, mapping, 2);
106 }
107
108 for (EntryTreeNode<EntryMapping> child : node.getChildNodes()) {
109 Entry entry = child.getEntry();
110 if (entry instanceof LocalVariableEntry) {
111 writeParameter(writer, child);
112 }
113 // TODO write actual local variables
114 }
115 }
116
117 private void writeField(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
118 if (node.getValue() == null)
119 return; // Shortcut
120
121 writer.print(indent(1));
122 writer.print("f\t");
123 writer.print(((FieldEntry) node.getEntry()).getDesc().toString());
124 writer.print("\t");
125 writer.print(node.getEntry().getName());
126 writer.print("\t");
127 EntryMapping mapping = node.getValue();
128 if (mapping == null) {
129 writer.println(node.getEntry().getName()); // todo fix v2 name inference
130 } else {
131 writer.println(mapping.getTargetName());
132
133 writeComment(writer, mapping, 2);
134 }
135 }
136
137 private void writeParameter(PrintWriter writer, EntryTreeNode<EntryMapping> node) {
138 if (node.getValue() == null)
139 return; // Shortcut
140
141 writer.print(indent(2));
142 writer.print("p\t");
143 writer.print(((LocalVariableEntry) node.getEntry()).getIndex());
144 writer.print("\t");
145 writer.print(node.getEntry().getName());
146 writer.print("\t");
147 EntryMapping mapping = node.getValue();
148 if (mapping == null) {
149 writer.println(); // todo ???
150 } else {
151 writer.println(mapping.getTargetName());
152
153 writeComment(writer, mapping, 3);
154 }
155 }
156
157 private void writeComment(PrintWriter writer, EntryMapping mapping, int indent) {
158 if (mapping != null && mapping.getJavadoc() != null) {
159 writer.print(indent(indent));
160 writer.print("c\t");
161 writer.print(MappingHelper.escape(mapping.getJavadoc()));
162 writer.println();
163 }
164 }
165
166 private String indent(int level) {
167 return Strings.repeat("\t", level);
168 }
169}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
deleted file mode 100644
index 255fa5f..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/DeltaTrackingTree.java
+++ /dev/null
@@ -1,110 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.mapping.MappingDelta;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.Iterator;
13import java.util.stream.Stream;
14
15public class DeltaTrackingTree<T> implements EntryTree<T> {
16 private final EntryTree<T> delegate;
17
18 private EntryTree<T> deltaReference;
19 private EntryTree<Object> changes = new HashEntryTree<>();
20
21 public DeltaTrackingTree(EntryTree<T> delegate) {
22 this.delegate = delegate;
23 this.deltaReference = new HashEntryTree<>(delegate);
24 }
25
26 public DeltaTrackingTree() {
27 this(new HashEntryTree<>());
28 }
29
30 @Override
31 public void insert(Entry<?> entry, T value) {
32 trackChange(entry);
33 delegate.insert(entry, value);
34 }
35
36 @Nullable
37 @Override
38 public T remove(Entry<?> entry) {
39 trackChange(entry);
40 return delegate.remove(entry);
41 }
42
43 public void trackChange(Entry<?> entry) {
44 changes.insert(entry, MappingDelta.PLACEHOLDER);
45 }
46
47 @Nullable
48 @Override
49 public T get(Entry<?> entry) {
50 return delegate.get(entry);
51 }
52
53 @Override
54 public Collection<Entry<?>> getChildren(Entry<?> entry) {
55 return delegate.getChildren(entry);
56 }
57
58 @Override
59 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
60 return delegate.getSiblings(entry);
61 }
62
63 @Nullable
64 @Override
65 public EntryTreeNode<T> findNode(Entry<?> entry) {
66 return delegate.findNode(entry);
67 }
68
69 @Override
70 public Stream<EntryTreeNode<T>> getRootNodes() {
71 return delegate.getRootNodes();
72 }
73
74 @Override
75 public DeltaTrackingTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
76 DeltaTrackingTree<T> translatedTree = new DeltaTrackingTree<>(delegate.translate(translator, resolver, mappings));
77 translatedTree.changes = changes.translate(translator, resolver, mappings);
78 return translatedTree;
79 }
80
81 @Override
82 public Stream<Entry<?>> getAllEntries() {
83 return delegate.getAllEntries();
84 }
85
86 @Override
87 public boolean isEmpty() {
88 return delegate.isEmpty();
89 }
90
91 @Override
92 public Iterator<EntryTreeNode<T>> iterator() {
93 return delegate.iterator();
94 }
95
96 public MappingDelta<T> takeDelta() {
97 MappingDelta<T> delta = new MappingDelta<>(deltaReference, changes);
98 resetDelta();
99 return delta;
100 }
101
102 private void resetDelta() {
103 deltaReference = new HashEntryTree<>(delegate);
104 changes = new HashEntryTree<>();
105 }
106
107 public boolean isDirty() {
108 return !changes.isEmpty();
109 }
110}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
deleted file mode 100644
index daaefcc..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTree.java
+++ /dev/null
@@ -1,26 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.representation.entry.Entry;
9
10import javax.annotation.Nullable;
11import java.util.Collection;
12import java.util.stream.Stream;
13
14public interface EntryTree<T> extends EntryMap<T>, Iterable<EntryTreeNode<T>>, Translatable {
15 Collection<Entry<?>> getChildren(Entry<?> entry);
16
17 Collection<Entry<?>> getSiblings(Entry<?> entry);
18
19 @Nullable
20 EntryTreeNode<T> findNode(Entry<?> entry);
21
22 Stream<EntryTreeNode<T>> getRootNodes();
23
24 @Override
25 EntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings);
26}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
deleted file mode 100644
index affcd50..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/EntryTreeNode.java
+++ /dev/null
@@ -1,40 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nullable;
6import java.util.ArrayList;
7import java.util.Collection;
8import java.util.stream.Collectors;
9
10public interface EntryTreeNode<T> {
11 @Nullable
12 T getValue();
13
14 Entry<?> getEntry();
15
16 boolean isEmpty();
17
18 Collection<Entry<?>> getChildren();
19
20 Collection<? extends EntryTreeNode<T>> getChildNodes();
21
22 default Collection<? extends EntryTreeNode<T>> getNodesRecursively() {
23 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
24 nodes.add(this);
25 for (EntryTreeNode<T> node : getChildNodes()) {
26 nodes.addAll(node.getNodesRecursively());
27 }
28 return nodes;
29 }
30
31 default Collection<Entry<?>> getChildrenRecursively() {
32 return getNodesRecursively().stream()
33 .map(EntryTreeNode::getEntry)
34 .collect(Collectors.toList());
35 }
36
37 default boolean hasValue() {
38 return getValue() != null;
39 }
40}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
deleted file mode 100644
index 570941c..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashEntryTree.java
+++ /dev/null
@@ -1,188 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.Translator;
4import cuchaz.enigma.translation.mapping.EntryMap;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.EntryResolver;
7import cuchaz.enigma.translation.representation.entry.Entry;
8
9import javax.annotation.Nullable;
10import java.util.*;
11import java.util.function.Function;
12import java.util.stream.Stream;
13import java.util.stream.StreamSupport;
14
15public class HashEntryTree<T> implements EntryTree<T> {
16 private final Map<Entry<?>, HashTreeNode<T>> root = new HashMap<>();
17
18 public HashEntryTree() {
19 }
20
21 public HashEntryTree(EntryTree<T> tree) {
22 for (EntryTreeNode<T> node : tree) {
23 insert(node.getEntry(), node.getValue());
24 }
25 }
26
27 @Override
28 public void insert(Entry<?> entry, T value) {
29 List<HashTreeNode<T>> path = computePath(entry, true);
30 path.get(path.size() - 1).putValue(value);
31 if (value == null) {
32 removeDeadAlong(path);
33 }
34 }
35
36 @Override
37 @Nullable
38 public T remove(Entry<?> entry) {
39 List<HashTreeNode<T>> path = computePath(entry, false);
40 if (path.isEmpty()) {
41 return null;
42 }
43
44 T value = path.get(path.size() - 1).removeValue();
45
46 removeDeadAlong(path);
47
48 return value;
49 }
50
51 @Override
52 @Nullable
53 public T get(Entry<?> entry) {
54 HashTreeNode<T> node = findNode(entry);
55 if (node == null) {
56 return null;
57 }
58 return node.getValue();
59 }
60
61 @Override
62 public boolean contains(Entry<?> entry) {
63 return get(entry) != null;
64 }
65
66 @Override
67 public Collection<Entry<?>> getChildren(Entry<?> entry) {
68 HashTreeNode<T> leaf = findNode(entry);
69 if (leaf == null) {
70 return Collections.emptyList();
71 }
72 return leaf.getChildren();
73 }
74
75 @Override
76 public Collection<Entry<?>> getSiblings(Entry<?> entry) {
77 Entry<?> parent = entry.getParent();
78 if (parent == null) {
79 return getSiblings(entry, root.keySet());
80 }
81 return getSiblings(entry, getChildren(parent));
82 }
83
84 private Collection<Entry<?>> getSiblings(Entry<?> entry, Collection<Entry<?>> generation) {
85 Set<Entry<?>> siblings = new HashSet<>(generation);
86 siblings.remove(entry);
87 return siblings;
88 }
89
90 @Override
91 @Nullable
92 public HashTreeNode<T> findNode(Entry<?> target) {
93 List<Entry<?>> parentChain = target.getAncestry();
94 if (parentChain.isEmpty()) {
95 return null;
96 }
97
98 HashTreeNode<T> node = root.get(parentChain.get(0));
99 for (int i = 1; i < parentChain.size(); i++) {
100 if (node == null) {
101 return null;
102 }
103 node = node.getChild(parentChain.get(i));
104 }
105
106 return node;
107 }
108
109 private List<HashTreeNode<T>> computePath(Entry<?> target, boolean make) {
110 List<Entry<?>> ancestry = target.getAncestry();
111 if (ancestry.isEmpty()) {
112 return Collections.emptyList();
113 }
114
115 List<HashTreeNode<T>> path = new ArrayList<>(ancestry.size());
116
117 Entry<?> rootEntry = ancestry.get(0);
118 HashTreeNode<T> node = make ? root.computeIfAbsent(rootEntry, HashTreeNode::new) : root.get(rootEntry);
119 if (node == null) {
120 return Collections.emptyList();
121 }
122
123 path.add(node);
124
125 for (int i = 1; i < ancestry.size(); i++) {
126 Entry<?> ancestor = ancestry.get(i);
127 node = make ? node.computeChild(ancestor) : node.getChild(ancestor);
128 if (node == null) {
129 return Collections.emptyList();
130 }
131
132 path.add(node);
133 }
134
135 return path;
136 }
137
138 private void removeDeadAlong(List<HashTreeNode<T>> path) {
139 for (int i = path.size() - 1; i >= 0; i--) {
140 HashTreeNode<T> node = path.get(i);
141 if (node.isEmpty()) {
142 if (i > 0) {
143 HashTreeNode<T> parentNode = path.get(i - 1);
144 parentNode.remove(node.getEntry());
145 } else {
146 root.remove(node.getEntry());
147 }
148 } else {
149 break;
150 }
151 }
152 }
153
154 @Override
155 public Iterator<EntryTreeNode<T>> iterator() {
156 Collection<EntryTreeNode<T>> nodes = new ArrayList<>();
157 for (EntryTreeNode<T> node : root.values()) {
158 nodes.addAll(node.getNodesRecursively());
159 }
160 return nodes.iterator();
161 }
162
163 @Override
164 public Stream<Entry<?>> getAllEntries() {
165 return StreamSupport.stream(spliterator(), false)
166 .filter(EntryTreeNode::hasValue)
167 .map(EntryTreeNode::getEntry);
168 }
169
170 @Override
171 public Stream<EntryTreeNode<T>> getRootNodes() {
172 return root.values().stream().map(Function.identity());
173 }
174
175 @Override
176 public boolean isEmpty() {
177 return root.isEmpty();
178 }
179
180 @Override
181 public HashEntryTree<T> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
182 HashEntryTree<T> translatedTree = new HashEntryTree<>();
183 for (EntryTreeNode<T> node : this) {
184 translatedTree.insert(translator.translate(node.getEntry()), node.getValue());
185 }
186 return translatedTree;
187 }
188}
diff --git a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java b/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
deleted file mode 100644
index 0a990bd..0000000
--- a/src/main/java/cuchaz/enigma/translation/mapping/tree/HashTreeNode.java
+++ /dev/null
@@ -1,75 +0,0 @@
1package cuchaz.enigma.translation.mapping.tree;
2
3import cuchaz.enigma.translation.representation.entry.Entry;
4
5import javax.annotation.Nonnull;
6import javax.annotation.Nullable;
7import java.util.Collection;
8import java.util.HashMap;
9import java.util.Iterator;
10import java.util.Map;
11
12public class HashTreeNode<T> implements EntryTreeNode<T>, Iterable<HashTreeNode<T>> {
13 private final Entry<?> entry;
14 private final Map<Entry<?>, HashTreeNode<T>> children = new HashMap<>();
15 private T value;
16
17 HashTreeNode(Entry<?> entry) {
18 this.entry = entry;
19 }
20
21 void putValue(T value) {
22 this.value = value;
23 }
24
25 T removeValue() {
26 T value = this.value;
27 this.value = null;
28 return value;
29 }
30
31 @Nullable
32 HashTreeNode<T> getChild(Entry<?> entry) {
33 return children.get(entry);
34 }
35
36 @Nonnull
37 HashTreeNode<T> computeChild(Entry<?> entry) {
38 return children.computeIfAbsent(entry, HashTreeNode::new);
39 }
40
41 void remove(Entry<?> entry) {
42 children.remove(entry);
43 }
44
45 @Override
46 @Nullable
47 public T getValue() {
48 return value;
49 }
50
51 @Override
52 public Entry<?> getEntry() {
53 return entry;
54 }
55
56 @Override
57 public boolean isEmpty() {
58 return children.isEmpty() && value == null;
59 }
60
61 @Override
62 public Collection<Entry<?>> getChildren() {
63 return children.keySet();
64 }
65
66 @Override
67 public Collection<? extends EntryTreeNode<T>> getChildNodes() {
68 return children.values();
69 }
70
71 @Override
72 public Iterator<HashTreeNode<T>> iterator() {
73 return children.values().iterator();
74 }
75}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java b/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
deleted file mode 100644
index b280eef..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/AccessFlags.java
+++ /dev/null
@@ -1,116 +0,0 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.analysis.Access;
4import org.objectweb.asm.Opcodes;
5
6import java.lang.reflect.Modifier;
7
8public class AccessFlags {
9 public static final AccessFlags PRIVATE = new AccessFlags(Opcodes.ACC_PRIVATE);
10 public static final AccessFlags PUBLIC = new AccessFlags(Opcodes.ACC_PUBLIC);
11
12 private int flags;
13
14 public AccessFlags(int flags) {
15 this.flags = flags;
16 }
17
18 public boolean isPrivate() {
19 return Modifier.isPrivate(this.flags);
20 }
21
22 public boolean isProtected() {
23 return Modifier.isProtected(this.flags);
24 }
25
26 public boolean isPublic() {
27 return Modifier.isPublic(this.flags);
28 }
29
30 public boolean isSynthetic() {
31 return (this.flags & Opcodes.ACC_SYNTHETIC) != 0;
32 }
33
34 public boolean isStatic() {
35 return Modifier.isStatic(this.flags);
36 }
37
38 public boolean isEnum() {
39 return (flags & Opcodes.ACC_ENUM) != 0;
40 }
41
42 public boolean isBridge() {
43 return (flags & Opcodes.ACC_BRIDGE) != 0;
44 }
45
46 public boolean isFinal() {
47 return (flags & Opcodes.ACC_FINAL) != 0;
48 }
49
50 public boolean isInterface() {
51 return (flags & Opcodes.ACC_INTERFACE) != 0;
52 }
53
54 public AccessFlags setPrivate() {
55 this.setVisibility(Opcodes.ACC_PRIVATE);
56 return this;
57 }
58
59 public AccessFlags setProtected() {
60 this.setVisibility(Opcodes.ACC_PROTECTED);
61 return this;
62 }
63
64 public AccessFlags setPublic() {
65 this.setVisibility(Opcodes.ACC_PUBLIC);
66 return this;
67 }
68
69 public AccessFlags setBridge() {
70 flags |= Opcodes.ACC_BRIDGE;
71 return this;
72 }
73
74 @Deprecated
75 public AccessFlags setBridged() {
76 return setBridge();
77 }
78
79 public void setVisibility(int visibility) {
80 this.resetVisibility();
81 this.flags |= visibility;
82 }
83
84 private void resetVisibility() {
85 this.flags &= ~(Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED | Opcodes.ACC_PUBLIC);
86 }
87
88 public int getFlags() {
89 return this.flags;
90 }
91
92 @Override
93 public boolean equals(Object obj) {
94 return obj instanceof AccessFlags && ((AccessFlags) obj).flags == flags;
95 }
96
97 @Override
98 public int hashCode() {
99 return flags;
100 }
101
102 @Override
103 public String toString() {
104 StringBuilder builder = new StringBuilder(Access.get(this).toString().toLowerCase());
105 if (isStatic()) {
106 builder.append(" static");
107 }
108 if (isSynthetic()) {
109 builder.append(" synthetic");
110 }
111 if (isBridge()) {
112 builder.append(" bridge");
113 }
114 return builder.toString();
115 }
116}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
deleted file mode 100644
index 63eb563..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java
+++ /dev/null
@@ -1,105 +0,0 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.translation.Translatable;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMap;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.EntryResolver;
8import cuchaz.enigma.translation.mapping.ResolutionStrategy;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10import cuchaz.enigma.translation.representation.entry.MethodEntry;
11import cuchaz.enigma.translation.representation.entry.ParentedEntry;
12
13import java.util.Objects;
14
15public class Lambda implements Translatable {
16 private final String invokedName;
17 private final MethodDescriptor invokedType;
18 private final MethodDescriptor samMethodType;
19 private final ParentedEntry<?> implMethod;
20 private final MethodDescriptor instantiatedMethodType;
21
22 public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry<?> implMethod, MethodDescriptor instantiatedMethodType) {
23 this.invokedName = invokedName;
24 this.invokedType = invokedType;
25 this.samMethodType = samMethodType;
26 this.implMethod = implMethod;
27 this.instantiatedMethodType = instantiatedMethodType;
28 }
29
30 @Override
31 public Lambda translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
32 MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType);
33 EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod);
34
35 return new Lambda(
36 samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName,
37 invokedType.translate(translator, resolver, mappings),
38 samMethodType.translate(translator, resolver, mappings),
39 implMethod.translate(translator, resolver, mappings),
40 instantiatedMethodType.translate(translator, resolver, mappings)
41 );
42 }
43
44 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings, MethodEntry methodEntry) {
45 for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) {
46 EntryMapping mapping = mappings.get(entry);
47 if (mapping != null) {
48 return mapping;
49 }
50 }
51 return null;
52 }
53
54 public ClassEntry getInterface() {
55 return invokedType.getReturnDesc().getTypeEntry();
56 }
57
58 public String getInvokedName() {
59 return invokedName;
60 }
61
62 public MethodDescriptor getInvokedType() {
63 return invokedType;
64 }
65
66 public MethodDescriptor getSamMethodType() {
67 return samMethodType;
68 }
69
70 public ParentedEntry<?> getImplMethod() {
71 return implMethod;
72 }
73
74 public MethodDescriptor getInstantiatedMethodType() {
75 return instantiatedMethodType;
76 }
77
78 @Override
79 public boolean equals(Object o) {
80 if (this == o) return true;
81 if (o == null || getClass() != o.getClass()) return false;
82 Lambda lambda = (Lambda) o;
83 return Objects.equals(invokedName, lambda.invokedName) &&
84 Objects.equals(invokedType, lambda.invokedType) &&
85 Objects.equals(samMethodType, lambda.samMethodType) &&
86 Objects.equals(implMethod, lambda.implMethod) &&
87 Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType);
88 }
89
90 @Override
91 public int hashCode() {
92 return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType);
93 }
94
95 @Override
96 public String toString() {
97 return "Lambda{" +
98 "invokedName='" + invokedName + '\'' +
99 ", invokedType=" + invokedType +
100 ", samMethodType=" + samMethodType +
101 ", implMethod=" + implMethod +
102 ", instantiatedMethodType=" + instantiatedMethodType +
103 '}';
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
deleted file mode 100644
index 37a7014..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java
+++ /dev/null
@@ -1,132 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation;
13
14import com.google.common.collect.Lists;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMapping;
18import cuchaz.enigma.translation.mapping.EntryResolver;
19import cuchaz.enigma.translation.mapping.EntryMap;
20import cuchaz.enigma.translation.representation.entry.ClassEntry;
21import cuchaz.enigma.utils.Utils;
22
23import java.util.ArrayList;
24import java.util.List;
25import java.util.function.Function;
26
27public class MethodDescriptor implements Translatable {
28
29 private List<TypeDescriptor> argumentDescs;
30 private TypeDescriptor returnDesc;
31
32 public MethodDescriptor(String desc) {
33 try {
34 this.argumentDescs = Lists.newArrayList();
35 int i = 0;
36 while (i < desc.length()) {
37 char c = desc.charAt(i);
38 if (c == '(') {
39 assert (this.argumentDescs.isEmpty());
40 assert (this.returnDesc == null);
41 i++;
42 } else if (c == ')') {
43 i++;
44 break;
45 } else {
46 String type = TypeDescriptor.parseFirst(desc.substring(i));
47 this.argumentDescs.add(new TypeDescriptor(type));
48 i += type.length();
49 }
50 }
51 this.returnDesc = new TypeDescriptor(TypeDescriptor.parseFirst(desc.substring(i)));
52 } catch (Exception ex) {
53 throw new IllegalArgumentException("Unable to parse method descriptor: " + desc, ex);
54 }
55 }
56
57 public MethodDescriptor(List<TypeDescriptor> argumentDescs, TypeDescriptor returnDesc) {
58 this.argumentDescs = argumentDescs;
59 this.returnDesc = returnDesc;
60 }
61
62 public List<TypeDescriptor> getArgumentDescs() {
63 return this.argumentDescs;
64 }
65
66 public TypeDescriptor getReturnDesc() {
67 return this.returnDesc;
68 }
69
70 @Override
71 public String toString() {
72 StringBuilder buf = new StringBuilder();
73 buf.append("(");
74 for (TypeDescriptor desc : this.argumentDescs) {
75 buf.append(desc);
76 }
77 buf.append(")");
78 buf.append(this.returnDesc);
79 return buf.toString();
80 }
81
82 public Iterable<TypeDescriptor> types() {
83 List<TypeDescriptor> descs = Lists.newArrayList();
84 descs.addAll(this.argumentDescs);
85 descs.add(this.returnDesc);
86 return descs;
87 }
88
89 @Override
90 public boolean equals(Object other) {
91 return other instanceof MethodDescriptor && equals((MethodDescriptor) other);
92 }
93
94 public boolean equals(MethodDescriptor other) {
95 return this.argumentDescs.equals(other.argumentDescs) && this.returnDesc.equals(other.returnDesc);
96 }
97
98 @Override
99 public int hashCode() {
100 return Utils.combineHashesOrdered(this.argumentDescs.hashCode(), this.returnDesc.hashCode());
101 }
102
103 public boolean hasClass(ClassEntry classEntry) {
104 for (TypeDescriptor desc : types()) {
105 if (desc.containsType() && desc.getTypeEntry().equals(classEntry)) {
106 return true;
107 }
108 }
109 return false;
110 }
111
112 public MethodDescriptor remap(Function<String, String> remapper) {
113 List<TypeDescriptor> argumentDescs = new ArrayList<>(this.argumentDescs.size());
114 for (TypeDescriptor desc : this.argumentDescs) {
115 argumentDescs.add(desc.remap(remapper));
116 }
117 return new MethodDescriptor(argumentDescs, returnDesc.remap(remapper));
118 }
119
120 @Override
121 public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
122 List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size());
123 for (TypeDescriptor argument : argumentDescs) {
124 translatedArguments.add(translator.translate(argument));
125 }
126 return new MethodDescriptor(translatedArguments, translator.translate(returnDesc));
127 }
128
129 public boolean canConflictWith(MethodDescriptor descriptor) {
130 return descriptor.argumentDescs.equals(argumentDescs);
131 }
132}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Signature.java b/src/main/java/cuchaz/enigma/translation/representation/Signature.java
deleted file mode 100644
index 424088a..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/Signature.java
+++ /dev/null
@@ -1,98 +0,0 @@
1package cuchaz.enigma.translation.representation;
2
3import cuchaz.enigma.bytecode.translators.TranslationSignatureVisitor;
4import cuchaz.enigma.translation.Translatable;
5import cuchaz.enigma.translation.Translator;
6import cuchaz.enigma.translation.mapping.EntryMap;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.EntryResolver;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10import org.objectweb.asm.signature.SignatureReader;
11import org.objectweb.asm.signature.SignatureVisitor;
12import org.objectweb.asm.signature.SignatureWriter;
13
14import java.util.function.Function;
15import java.util.regex.Pattern;
16
17public class Signature implements Translatable {
18 private static final Pattern OBJECT_PATTERN = Pattern.compile(".*:Ljava/lang/Object;:.*");
19
20 private final String signature;
21 private final boolean isType;
22
23 private Signature(String signature, boolean isType) {
24 if (signature != null && OBJECT_PATTERN.matcher(signature).matches()) {
25 signature = signature.replaceAll(":Ljava/lang/Object;:", "::");
26 }
27
28 this.signature = signature;
29 this.isType = isType;
30 }
31
32 public static Signature createTypedSignature(String signature) {
33 if (signature != null && !signature.isEmpty()) {
34 return new Signature(signature, true);
35 }
36 return new Signature(null, true);
37 }
38
39 public static Signature createSignature(String signature) {
40 if (signature != null && !signature.isEmpty()) {
41 return new Signature(signature, false);
42 }
43 return new Signature(null, false);
44 }
45
46 public String getSignature() {
47 return signature;
48 }
49
50 public boolean isType() {
51 return isType;
52 }
53
54 public Signature remap(Function<String, String> remapper) {
55 if (signature == null) {
56 return this;
57 }
58 SignatureWriter writer = new SignatureWriter();
59 SignatureVisitor visitor = new TranslationSignatureVisitor(remapper, writer);
60 if (isType) {
61 new SignatureReader(signature).acceptType(visitor);
62 } else {
63 new SignatureReader(signature).accept(visitor);
64 }
65 return new Signature(writer.toString(), isType);
66 }
67
68 @Override
69 public boolean equals(Object obj) {
70 if (obj instanceof Signature) {
71 Signature other = (Signature) obj;
72 return (other.signature == null && signature == null || other.signature != null
73 && signature != null && other.signature.equals(signature))
74 && other.isType == this.isType;
75 }
76 return false;
77 }
78
79 @Override
80 public int hashCode() {
81 int hash = (isType ? 1 : 0) << 16;
82 if (signature != null) {
83 hash |= signature.hashCode();
84 }
85
86 return hash;
87 }
88
89 @Override
90 public String toString() {
91 return signature;
92 }
93
94 @Override
95 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
96 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
97 }
98}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
deleted file mode 100644
index f7ba849..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/TypeDescriptor.java
+++ /dev/null
@@ -1,268 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation;
13
14import com.google.common.base.Preconditions;
15import com.google.common.collect.Maps;
16import cuchaz.enigma.translation.Translatable;
17import cuchaz.enigma.translation.Translator;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.EntryMap;
21import cuchaz.enigma.translation.representation.entry.ClassEntry;
22
23import java.util.Map;
24import java.util.function.Function;
25
26public class TypeDescriptor implements Translatable {
27
28 protected final String desc;
29
30 public TypeDescriptor(String desc) {
31 Preconditions.checkNotNull(desc, "Desc cannot be null");
32
33 // don't deal with generics
34 // this is just for raw jvm types
35 if (desc.charAt(0) == 'T' || desc.indexOf('<') >= 0 || desc.indexOf('>') >= 0) {
36 throw new IllegalArgumentException("don't use with generic types or templates: " + desc);
37 }
38
39 this.desc = desc;
40 }
41
42 public static String parseFirst(String in) {
43
44 if (in == null || in.length() <= 0) {
45 throw new IllegalArgumentException("No desc to parse, input is empty!");
46 }
47
48 // read one desc from the input
49
50 char c = in.charAt(0);
51
52 // first check for void
53 if (c == 'V') {
54 return "V";
55 }
56
57 // then check for primitives
58 Primitive primitive = Primitive.get(c);
59 if (primitive != null) {
60 return in.substring(0, 1);
61 }
62
63 // then check for classes
64 if (c == 'L') {
65 return readClass(in);
66 }
67
68 // then check for templates
69 if (c == 'T') {
70 return readClass(in);
71 }
72
73 // then check for arrays
74 int dim = countArrayDimension(in);
75 if (dim > 0) {
76 String arrayType = TypeDescriptor.parseFirst(in.substring(dim));
77 return in.substring(0, dim + arrayType.length());
78 }
79
80 throw new IllegalArgumentException("don't know how to parse: " + in);
81 }
82
83 private static int countArrayDimension(String in) {
84 int i = 0;
85 while (i < in.length() && in.charAt(i) == '[')
86 i++;
87 return i;
88 }
89
90 private static String readClass(String in) {
91 // read all the characters in the buffer until we hit a ';'
92 // include the parameters too
93 StringBuilder buf = new StringBuilder();
94 int depth = 0;
95 for (int i = 0; i < in.length(); i++) {
96 char c = in.charAt(i);
97 buf.append(c);
98
99 if (c == '<') {
100 depth++;
101 } else if (c == '>') {
102 depth--;
103 } else if (depth == 0 && c == ';') {
104 return buf.toString();
105 }
106 }
107 return null;
108 }
109
110 public static TypeDescriptor of(String name) {
111 return new TypeDescriptor("L" + name + ";");
112 }
113
114 @Override
115 public String toString() {
116 return this.desc;
117 }
118
119 public boolean isVoid() {
120 return this.desc.length() == 1 && this.desc.charAt(0) == 'V';
121 }
122
123 public boolean isPrimitive() {
124 return this.desc.length() == 1 && Primitive.get(this.desc.charAt(0)) != null;
125 }
126
127 public Primitive getPrimitive() {
128 if (!isPrimitive()) {
129 throw new IllegalStateException("not a primitive");
130 }
131 return Primitive.get(this.desc.charAt(0));
132 }
133
134 public boolean isType() {
135 return this.desc.charAt(0) == 'L' && this.desc.charAt(this.desc.length() - 1) == ';';
136 }
137
138 public ClassEntry getTypeEntry() {
139 if (isType()) {
140 String name = this.desc.substring(1, this.desc.length() - 1);
141
142 int pos = name.indexOf('<');
143 if (pos >= 0) {
144 // remove the parameters from the class name
145 name = name.substring(0, pos);
146 }
147
148 return new ClassEntry(name);
149
150 } else if (isArray() && getArrayType().isType()) {
151 return getArrayType().getTypeEntry();
152 } else {
153 throw new IllegalStateException("desc doesn't have a class");
154 }
155 }
156
157 public boolean isArray() {
158 return this.desc.charAt(0) == '[';
159 }
160
161 public int getArrayDimension() {
162 if (!isArray()) {
163 throw new IllegalStateException("not an array");
164 }
165 return countArrayDimension(this.desc);
166 }
167
168 public TypeDescriptor getArrayType() {
169 if (!isArray()) {
170 throw new IllegalStateException("not an array");
171 }
172 return new TypeDescriptor(this.desc.substring(getArrayDimension()));
173 }
174
175 public boolean containsType() {
176 return isType() || (isArray() && getArrayType().containsType());
177 }
178
179 @Override
180 public boolean equals(Object other) {
181 return other instanceof TypeDescriptor && equals((TypeDescriptor) other);
182 }
183
184 public boolean equals(TypeDescriptor other) {
185 return this.desc.equals(other.desc);
186 }
187
188 @Override
189 public int hashCode() {
190 return this.desc.hashCode();
191 }
192
193 public TypeDescriptor remap(Function<String, String> remapper) {
194 String desc = this.desc;
195 if (isType() || (isArray() && containsType())) {
196 String replacedName = remapper.apply(this.getTypeEntry().getFullName());
197 if (replacedName != null) {
198 if (this.isType()) {
199 desc = "L" + replacedName + ";";
200 } else {
201 desc = getArrayPrefix(this.getArrayDimension()) + "L" + replacedName + ";";
202 }
203 }
204 }
205 return new TypeDescriptor(desc);
206 }
207
208 private static String getArrayPrefix(int dimension) {
209 StringBuilder buf = new StringBuilder();
210 for (int i = 0; i < dimension; i++) {
211 buf.append("[");
212 }
213 return buf.toString();
214 }
215
216 public int getSize() {
217 switch (desc.charAt(0)) {
218 case 'J':
219 case 'D':
220 if (desc.length() == 1) {
221 return 2;
222 } else {
223 return 1;
224 }
225 default:
226 return 1;
227 }
228 }
229
230 @Override
231 public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
232 return remap(name -> translator.translate(new ClassEntry(name)).getFullName());
233 }
234
235 public enum Primitive {
236 BYTE('B'),
237 CHARACTER('C'),
238 SHORT('S'),
239 INTEGER('I'),
240 LONG('J'),
241 FLOAT('F'),
242 DOUBLE('D'),
243 BOOLEAN('Z');
244
245 private static final Map<Character, Primitive> lookup;
246
247 static {
248 lookup = Maps.newTreeMap();
249 for (Primitive val : values()) {
250 lookup.put(val.getCode(), val);
251 }
252 }
253
254 private char code;
255
256 Primitive(char code) {
257 this.code = code;
258 }
259
260 public static Primitive get(char code) {
261 return lookup.get(code);
262 }
263
264 public char getCode() {
265 return this.code;
266 }
267 }
268}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
deleted file mode 100644
index 6930765..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassDefEntry.java
+++ /dev/null
@@ -1,93 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19
20import javax.annotation.Nullable;
21import java.util.Arrays;
22
23public class ClassDefEntry extends ClassEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26 private final ClassEntry superClass;
27 private final ClassEntry[] interfaces;
28
29 public ClassDefEntry(String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
30 this(getOuterClass(className), getInnerName(className), signature, access, superClass, interfaces, null);
31 }
32
33 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass, ClassEntry[] interfaces) {
34 this(parent, className, signature, access, superClass, interfaces, null);
35 }
36
37 public ClassDefEntry(ClassEntry parent, String className, Signature signature, AccessFlags access, @Nullable ClassEntry superClass,
38 ClassEntry[] interfaces, String javadocs) {
39 super(parent, className, javadocs);
40 Preconditions.checkNotNull(signature, "Class signature cannot be null");
41 Preconditions.checkNotNull(access, "Class access cannot be null");
42
43 this.signature = signature;
44 this.access = access;
45 this.superClass = superClass;
46 this.interfaces = interfaces != null ? interfaces : new ClassEntry[0];
47 }
48
49 public static ClassDefEntry parse(int access, String name, String signature, String superName, String[] interfaces) {
50 ClassEntry superClass = superName != null ? new ClassEntry(superName) : null;
51 ClassEntry[] interfaceClasses = Arrays.stream(interfaces).map(ClassEntry::new).toArray(ClassEntry[]::new);
52 return new ClassDefEntry(name, Signature.createSignature(signature), new AccessFlags(access), superClass, interfaceClasses);
53 }
54
55 public Signature getSignature() {
56 return signature;
57 }
58
59 @Override
60 public AccessFlags getAccess() {
61 return access;
62 }
63
64 @Nullable
65 public ClassEntry getSuperClass() {
66 return superClass;
67 }
68
69 public ClassEntry[] getInterfaces() {
70 return interfaces;
71 }
72
73 @Override
74 public ClassDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
75 Signature translatedSignature = translator.translate(signature);
76 String translatedName = mapping != null ? mapping.getTargetName() : name;
77 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
78 ClassEntry translatedSuper = translator.translate(superClass);
79 ClassEntry[] translatedInterfaces = Arrays.stream(interfaces).map(translator::translate).toArray(ClassEntry[]::new);
80 String docs = mapping != null ? mapping.getJavadoc() : null;
81 return new ClassDefEntry(parent, translatedName, translatedSignature, translatedAccess, translatedSuper, translatedInterfaces, docs);
82 }
83
84 @Override
85 public ClassDefEntry withName(String name) {
86 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs);
87 }
88
89 @Override
90 public ClassDefEntry withParent(ClassEntry parent) {
91 return new ClassDefEntry(parent, name, signature, access, superClass, interfaces, javadocs);
92 }
93}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
deleted file mode 100644
index d6171f1..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/ClassEntry.java
+++ /dev/null
@@ -1,214 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.mapping.NameValidator;
18import cuchaz.enigma.translation.representation.TypeDescriptor;
19
20import javax.annotation.Nonnull;
21import javax.annotation.Nullable;
22import java.util.List;
23import java.util.Objects;
24
25public class ClassEntry extends ParentedEntry<ClassEntry> implements Comparable<ClassEntry> {
26 private final String fullName;
27
28 public ClassEntry(String className) {
29 this(getOuterClass(className), getInnerName(className), null);
30 }
31
32 public ClassEntry(@Nullable ClassEntry parent, String className) {
33 this(parent, className, null);
34 }
35
36 public ClassEntry(@Nullable ClassEntry parent, String className, @Nullable String javadocs) {
37 super(parent, className, javadocs);
38 if (parent != null) {
39 fullName = parent.getFullName() + "$" + name;
40 } else {
41 fullName = name;
42 }
43
44 if (parent == null && className.indexOf('.') >= 0) {
45 throw new IllegalArgumentException("Class name must be in JVM format. ie, path/to/package/class$inner : " + className);
46 }
47 }
48
49 @Override
50 public Class<ClassEntry> getParentType() {
51 return ClassEntry.class;
52 }
53
54 @Override
55 public String getName() {
56 return this.name;
57 }
58
59 public String getFullName() {
60 return fullName;
61 }
62
63 @Override
64 public ClassEntry translate(Translator translator, @Nullable EntryMapping mapping) {
65 if (name.charAt(0) == '[') {
66 String translatedName = translator.translate(new TypeDescriptor(name)).toString();
67 return new ClassEntry(parent, translatedName);
68 }
69
70 String translatedName = mapping != null ? mapping.getTargetName() : name;
71 String docs = mapping != null ? mapping.getJavadoc() : null;
72 return new ClassEntry(parent, translatedName, docs);
73 }
74
75 @Override
76 public ClassEntry getContainingClass() {
77 return this;
78 }
79
80 @Override
81 public int hashCode() {
82 return fullName.hashCode();
83 }
84
85 @Override
86 public boolean equals(Object other) {
87 return other instanceof ClassEntry && equals((ClassEntry) other);
88 }
89
90 public boolean equals(ClassEntry other) {
91 return other != null && Objects.equals(parent, other.parent) && this.name.equals(other.name);
92 }
93
94 @Override
95 public boolean canConflictWith(Entry<?> entry) {
96 return true;
97 }
98
99 @Override
100 public void validateName(String name) throws IllegalNameException {
101 NameValidator.validateClassName(name);
102 }
103
104 @Override
105 public ClassEntry withName(String name) {
106 return new ClassEntry(parent, name, javadocs);
107 }
108
109 @Override
110 public ClassEntry withParent(ClassEntry parent) {
111 return new ClassEntry(parent, name, javadocs);
112 }
113
114 @Override
115 public String toString() {
116 return getFullName();
117 }
118
119 public String getPackageName() {
120 return getPackageName(fullName);
121 }
122
123 public String getSimpleName() {
124 int packagePos = name.lastIndexOf('/');
125 if (packagePos > 0) {
126 return name.substring(packagePos + 1);
127 }
128 return name;
129 }
130
131 public boolean isInnerClass() {
132 return parent != null;
133 }
134
135 @Nullable
136 public ClassEntry getOuterClass() {
137 return parent;
138 }
139
140 @Nonnull
141 public ClassEntry getOutermostClass() {
142 if (parent == null) {
143 return this;
144 }
145 return parent.getOutermostClass();
146 }
147
148 public ClassEntry buildClassEntry(List<ClassEntry> classChain) {
149 assert (classChain.contains(this));
150 StringBuilder buf = new StringBuilder();
151 for (ClassEntry chainEntry : classChain) {
152 if (buf.length() == 0) {
153 buf.append(chainEntry.getFullName());
154 } else {
155 buf.append("$");
156 buf.append(chainEntry.getSimpleName());
157 }
158
159 if (chainEntry == this) {
160 break;
161 }
162 }
163 return new ClassEntry(buf.toString());
164 }
165
166 public boolean isJre() {
167 String packageName = getPackageName();
168 return packageName != null && (packageName.startsWith("java") || packageName.startsWith("javax"));
169 }
170
171 public static String getPackageName(String name) {
172 int pos = name.lastIndexOf('/');
173 if (pos > 0) {
174 return name.substring(0, pos);
175 }
176 return null;
177 }
178
179 @Nullable
180 public static ClassEntry getOuterClass(String name) {
181 int index = name.lastIndexOf('$');
182 if (index >= 0) {
183 return new ClassEntry(name.substring(0, index));
184 }
185 return null;
186 }
187
188 public static String getInnerName(String name) {
189 int innerClassPos = name.lastIndexOf('$');
190 if (innerClassPos > 0) {
191 return name.substring(innerClassPos + 1);
192 }
193 return name;
194 }
195
196 @Override
197 public String getSourceRemapName() {
198 ClassEntry outerClass = getOuterClass();
199 if (outerClass != null) {
200 return outerClass.getSourceRemapName() + "." + name;
201 }
202 return getSimpleName();
203 }
204
205 @Override
206 public int compareTo(ClassEntry entry) {
207 String fullName = getFullName();
208 String otherFullName = entry.getFullName();
209 if (fullName.length() != otherFullName.length()) {
210 return fullName.length() - otherFullName.length();
211 }
212 return fullName.compareTo(otherFullName);
213 }
214}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
deleted file mode 100644
index 82536c7..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/DefEntry.java
+++ /dev/null
@@ -1,7 +0,0 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import cuchaz.enigma.translation.representation.AccessFlags;
4
5public interface DefEntry<P extends Entry<?>> extends Entry<P> {
6 AccessFlags getAccess();
7}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
deleted file mode 100644
index 72b0391..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/Entry.java
+++ /dev/null
@@ -1,107 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import cuchaz.enigma.throwables.IllegalNameException;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.mapping.NameValidator;
17
18import javax.annotation.Nullable;
19import java.util.ArrayList;
20import java.util.List;
21
22public interface Entry<P extends Entry<?>> extends Translatable {
23 String getName();
24
25 String getJavadocs();
26
27 default String getSourceRemapName() {
28 return getName();
29 }
30
31 @Nullable
32 P getParent();
33
34 Class<P> getParentType();
35
36 Entry<P> withName(String name);
37
38 Entry<P> withParent(P parent);
39
40 boolean canConflictWith(Entry<?> entry);
41
42 @Nullable
43 default ClassEntry getContainingClass() {
44 P parent = getParent();
45 if (parent == null) {
46 return null;
47 }
48 if (parent instanceof ClassEntry) {
49 return (ClassEntry) parent;
50 }
51 return parent.getContainingClass();
52 }
53
54 default List<Entry<?>> getAncestry() {
55 P parent = getParent();
56 List<Entry<?>> entries = new ArrayList<>();
57 if (parent != null) {
58 entries.addAll(parent.getAncestry());
59 }
60 entries.add(this);
61 return entries;
62 }
63
64 @Nullable
65 @SuppressWarnings("unchecked")
66 default <E extends Entry<?>> E findAncestor(Class<E> type) {
67 List<Entry<?>> ancestry = getAncestry();
68 for (int i = ancestry.size() - 1; i >= 0; i--) {
69 Entry<?> ancestor = ancestry.get(i);
70 if (type.isAssignableFrom(ancestor.getClass())) {
71 return (E) ancestor;
72 }
73 }
74 return null;
75 }
76
77 @SuppressWarnings("unchecked")
78 default <E extends Entry<?>> Entry<P> replaceAncestor(E target, E replacement) {
79 if (replacement.equals(target)) {
80 return this;
81 }
82
83 if (equals(target)) {
84 return (Entry<P>) replacement;
85 }
86
87 P parent = getParent();
88 if (parent == null) {
89 return this;
90 }
91
92 return withParent((P) parent.replaceAncestor(target, replacement));
93 }
94
95 default void validateName(String name) throws IllegalNameException {
96 NameValidator.validateIdentifier(name);
97 }
98
99 @SuppressWarnings("unchecked")
100 @Nullable
101 default <C extends Entry<?>> Entry<C> castParent(Class<C> parentType) {
102 if (parentType.equals(getParentType())) {
103 return (Entry<C>) this;
104 }
105 return null;
106 }
107}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
deleted file mode 100644
index f9282b2..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldDefEntry.java
+++ /dev/null
@@ -1,71 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.Signature;
19import cuchaz.enigma.translation.representation.TypeDescriptor;
20
21import javax.annotation.Nullable;
22
23public class FieldDefEntry extends FieldEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access) {
28 this(owner, name, desc, signature, access, null);
29 }
30
31 public FieldDefEntry(ClassEntry owner, String name, TypeDescriptor desc, Signature signature, AccessFlags access, String javadocs) {
32 super(owner, name, desc, javadocs);
33 Preconditions.checkNotNull(access, "Field access cannot be null");
34 Preconditions.checkNotNull(signature, "Field signature cannot be null");
35 this.access = access;
36 this.signature = signature;
37 }
38
39 public static FieldDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
40 return new FieldDefEntry(owner, name, new TypeDescriptor(desc), Signature.createTypedSignature(signature), new AccessFlags(access), null);
41 }
42
43 @Override
44 public AccessFlags getAccess() {
45 return access;
46 }
47
48 public Signature getSignature() {
49 return signature;
50 }
51
52 @Override
53 public FieldDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 TypeDescriptor translatedDesc = translator.translate(desc);
55 Signature translatedSignature = translator.translate(signature);
56 String translatedName = mapping != null ? mapping.getTargetName() : name;
57 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
58 String docs = mapping != null ? mapping.getJavadoc() : null;
59 return new FieldDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs);
60 }
61
62 @Override
63 public FieldDefEntry withName(String name) {
64 return new FieldDefEntry(parent, name, desc, signature, access, javadocs);
65 }
66
67 @Override
68 public FieldDefEntry withParent(ClassEntry owner) {
69 return new FieldDefEntry(owner, this.name, this.desc, signature, access, javadocs);
70 }
71}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
deleted file mode 100644
index bef0edf..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/FieldEntry.java
+++ /dev/null
@@ -1,96 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.TypeDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class FieldEntry extends ParentedEntry<ClassEntry> implements Comparable<FieldEntry> {
23 protected final TypeDescriptor desc;
24
25 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc) {
26 this(parent, name, desc, null);
27 }
28
29 public FieldEntry(ClassEntry parent, String name, TypeDescriptor desc, String javadocs) {
30 super(parent, name, javadocs);
31
32 Preconditions.checkNotNull(parent, "Owner cannot be null");
33 Preconditions.checkNotNull(desc, "Field descriptor cannot be null");
34
35 this.desc = desc;
36 }
37
38 public static FieldEntry parse(String owner, String name, String desc) {
39 return new FieldEntry(new ClassEntry(owner), name, new TypeDescriptor(desc), null);
40 }
41
42 @Override
43 public Class<ClassEntry> getParentType() {
44 return ClassEntry.class;
45 }
46
47 public TypeDescriptor getDesc() {
48 return this.desc;
49 }
50
51 @Override
52 public FieldEntry withName(String name) {
53 return new FieldEntry(parent, name, desc, null);
54 }
55
56 @Override
57 public FieldEntry withParent(ClassEntry parent) {
58 return new FieldEntry(parent, this.name, this.desc, null);
59 }
60
61 @Override
62 protected FieldEntry translate(Translator translator, @Nullable EntryMapping mapping) {
63 String translatedName = mapping != null ? mapping.getTargetName() : name;
64 String docs = mapping != null ? mapping.getJavadoc() : null;
65 return new FieldEntry(parent, translatedName, translator.translate(desc), docs);
66 }
67
68 @Override
69 public int hashCode() {
70 return Utils.combineHashesOrdered(this.parent, this.name, this.desc);
71 }
72
73 @Override
74 public boolean equals(Object other) {
75 return other instanceof FieldEntry && equals((FieldEntry) other);
76 }
77
78 public boolean equals(FieldEntry other) {
79 return this.parent.equals(other.parent) && name.equals(other.name) && desc.equals(other.desc);
80 }
81
82 @Override
83 public boolean canConflictWith(Entry<?> entry) {
84 return entry instanceof FieldEntry && ((FieldEntry) entry).parent.equals(parent);
85 }
86
87 @Override
88 public String toString() {
89 return this.parent.getFullName() + "." + this.name + ":" + this.desc;
90 }
91
92 @Override
93 public int compareTo(FieldEntry entry) {
94 return (name + desc.toString()).compareTo(entry.name + entry.desc.toString());
95 }
96}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
deleted file mode 100644
index aad4236..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableDefEntry.java
+++ /dev/null
@@ -1,51 +0,0 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.representation.TypeDescriptor;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableDefEntry extends LocalVariableEntry {
16 protected final TypeDescriptor desc;
17
18 public LocalVariableDefEntry(MethodEntry ownerEntry, int index, String name, boolean parameter, TypeDescriptor desc, String javadoc) {
19 super(ownerEntry, index, name, parameter, javadoc);
20 Preconditions.checkNotNull(desc, "Variable desc cannot be null");
21
22 this.desc = desc;
23 }
24
25 public TypeDescriptor getDesc() {
26 return desc;
27 }
28
29 @Override
30 public LocalVariableDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
31 TypeDescriptor translatedDesc = translator.translate(desc);
32 String translatedName = mapping != null ? mapping.getTargetName() : name;
33 String javadoc = mapping != null ? mapping.getJavadoc() : javadocs;
34 return new LocalVariableDefEntry(parent, index, translatedName, parameter, translatedDesc, javadoc);
35 }
36
37 @Override
38 public LocalVariableDefEntry withName(String name) {
39 return new LocalVariableDefEntry(parent, index, name, parameter, desc, javadocs);
40 }
41
42 @Override
43 public LocalVariableDefEntry withParent(MethodEntry entry) {
44 return new LocalVariableDefEntry(entry, index, name, parameter, desc, javadocs);
45 }
46
47 @Override
48 public String toString() {
49 return this.parent + "(" + this.index + ":" + this.name + ":" + this.desc + ")";
50 }
51}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
deleted file mode 100644
index 3ccb1fa..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/LocalVariableEntry.java
+++ /dev/null
@@ -1,93 +0,0 @@
1package cuchaz.enigma.translation.representation.entry;
2
3import com.google.common.base.Preconditions;
4import cuchaz.enigma.translation.Translator;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.utils.Utils;
7
8import javax.annotation.Nullable;
9
10/**
11 * TypeDescriptor...
12 * Created by Thog
13 * 19/10/2016
14 */
15public class LocalVariableEntry extends ParentedEntry<MethodEntry> implements Comparable<LocalVariableEntry> {
16
17 protected final int index;
18 protected final boolean parameter;
19
20 public LocalVariableEntry(MethodEntry parent, int index, String name, boolean parameter, String javadoc) {
21 super(parent, name, javadoc);
22
23 Preconditions.checkNotNull(parent, "Variable owner cannot be null");
24 Preconditions.checkArgument(index >= 0, "Index must be positive");
25
26 this.index = index;
27 this.parameter = parameter;
28 }
29
30 @Override
31 public Class<MethodEntry> getParentType() {
32 return MethodEntry.class;
33 }
34
35 public boolean isArgument() {
36 return this.parameter;
37 }
38
39 public int getIndex() {
40 return index;
41 }
42
43 @Override
44 public String getName() {
45 return this.name;
46 }
47
48 @Override
49 public LocalVariableEntry translate(Translator translator, @Nullable EntryMapping mapping) {
50 String translatedName = mapping != null ? mapping.getTargetName() : name;
51 String javadoc = mapping != null ? mapping.getJavadoc() : null;
52 return new LocalVariableEntry(parent, index, translatedName, parameter, javadoc);
53 }
54
55 @Override
56 public LocalVariableEntry withName(String name) {
57 return new LocalVariableEntry(parent, index, name, parameter, javadocs);
58 }
59
60 @Override
61 public LocalVariableEntry withParent(MethodEntry parent) {
62 return new LocalVariableEntry(parent, index, name, parameter, javadocs);
63 }
64
65 @Override
66 public int hashCode() {
67 return Utils.combineHashesOrdered(this.parent, this.index);
68 }
69
70 @Override
71 public boolean equals(Object other) {
72 return other instanceof LocalVariableEntry && equals((LocalVariableEntry) other);
73 }
74
75 public boolean equals(LocalVariableEntry other) {
76 return this.parent.equals(other.parent) && this.index == other.index;
77 }
78
79 @Override
80 public boolean canConflictWith(Entry<?> entry) {
81 return entry instanceof LocalVariableEntry && ((LocalVariableEntry) entry).parent.equals(parent);
82 }
83
84 @Override
85 public String toString() {
86 return this.parent + "(" + this.index + ":" + this.name + ")";
87 }
88
89 @Override
90 public int compareTo(LocalVariableEntry entry) {
91 return Integer.compare(index, entry.index);
92 }
93}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
deleted file mode 100644
index 4e75a5c..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodDefEntry.java
+++ /dev/null
@@ -1,71 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.AccessFlags;
18import cuchaz.enigma.translation.representation.MethodDescriptor;
19import cuchaz.enigma.translation.representation.Signature;
20
21import javax.annotation.Nullable;
22
23public class MethodDefEntry extends MethodEntry implements DefEntry<ClassEntry> {
24 private final AccessFlags access;
25 private final Signature signature;
26
27 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access) {
28 this(owner, name, descriptor, signature, access, null);
29 }
30
31 public MethodDefEntry(ClassEntry owner, String name, MethodDescriptor descriptor, Signature signature, AccessFlags access, String docs) {
32 super(owner, name, descriptor, docs);
33 Preconditions.checkNotNull(access, "Method access cannot be null");
34 Preconditions.checkNotNull(signature, "Method signature cannot be null");
35 this.access = access;
36 this.signature = signature;
37 }
38
39 public static MethodDefEntry parse(ClassEntry owner, int access, String name, String desc, String signature) {
40 return new MethodDefEntry(owner, name, new MethodDescriptor(desc), Signature.createSignature(signature), new AccessFlags(access), null);
41 }
42
43 @Override
44 public AccessFlags getAccess() {
45 return access;
46 }
47
48 public Signature getSignature() {
49 return signature;
50 }
51
52 @Override
53 public MethodDefEntry translate(Translator translator, @Nullable EntryMapping mapping) {
54 MethodDescriptor translatedDesc = translator.translate(descriptor);
55 Signature translatedSignature = translator.translate(signature);
56 String translatedName = mapping != null ? mapping.getTargetName() : name;
57 AccessFlags translatedAccess = mapping != null ? mapping.getAccessModifier().transform(access) : access;
58 String docs = mapping != null ? mapping.getJavadoc() : null;
59 return new MethodDefEntry(parent, translatedName, translatedDesc, translatedSignature, translatedAccess, docs);
60 }
61
62 @Override
63 public MethodDefEntry withName(String name) {
64 return new MethodDefEntry(parent, name, descriptor, signature, access, javadocs);
65 }
66
67 @Override
68 public MethodDefEntry withParent(ClassEntry parent) {
69 return new MethodDefEntry(new ClassEntry(parent.getFullName()), name, descriptor, signature, access, javadocs);
70 }
71}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
deleted file mode 100644
index e1ffad3..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/MethodEntry.java
+++ /dev/null
@@ -1,105 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translator;
16import cuchaz.enigma.translation.mapping.EntryMapping;
17import cuchaz.enigma.translation.representation.MethodDescriptor;
18import cuchaz.enigma.utils.Utils;
19
20import javax.annotation.Nullable;
21
22public class MethodEntry extends ParentedEntry<ClassEntry> implements Comparable<MethodEntry> {
23
24 protected final MethodDescriptor descriptor;
25
26 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor) {
27 this(parent, name, descriptor, null);
28 }
29
30 public MethodEntry(ClassEntry parent, String name, MethodDescriptor descriptor, String javadocs) {
31 super(parent, name, javadocs);
32
33 Preconditions.checkNotNull(parent, "Parent cannot be null");
34 Preconditions.checkNotNull(descriptor, "Method descriptor cannot be null");
35
36 this.descriptor = descriptor;
37 }
38
39 public static MethodEntry parse(String owner, String name, String desc) {
40 return new MethodEntry(new ClassEntry(owner), name, new MethodDescriptor(desc), null);
41 }
42
43 @Override
44 public Class<ClassEntry> getParentType() {
45 return ClassEntry.class;
46 }
47
48 public MethodDescriptor getDesc() {
49 return this.descriptor;
50 }
51
52 public boolean isConstructor() {
53 return name.equals("<init>") || name.equals("<clinit>");
54 }
55
56 @Override
57 public MethodEntry translate(Translator translator, @Nullable EntryMapping mapping) {
58 String translatedName = mapping != null ? mapping.getTargetName() : name;
59 String docs = mapping != null ? mapping.getJavadoc() : null;
60 return new MethodEntry(parent, translatedName, translator.translate(descriptor), docs);
61 }
62
63 @Override
64 public MethodEntry withName(String name) {
65 return new MethodEntry(parent, name, descriptor, javadocs);
66 }
67
68 @Override
69 public MethodEntry withParent(ClassEntry parent) {
70 return new MethodEntry(new ClassEntry(parent.getFullName()), name, descriptor, javadocs);
71 }
72
73 @Override
74 public int hashCode() {
75 return Utils.combineHashesOrdered(this.parent, this.name, this.descriptor);
76 }
77
78 @Override
79 public boolean equals(Object other) {
80 return other instanceof MethodEntry && equals((MethodEntry) other);
81 }
82
83 public boolean equals(MethodEntry other) {
84 return this.parent.equals(other.getParent()) && this.name.equals(other.getName()) && this.descriptor.equals(other.getDesc());
85 }
86
87 @Override
88 public boolean canConflictWith(Entry<?> entry) {
89 if (entry instanceof MethodEntry) {
90 MethodEntry methodEntry = (MethodEntry) entry;
91 return methodEntry.parent.equals(parent) && methodEntry.descriptor.canConflictWith(descriptor);
92 }
93 return false;
94 }
95
96 @Override
97 public String toString() {
98 return this.parent.getFullName() + "." + this.name + this.descriptor;
99 }
100
101 @Override
102 public int compareTo(MethodEntry entry) {
103 return (name + descriptor.toString()).compareTo(entry.name + entry.descriptor.toString());
104 }
105}
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
deleted file mode 100644
index cbc5faf..0000000
--- a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java
+++ /dev/null
@@ -1,82 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.translation.representation.entry;
13
14import com.google.common.base.Preconditions;
15import cuchaz.enigma.translation.Translatable;
16import cuchaz.enigma.translation.Translator;
17import cuchaz.enigma.translation.mapping.EntryMap;
18import cuchaz.enigma.translation.mapping.EntryMapping;
19import cuchaz.enigma.translation.mapping.EntryResolver;
20import cuchaz.enigma.translation.mapping.ResolutionStrategy;
21
22import javax.annotation.Nullable;
23
24public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> {
25 protected final P parent;
26 protected final String name;
27 protected final @Nullable String javadocs;
28
29 protected ParentedEntry(P parent, String name, String javadocs) {
30 this.parent = parent;
31 this.name = name;
32 this.javadocs = javadocs;
33
34 Preconditions.checkNotNull(name, "Name cannot be null");
35 }
36
37 @Override
38 public abstract ParentedEntry<P> withParent(P parent);
39
40 @Override
41 public abstract ParentedEntry<P> withName(String name);
42
43 protected abstract ParentedEntry<P> translate(Translator translator, @Nullable EntryMapping mapping);
44
45 @Override
46 public String getName() {
47 return name;
48 }
49
50 @Override
51 @Nullable
52 public P getParent() {
53 return parent;
54 }
55
56 @Nullable
57 @Override
58 public String getJavadocs() {
59 return javadocs;
60 }
61
62 @Override
63 public ParentedEntry<P> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) {
64 P parent = getParent();
65 EntryMapping mapping = resolveMapping(resolver, mappings);
66 if (parent == null) {
67 return translate(translator, mapping);
68 }
69 P translatedParent = translator.translate(parent);
70 return withParent(translatedParent).translate(translator, mapping);
71 }
72
73 private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings) {
74 for (ParentedEntry<P> entry : resolver.resolveEntry(this, ResolutionStrategy.RESOLVE_ROOT)) {
75 EntryMapping mapping = mappings.get(entry);
76 if (mapping != null) {
77 return mapping;
78 }
79 }
80 return null;
81 }
82}
diff --git a/src/main/java/cuchaz/enigma/utils/I18n.java b/src/main/java/cuchaz/enigma/utils/I18n.java
deleted file mode 100644
index f91c916..0000000
--- a/src/main/java/cuchaz/enigma/utils/I18n.java
+++ /dev/null
@@ -1,102 +0,0 @@
1package cuchaz.enigma.utils;
2
3import java.io.BufferedReader;
4import java.io.IOException;
5import java.io.InputStream;
6import java.io.InputStreamReader;
7import java.nio.charset.StandardCharsets;
8import java.util.ArrayList;
9import java.util.Collections;
10import java.util.Map;
11import java.util.stream.Stream;
12
13import com.google.common.collect.ImmutableList;
14import com.google.common.collect.Maps;
15import com.google.common.reflect.ClassPath;
16import com.google.common.reflect.ClassPath.ResourceInfo;
17import com.google.gson.Gson;
18
19import cuchaz.enigma.config.Config;
20
21public class I18n {
22 public static final String DEFAULT_LANGUAGE = "en_us";
23 private static final Gson GSON = new Gson();
24 private static Map<String, String> translations = Maps.newHashMap();
25 private static Map<String, String> defaultTranslations = Maps.newHashMap();
26 private static Map<String, String> languageNames = Maps.newHashMap();
27
28 static {
29 translations = load(Config.getInstance().language);
30 defaultTranslations = load(DEFAULT_LANGUAGE);
31 }
32
33 @SuppressWarnings("unchecked")
34 public static Map<String, String> load(String language) {
35 try (InputStream inputStream = I18n.class.getResourceAsStream("/lang/" + language + ".json")) {
36 if (inputStream != null) {
37 try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8))) {
38 return GSON.fromJson(reader, Map.class);
39 }
40 }
41 } catch (IOException e) {
42 e.printStackTrace();
43 }
44 return Collections.emptyMap();
45 }
46
47 public static String translate(String key) {
48 String value = translations.get(key);
49 if (value != null) {
50 return value;
51 }
52 value = defaultTranslations.get(key);
53 if (value != null) {
54 return value;
55 }
56 return key;
57 }
58
59 public static String getLanguageName(String language) {
60 return languageNames.get(language);
61 }
62
63 public static void setLanguage(String language) {
64 Config.getInstance().language = language;
65 try {
66 Config.getInstance().saveConfig();
67 } catch (IOException e) {
68 e.printStackTrace();
69 }
70 }
71
72 public static ArrayList<String> getAvailableLanguages() {
73 ArrayList<String> list = new ArrayList<String>();
74
75 try {
76 ImmutableList<ResourceInfo> resources = ClassPath.from(Thread.currentThread().getContextClassLoader()).getResources().asList();
77 Stream<ResourceInfo> dirStream = resources.stream();
78 dirStream.forEach(context -> {
79 String file = context.getResourceName();
80 if (file.startsWith("lang/") && file.endsWith(".json")) {
81 String fileName = file.substring(5, file.length() - 5);
82 list.add(fileName);
83 loadLanguageName(fileName);
84 }
85 });
86 } catch (IOException e) {
87 e.printStackTrace();
88 }
89 return list;
90 }
91
92 private static void loadLanguageName(String fileName) {
93 try (InputStream stream = Thread.currentThread().getContextClassLoader().getResourceAsStream("lang/" + fileName + ".json")) {
94 try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
95 Map<?, ?> map = GSON.fromJson(reader, Map.class);
96 languageNames.put(fileName, map.get("language").toString());
97 }
98 } catch (IOException e) {
99 e.printStackTrace();
100 }
101 }
102}
diff --git a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java b/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java
deleted file mode 100644
index c12e913..0000000
--- a/src/main/java/cuchaz/enigma/utils/LFPrintWriter.java
+++ /dev/null
@@ -1,16 +0,0 @@
1package cuchaz.enigma.utils;
2
3import java.io.PrintWriter;
4import java.io.Writer;
5
6public class LFPrintWriter extends PrintWriter {
7 public LFPrintWriter(Writer out) {
8 super(out);
9 }
10
11 @Override
12 public void println() {
13 // https://stackoverflow.com/a/14749004
14 write('\n');
15 }
16}
diff --git a/src/main/java/cuchaz/enigma/utils/Message.java b/src/main/java/cuchaz/enigma/utils/Message.java
deleted file mode 100644
index d7c5f23..0000000
--- a/src/main/java/cuchaz/enigma/utils/Message.java
+++ /dev/null
@@ -1,392 +0,0 @@
1package cuchaz.enigma.utils;
2
3import java.io.DataInput;
4import java.io.DataOutput;
5import java.io.IOException;
6import java.util.Objects;
7
8import cuchaz.enigma.network.packet.PacketHelper;
9import cuchaz.enigma.translation.representation.entry.Entry;
10
11public abstract class Message {
12
13 public final String user;
14
15 public static Chat chat(String user, String message) {
16 return new Chat(user, message);
17 }
18
19 public static Connect connect(String user) {
20 return new Connect(user);
21 }
22
23 public static Disconnect disconnect(String user) {
24 return new Disconnect(user);
25 }
26
27 public static EditDocs editDocs(String user, Entry<?> entry) {
28 return new EditDocs(user, entry);
29 }
30
31 public static MarkDeobf markDeobf(String user, Entry<?> entry) {
32 return new MarkDeobf(user, entry);
33 }
34
35 public static RemoveMapping removeMapping(String user, Entry<?> entry) {
36 return new RemoveMapping(user, entry);
37 }
38
39 public static Rename rename(String user, Entry<?> entry, String newName) {
40 return new Rename(user, entry, newName);
41 }
42
43 public abstract String translate();
44
45 public abstract Type getType();
46
47 public static Message read(DataInput input) throws IOException {
48 byte typeId = input.readByte();
49 if (typeId < 0 || typeId >= Type.values().length) {
50 throw new IOException(String.format("Invalid message type ID %d", typeId));
51 }
52 Type type = Type.values()[typeId];
53 String user = input.readUTF();
54 switch (type) {
55 case CHAT:
56 String message = input.readUTF();
57 return chat(user, message);
58 case CONNECT:
59 return connect(user);
60 case DISCONNECT:
61 return disconnect(user);
62 case EDIT_DOCS:
63 Entry<?> entry = PacketHelper.readEntry(input);
64 return editDocs(user, entry);
65 case MARK_DEOBF:
66 entry = PacketHelper.readEntry(input);
67 return markDeobf(user, entry);
68 case REMOVE_MAPPING:
69 entry = PacketHelper.readEntry(input);
70 return removeMapping(user, entry);
71 case RENAME:
72 entry = PacketHelper.readEntry(input);
73 String newName = input.readUTF();
74 return rename(user, entry, newName);
75 default:
76 throw new IllegalStateException("unreachable");
77 }
78 }
79
80 public void write(DataOutput output) throws IOException {
81 output.writeByte(getType().ordinal());
82 PacketHelper.writeString(output, user);
83 }
84
85 private Message(String user) {
86 this.user = user;
87 }
88
89 @Override
90 public boolean equals(Object o) {
91 if (this == o) return true;
92 if (o == null || getClass() != o.getClass()) return false;
93 Message message = (Message) o;
94 return Objects.equals(user, message.user);
95 }
96
97 @Override
98 public int hashCode() {
99 return Objects.hash(user);
100 }
101
102 public enum Type {
103 CHAT,
104 CONNECT,
105 DISCONNECT,
106 EDIT_DOCS,
107 MARK_DEOBF,
108 REMOVE_MAPPING,
109 RENAME,
110 }
111
112 public static final class Chat extends Message {
113
114 public final String message;
115
116 private Chat(String user, String message) {
117 super(user);
118 this.message = message;
119 }
120
121 @Override
122 public void write(DataOutput output) throws IOException {
123 super.write(output);
124 PacketHelper.writeString(output, message);
125 }
126
127 @Override
128 public String translate() {
129 return String.format(I18n.translate("message.chat.text"), user, message);
130 }
131
132 @Override
133 public Type getType() {
134 return Type.CHAT;
135 }
136
137 @Override
138 public boolean equals(Object o) {
139 if (this == o) return true;
140 if (o == null || getClass() != o.getClass()) return false;
141 if (!super.equals(o)) return false;
142 Chat chat = (Chat) o;
143 return Objects.equals(message, chat.message);
144 }
145
146 @Override
147 public int hashCode() {
148 return Objects.hash(super.hashCode(), message);
149 }
150
151 @Override
152 public String toString() {
153 return String.format("Message.Chat { user: '%s', message: '%s' }", user, message);
154 }
155
156 }
157
158 public static final class Connect extends Message {
159
160 private Connect(String user) {
161 super(user);
162 }
163
164 @Override
165 public String translate() {
166 return String.format(I18n.translate("message.connect.text"), user);
167 }
168
169 @Override
170 public Type getType() {
171 return Type.CONNECT;
172 }
173
174 @Override
175 public String toString() {
176 return String.format("Message.Connect { user: '%s' }", user);
177 }
178
179 }
180
181 public static final class Disconnect extends Message {
182
183 private Disconnect(String user) {
184 super(user);
185 }
186
187 @Override
188 public String translate() {
189 return String.format(I18n.translate("message.disconnect.text"), user);
190 }
191
192 @Override
193 public Type getType() {
194 return Type.DISCONNECT;
195 }
196
197 @Override
198 public String toString() {
199 return String.format("Message.Disconnect { user: '%s' }", user);
200 }
201
202 }
203
204 public static final class EditDocs extends Message {
205
206 public final Entry<?> entry;
207
208 private EditDocs(String user, Entry<?> entry) {
209 super(user);
210 this.entry = entry;
211 }
212
213 @Override
214 public void write(DataOutput output) throws IOException {
215 super.write(output);
216 PacketHelper.writeEntry(output, entry);
217 }
218
219 @Override
220 public String translate() {
221 return String.format(I18n.translate("message.edit_docs.text"), user, entry);
222 }
223
224 @Override
225 public Type getType() {
226 return Type.EDIT_DOCS;
227 }
228
229 @Override
230 public boolean equals(Object o) {
231 if (this == o) return true;
232 if (o == null || getClass() != o.getClass()) return false;
233 if (!super.equals(o)) return false;
234 EditDocs editDocs = (EditDocs) o;
235 return Objects.equals(entry, editDocs.entry);
236 }
237
238 @Override
239 public int hashCode() {
240 return Objects.hash(super.hashCode(), entry);
241 }
242
243 @Override
244 public String toString() {
245 return String.format("Message.EditDocs { user: '%s', entry: %s }", user, entry);
246 }
247
248 }
249
250 public static final class MarkDeobf extends Message {
251
252 public final Entry<?> entry;
253
254 private MarkDeobf(String user, Entry<?> entry) {
255 super(user);
256 this.entry = entry;
257 }
258
259 @Override
260 public void write(DataOutput output) throws IOException {
261 super.write(output);
262 PacketHelper.writeEntry(output, entry);
263 }
264
265 @Override
266 public String translate() {
267 return String.format(I18n.translate("message.mark_deobf.text"), user, entry);
268 }
269
270 @Override
271 public Type getType() {
272 return Type.MARK_DEOBF;
273 }
274
275 @Override
276 public boolean equals(Object o) {
277 if (this == o) return true;
278 if (o == null || getClass() != o.getClass()) return false;
279 if (!super.equals(o)) return false;
280 MarkDeobf markDeobf = (MarkDeobf) o;
281 return Objects.equals(entry, markDeobf.entry);
282 }
283
284 @Override
285 public int hashCode() {
286 return Objects.hash(super.hashCode(), entry);
287 }
288
289 @Override
290 public String toString() {
291 return String.format("Message.MarkDeobf { user: '%s', entry: %s }", user, entry);
292 }
293
294 }
295
296 public static final class RemoveMapping extends Message {
297
298 public final Entry<?> entry;
299
300 private RemoveMapping(String user, Entry<?> entry) {
301 super(user);
302 this.entry = entry;
303 }
304
305 @Override
306 public void write(DataOutput output) throws IOException {
307 super.write(output);
308 PacketHelper.writeEntry(output, entry);
309 }
310
311 @Override
312 public String translate() {
313 return String.format(I18n.translate("message.remove_mapping.text"), user, entry);
314 }
315
316 @Override
317 public Type getType() {
318 return Type.REMOVE_MAPPING;
319 }
320
321 @Override
322 public boolean equals(Object o) {
323 if (this == o) return true;
324 if (o == null || getClass() != o.getClass()) return false;
325 if (!super.equals(o)) return false;
326 RemoveMapping that = (RemoveMapping) o;
327 return Objects.equals(entry, that.entry);
328 }
329
330 @Override
331 public int hashCode() {
332 return Objects.hash(super.hashCode(), entry);
333 }
334
335 @Override
336 public String toString() {
337 return String.format("Message.RemoveMapping { user: '%s', entry: %s }", user, entry);
338 }
339
340 }
341
342 public static final class Rename extends Message {
343
344 public final Entry<?> entry;
345 public final String newName;
346
347 private Rename(String user, Entry<?> entry, String newName) {
348 super(user);
349 this.entry = entry;
350 this.newName = newName;
351 }
352
353 @Override
354 public void write(DataOutput output) throws IOException {
355 super.write(output);
356 PacketHelper.writeEntry(output, entry);
357 PacketHelper.writeString(output, newName);
358 }
359
360 @Override
361 public String translate() {
362 return String.format(I18n.translate("message.rename.text"), user, entry, newName);
363 }
364
365 @Override
366 public Type getType() {
367 return Type.RENAME;
368 }
369
370 @Override
371 public boolean equals(Object o) {
372 if (this == o) return true;
373 if (o == null || getClass() != o.getClass()) return false;
374 if (!super.equals(o)) return false;
375 Rename rename = (Rename) o;
376 return Objects.equals(entry, rename.entry) &&
377 Objects.equals(newName, rename.newName);
378 }
379
380 @Override
381 public int hashCode() {
382 return Objects.hash(super.hashCode(), entry, newName);
383 }
384
385 @Override
386 public String toString() {
387 return String.format("Message.Rename { user: '%s', entry: %s, newName: '%s' }", user, entry, newName);
388 }
389
390 }
391
392}
diff --git a/src/main/java/cuchaz/enigma/utils/Pair.java b/src/main/java/cuchaz/enigma/utils/Pair.java
deleted file mode 100644
index bf02cef..0000000
--- a/src/main/java/cuchaz/enigma/utils/Pair.java
+++ /dev/null
@@ -1,26 +0,0 @@
1package cuchaz.enigma.utils;
2
3import java.util.Objects;
4
5public class Pair<A, B> {
6 public final A a;
7 public final B b;
8
9 public Pair(A a, B b) {
10 this.a = a;
11 this.b = b;
12 }
13
14 @Override
15 public int hashCode() {
16 return Objects.hashCode(a) * 31 +
17 Objects.hashCode(b);
18 }
19
20 @Override
21 public boolean equals(Object o) {
22 return o instanceof Pair &&
23 Objects.equals(a, ((Pair<?, ?>) o).a) &&
24 Objects.equals(b, ((Pair<?, ?>) o).b);
25 }
26}
diff --git a/src/main/java/cuchaz/enigma/utils/ReadableToken.java b/src/main/java/cuchaz/enigma/utils/ReadableToken.java
deleted file mode 100644
index de152fe..0000000
--- a/src/main/java/cuchaz/enigma/utils/ReadableToken.java
+++ /dev/null
@@ -1,30 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.utils;
13
14public class ReadableToken {
15
16 public int line;
17 public int startColumn;
18 public int endColumn;
19
20 public ReadableToken(int line, int startColumn, int endColumn) {
21 this.line = line;
22 this.startColumn = startColumn;
23 this.endColumn = endColumn;
24 }
25
26 @Override
27 public String toString() {
28 return "line " + line + " columns " + startColumn + "-" + endColumn;
29 }
30}
diff --git a/src/main/java/cuchaz/enigma/utils/Utils.java b/src/main/java/cuchaz/enigma/utils/Utils.java
deleted file mode 100644
index b45b00d..0000000
--- a/src/main/java/cuchaz/enigma/utils/Utils.java
+++ /dev/null
@@ -1,179 +0,0 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.utils;
13
14import com.google.common.io.CharStreams;
15import org.objectweb.asm.Opcodes;
16
17import javax.swing.*;
18import javax.swing.text.BadLocationException;
19import javax.swing.text.JTextComponent;
20import java.awt.*;
21import java.awt.event.MouseEvent;
22import java.io.IOException;
23import java.io.InputStream;
24import java.io.InputStreamReader;
25import java.net.URI;
26import java.net.URISyntaxException;
27import java.nio.charset.StandardCharsets;
28import java.nio.file.Files;
29import java.nio.file.Path;
30import java.security.MessageDigest;
31import java.security.NoSuchAlgorithmException;
32import java.util.*;
33import java.util.List;
34import java.util.stream.Collectors;
35import java.util.zip.ZipEntry;
36import java.util.zip.ZipFile;
37
38public class Utils {
39
40 public static final int ASM_VERSION = Opcodes.ASM8;
41
42 public static int combineHashesOrdered(Object... objs) {
43 final int prime = 67;
44 int result = 1;
45 for (Object obj : objs) {
46 result *= prime;
47 if (obj != null) {
48 result += obj.hashCode();
49 }
50 }
51 return result;
52 }
53
54 public static int combineHashesOrdered(List<Object> objs) {
55 final int prime = 67;
56 int result = 1;
57 for (Object obj : objs) {
58 result *= prime;
59 if (obj != null) {
60 result += obj.hashCode();
61 }
62 }
63 return result;
64 }
65
66 public static String readStreamToString(InputStream in) throws IOException {
67 return CharStreams.toString(new InputStreamReader(in, "UTF-8"));
68 }
69
70 public static String readResourceToString(String path) throws IOException {
71 InputStream in = Utils.class.getResourceAsStream(path);
72 if (in == null) {
73 throw new IllegalArgumentException("Resource not found! " + path);
74 }
75 return readStreamToString(in);
76 }
77
78 public static void openUrl(String url) {
79 if (Desktop.isDesktopSupported()) {
80 Desktop desktop = Desktop.getDesktop();
81 try {
82 desktop.browse(new URI(url));
83 } catch (IOException ex) {
84 throw new Error(ex);
85 } catch (URISyntaxException ex) {
86 throw new IllegalArgumentException(ex);
87 }
88 }
89 }
90
91 public static JLabel unboldLabel(JLabel label) {
92 Font font = label.getFont();
93 label.setFont(font.deriveFont(font.getStyle() & ~Font.BOLD));
94 return label;
95 }
96
97 public static void showToolTipNow(JComponent component) {
98 // HACKHACK: trick the tooltip manager into showing the tooltip right now
99 ToolTipManager manager = ToolTipManager.sharedInstance();
100 int oldDelay = manager.getInitialDelay();
101 manager.setInitialDelay(0);
102 manager.mouseMoved(new MouseEvent(component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, 0, 0, 0, false));
103 manager.setInitialDelay(oldDelay);
104 }
105
106 public static Rectangle safeModelToView(JTextComponent component, int modelPos) {
107 if (modelPos < 0) {
108 modelPos = 0;
109 } else if (modelPos >= component.getText().length()) {
110 modelPos = component.getText().length();
111 }
112 try {
113 return component.modelToView(modelPos);
114 } catch (BadLocationException e) {
115 throw new RuntimeException(e);
116 }
117 }
118
119 public static boolean getSystemPropertyAsBoolean(String property, boolean defValue) {
120 String value = System.getProperty(property);
121 return value == null ? defValue : Boolean.parseBoolean(value);
122 }
123
124 public static void delete(Path path) throws IOException {
125 if (Files.exists(path)) {
126 for (Path p : Files.walk(path).sorted(Comparator.reverseOrder()).collect(Collectors.toList())) {
127 Files.delete(p);
128 }
129 }
130 }
131
132 public static byte[] zipSha1(Path path) throws IOException {
133 MessageDigest digest;
134 try {
135 digest = MessageDigest.getInstance("SHA-1");
136 } catch (NoSuchAlgorithmException e) {
137 // Algorithm guaranteed to be supported
138 throw new RuntimeException(e);
139 }
140 try (ZipFile zip = new ZipFile(path.toFile())) {
141 List<? extends ZipEntry> entries = Collections.list(zip.entries());
142 // only compare classes (some implementations may not generate directory entries)
143 entries.removeIf(entry -> !entry.getName().toLowerCase(Locale.ROOT).endsWith(".class"));
144 // different implementations may add zip entries in a different order
145 entries.sort(Comparator.comparing(ZipEntry::getName));
146 byte[] buffer = new byte[8192];
147 for (ZipEntry entry : entries) {
148 digest.update(entry.getName().getBytes(StandardCharsets.UTF_8));
149 try (InputStream in = zip.getInputStream(entry)) {
150 int n;
151 while ((n = in.read(buffer)) != -1) {
152 digest.update(buffer, 0, n);
153 }
154 }
155 }
156 }
157 return digest.digest();
158 }
159
160 public static String caplisiseCamelCase(String input){
161 StringJoiner stringJoiner = new StringJoiner(" ");
162 for (String word : input.toLowerCase(Locale.ROOT).split("_")) {
163 stringJoiner.add(word.substring(0, 1).toUpperCase(Locale.ROOT) + word.substring(1));
164 }
165 return stringJoiner.toString();
166 }
167
168 public static boolean isBlank(String input) {
169 if (input == null) {
170 return true;
171 }
172 for (int i = 0; i < input.length(); i++) {
173 if (!Character.isWhitespace(input.charAt(i))) {
174 return false;
175 }
176 }
177 return true;
178 }
179}
diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java b/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java
deleted file mode 100644
index 48b255f..0000000
--- a/src/main/java/cuchaz/enigma/utils/search/SearchEntry.java
+++ /dev/null
@@ -1,17 +0,0 @@
1package cuchaz.enigma.utils.search;
2
3import java.util.List;
4
5public interface SearchEntry {
6
7 List<String> getSearchableNames();
8
9 /**
10 * Returns a type that uniquely identifies this search entry across possible changes.
11 * This is used for tracking the amount of times this entry has been selected.
12 *
13 * @return a unique identifier for this search entry
14 */
15 String getIdentifier();
16
17}
diff --git a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java b/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java
deleted file mode 100644
index a51afbb..0000000
--- a/src/main/java/cuchaz/enigma/utils/search/SearchUtil.java
+++ /dev/null
@@ -1,268 +0,0 @@
1package cuchaz.enigma.utils.search;
2
3import java.util.*;
4import java.util.concurrent.Executor;
5import java.util.concurrent.Executors;
6import java.util.concurrent.atomic.AtomicBoolean;
7import java.util.concurrent.atomic.AtomicInteger;
8import java.util.concurrent.locks.Lock;
9import java.util.concurrent.locks.ReentrantLock;
10import java.util.function.BiFunction;
11import java.util.stream.Collectors;
12import java.util.stream.Stream;
13
14import cuchaz.enigma.utils.Pair;
15
16public class SearchUtil<T extends SearchEntry> {
17
18 private final Map<T, Entry<T>> entries = new HashMap<>();
19 private final Map<String, Integer> hitCount = new HashMap<>();
20 private final Executor searchExecutor = Executors.newWorkStealingPool();
21
22 public void add(T entry) {
23 Entry<T> e = Entry.from(entry);
24 entries.put(entry, e);
25 }
26
27 public void add(Entry<T> entry) {
28 entries.put(entry.searchEntry, entry);
29 }
30
31 public void addAll(Collection<T> entries) {
32 this.entries.putAll(entries.parallelStream().collect(Collectors.toMap(e -> e, Entry::from)));
33 }
34
35 public void remove(T entry) {
36 entries.remove(entry);
37 }
38
39 public void clear() {
40 entries.clear();
41 }
42
43 public void clearHits() {
44 hitCount.clear();
45 }
46
47 public Stream<T> search(String term) {
48 return entries.values().parallelStream()
49 .map(e -> new Pair<>(e, e.getScore(term, hitCount.getOrDefault(e.searchEntry.getIdentifier(), 0))))
50 .filter(e -> e.b > 0)
51 .sorted(Comparator.comparingDouble(o -> -o.b))
52 .map(e -> e.a.searchEntry)
53 .sequential();
54 }
55
56 public SearchControl asyncSearch(String term, SearchResultConsumer<T> consumer) {
57 Map<String, Integer> hitCount = new HashMap<>(this.hitCount);
58 Map<T, Entry<T>> entries = new HashMap<>(this.entries);
59 float[] scores = new float[entries.size()];
60 Lock scoresLock = new ReentrantLock();
61 AtomicInteger size = new AtomicInteger();
62 AtomicBoolean control = new AtomicBoolean(false);
63 AtomicInteger elapsed = new AtomicInteger();
64 for (Entry<T> value : entries.values()) {
65 searchExecutor.execute(() -> {
66 try {
67 if (control.get()) return;
68 float score = value.getScore(term, hitCount.getOrDefault(value.searchEntry.getIdentifier(), 0));
69 if (score <= 0) return;
70 score = -score; // sort descending
71 try {
72 scoresLock.lock();
73 if (control.get()) return;
74 int dataSize = size.getAndIncrement();
75 int index = Arrays.binarySearch(scores, 0, dataSize, score);
76 if (index < 0) {
77 index = ~index;
78 }
79 System.arraycopy(scores, index, scores, index + 1, dataSize - index);
80 scores[index] = score;
81 consumer.add(index, value.searchEntry);
82 } finally {
83 scoresLock.unlock();
84 }
85 } finally {
86 elapsed.incrementAndGet();
87 }
88 });
89 }
90
91 return new SearchControl() {
92 @Override
93 public void stop() {
94 control.set(true);
95 }
96
97 @Override
98 public boolean isFinished() {
99 return entries.size() == elapsed.get();
100 }
101
102 @Override
103 public float getProgress() {
104 return (float) elapsed.get() / entries.size();
105 }
106 };
107 }
108
109 public void hit(T entry) {
110 if (entries.containsKey(entry)) {
111 hitCount.compute(entry.getIdentifier(), (_id, i) -> i == null ? 1 : i + 1);
112 }
113 }
114
115 public static final class Entry<T extends SearchEntry> {
116
117 public final T searchEntry;
118 private final String[][] components;
119
120 private Entry(T searchEntry, String[][] components) {
121 this.searchEntry = searchEntry;
122 this.components = components;
123 }
124
125 public float getScore(String term, int hits) {
126 String ucTerm = term.toUpperCase(Locale.ROOT);
127 float maxScore = (float) Arrays.stream(components)
128 .mapToDouble(name -> getScoreFor(ucTerm, name))
129 .max().orElse(0.0);
130 return maxScore * (hits + 1);
131 }
132
133 /**
134 * Computes the score for the given <code>name</code> against the given search term.
135 *
136 * @param term the search term (expected to be upper-case)
137 * @param name the entry name, split at word boundaries (see {@link Entry#wordwiseSplit(String)})
138 * @return the computed score for the entry
139 */
140 private static float getScoreFor(String term, String[] name) {
141 int totalLength = Arrays.stream(name).mapToInt(String::length).sum();
142 float scorePerChar = 1f / totalLength;
143
144 // This map contains a snapshot of all the states the search has
145 // been in. The keys are the remaining characters of the search
146 // term, the values are the maximum scores for that remaining
147 // search term part.
148 Map<String, Float> snapshots = new HashMap<>();
149 snapshots.put(term, 0f);
150
151 // For each component, start at each existing snapshot, searching
152 // for the next longest match, and calculate the new score for each
153 // match length until the maximum. Then the new scores are put back
154 // into the snapshot map.
155 for (int componentIndex = 0; componentIndex < name.length; componentIndex++) {
156 String component = name[componentIndex];
157 float posMultiplier = (name.length - componentIndex) * 0.3f;
158 Map<String, Float> newSnapshots = new HashMap<>();
159 for (Map.Entry<String, Float> snapshot : snapshots.entrySet()) {
160 String remaining = snapshot.getKey();
161 float score = snapshot.getValue();
162 component = component.toUpperCase(Locale.ROOT);
163 int l = compareEqualLength(remaining, component);
164 for (int i = 1; i <= l; i++) {
165 float baseScore = scorePerChar * i;
166 float chainBonus = (i - 1) * 0.5f;
167 merge(newSnapshots, Collections.singletonMap(remaining.substring(i), score + baseScore * posMultiplier + chainBonus), Math::max);
168 }
169 }
170 merge(snapshots, newSnapshots, Math::max);
171 }
172
173 // Only return the score for when the search term was completely
174 // consumed.
175 return snapshots.getOrDefault("", 0f);
176 }
177
178 private static <K, V> void merge(Map<K, V> self, Map<K, V> source, BiFunction<V, V, V> combiner) {
179 source.forEach((k, v) -> self.compute(k, (_k, v1) -> v1 == null ? v : v == null ? v1 : combiner.apply(v, v1)));
180 }
181
182 public static <T extends SearchEntry> Entry<T> from(T e) {
183 String[][] components = e.getSearchableNames().parallelStream()
184 .map(Entry::wordwiseSplit)
185 .toArray(String[][]::new);
186 return new Entry<>(e, components);
187 }
188
189 private static int compareEqualLength(String s1, String s2) {
190 int len = 0;
191 while (len < s1.length() && len < s2.length() && s1.charAt(len) == s2.charAt(len)) {
192 len += 1;
193 }
194 return len;
195 }
196
197 /**
198 * Splits the given input into components, trying to detect word parts.
199 * <p>
200 * Example of how words get split (using <code>|</code> as seperator):
201 * <p><code>MinecraftClientGame -> Minecraft|Client|Game</code></p>
202 * <p><code>HTTPInputStream -> HTTP|Input|Stream</code></p>
203 * <p><code>class_932 -> class|_|932</code></p>
204 * <p><code>X11FontManager -> X|11|Font|Manager</code></p>
205 * <p><code>openHTTPConnection -> open|HTTP|Connection</code></p>
206 * <p><code>open_http_connection -> open|_|http|_|connection</code></p>
207 *
208 * @param input the input to split
209 * @return the resulting components
210 */
211 private static String[] wordwiseSplit(String input) {
212 List<String> list = new ArrayList<>();
213 while (!input.isEmpty()) {
214 int take;
215 if (Character.isLetter(input.charAt(0))) {
216 if (input.length() == 1) {
217 take = 1;
218 } else {
219 boolean nextSegmentIsUppercase = Character.isUpperCase(input.charAt(0)) && Character.isUpperCase(input.charAt(1));
220 if (nextSegmentIsUppercase) {
221 int nextLowercase = 1;
222 while (Character.isUpperCase(input.charAt(nextLowercase))) {
223 nextLowercase += 1;
224 if (nextLowercase == input.length()) {
225 nextLowercase += 1;
226 break;
227 }
228 }
229 take = nextLowercase - 1;
230 } else {
231 int nextUppercase = 1;
232 while (nextUppercase < input.length() && Character.isLowerCase(input.charAt(nextUppercase))) {
233 nextUppercase += 1;
234 }
235 take = nextUppercase;
236 }
237 }
238 } else if (Character.isDigit(input.charAt(0))) {
239 int nextNonNum = 1;
240 while (nextNonNum < input.length() && Character.isLetter(input.charAt(nextNonNum)) && !Character.isLowerCase(input.charAt(nextNonNum))) {
241 nextNonNum += 1;
242 }
243 take = nextNonNum;
244 } else {
245 take = 1;
246 }
247 list.add(input.substring(0, take));
248 input = input.substring(take);
249 }
250 return list.toArray(new String[0]);
251 }
252
253 }
254
255 @FunctionalInterface
256 public interface SearchResultConsumer<T extends SearchEntry> {
257 void add(int index, T entry);
258 }
259
260 public interface SearchControl {
261 void stop();
262
263 boolean isFinished();
264
265 float getProgress();
266 }
267
268}