diff --git a/README.md b/README.md index 82a58df..2e1339a 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ Keep the latest library, remove upstream problems and absorb upstream ideas. ![GitHub all releases](https://img.shields.io/github/downloads/Ecdcaeb/JavaOctetEditor/total?style=flat-square) ![GitHub release (latest by date)](https://img.shields.io/github/v/release/Ecdcaeb/JavaOctetEditor?style=flat-square) +![GitHub release (latest SemVer including pre-releases)](https://img.shields.io/github/v/release/Ecdcaeb/JavaOctetEditor?include_prereleases&style=flat-square) ![GitHub](https://img.shields.io/github/license/Ecdcaeb/JavaOctetEditor?style=flat-square) ![](https://user-images.githubusercontent.com/32991121/190947407-bbc6642e-2c9d-46f3-921c-6558c74272cf.png) diff --git a/build.gradle b/build.gradle index 335d736..144f6a3 100644 --- a/build.gradle +++ b/build.gradle @@ -41,6 +41,7 @@ dependencies { implementation 'com.formdev:flatlaf:3.5.2' implementation 'com.formdev:flatlaf-extras:3.5.2' + implementation 'com.github.weisj:jsvg:1.6.1' //noinspection GradlePackageUpdate implementation 'com.miglayout:miglayout-swing:11.4.2' implementation 'com.github.bobbylight:RSyntaxTextArea:3.5.2' @@ -65,11 +66,11 @@ shadowJar { dependencies { include(dependency('com.formdev:.*')) include(dependency('com.miglayout:.*')) + include(dependency('com.github.weisj:.*')) include(dependency('com.github.bobbylight:RSyntaxTextArea')) include(dependency('org.ow2.asm:.*')) include(dependency('org.benf:cfr')) include(dependency('com.github.mstrobel.procyon:procyon-decompiler')) - include(dependency('org.javassist:javassist')) include(dependency('com.google.code.gson:gson')) include(dependency('org.tinylog:.*')) include(dependency('org.vineflower:vineflower')) diff --git a/src/main/java/cn/enaium/joe/Instance.java b/src/main/java/cn/enaium/joe/Instance.java new file mode 100644 index 0000000..2859c8d --- /dev/null +++ b/src/main/java/cn/enaium/joe/Instance.java @@ -0,0 +1,6 @@ +package cn.enaium.joe; + +public enum Instance { + INSTANCE; + public int classTabIndex = 0; +} diff --git a/src/main/java/cn/enaium/joe/JavaOctetEditor.java b/src/main/java/cn/enaium/joe/JavaOctetEditor.java index f3ba9c1..f6fab23 100644 --- a/src/main/java/cn/enaium/joe/JavaOctetEditor.java +++ b/src/main/java/cn/enaium/joe/JavaOctetEditor.java @@ -17,7 +17,9 @@ package cn.enaium.joe; import cn.enaium.joe.config.ConfigManager; +import cn.enaium.joe.config.extend.ApplicationConfig; import cn.enaium.joe.event.EventManager; +import cn.enaium.joe.gui.panel.BorderPanel; import cn.enaium.joe.gui.panel.BottomPanel; import cn.enaium.joe.gui.panel.file.tree.CenterPanel; import cn.enaium.joe.gui.panel.file.tabbed.FileTabbedPanel; @@ -25,10 +27,8 @@ import cn.enaium.joe.gui.panel.menu.*; import cn.enaium.joe.jar.Jar; import cn.enaium.joe.task.TaskManager; -import cn.enaium.joe.util.BytecodeTokenMaker; -import cn.enaium.joe.util.LangUtil; -import cn.enaium.joe.util.MessageUtil; -import cn.enaium.joe.util.ReflectUtil; +import cn.enaium.joe.util.*; +import com.formdev.flatlaf.FlatDarkLaf; import com.formdev.flatlaf.extras.FlatSVGIcon; import org.fife.ui.rsyntaxtextarea.AbstractTokenMakerFactory; import org.fife.ui.rsyntaxtextarea.TokenMakerFactory; @@ -45,7 +45,7 @@ public class JavaOctetEditor { public static final String TITLE = "JavaOctetEditor"; - public JFrame window = new JFrame(TITLE); + public JFrame window; private Jar jar; @@ -64,11 +64,22 @@ public class JavaOctetEditor { public JavaOctetEditor() { instance = this; + event = new EventManager(); config = new ConfigManager(); config.load(); task = new TaskManager(); Runtime.getRuntime().addShutdownHook(new Thread(config::save)); + + Integer value = config.getByClass(ApplicationConfig.class).scale.getValue(); + + if (value > 0) { + System.setProperty("sun.java2d.uiScale", value.toString()); + } + + FlatDarkLaf.setup(); + UIManager.put("Tree.paintLines", true); + fileTabbedPanel = new FileTabbedPanel(); fileTree = new FileTree(); bottomPanel = new BottomPanel(); @@ -77,10 +88,10 @@ public JavaOctetEditor() { public void run() { ToolTipManager.sharedInstance().setInitialDelay(0); - + FlatDarkLaf.setup(); AbstractTokenMakerFactory abstractTokenMakerFactory = (AbstractTokenMakerFactory) TokenMakerFactory.getDefaultInstance(); abstractTokenMakerFactory.putMapping("text/custom", BytecodeTokenMaker.class.getName()); - + window = new JFrame(TITLE); window.setIconImage(new FlatSVGIcon("icons/logo.svg").getImage()); window.setJMenuBar(new JMenuBar() {{ @@ -98,9 +109,9 @@ public void run() { add(new HelpMenu()); }}); - window.setContentPane(new JPanel(new BorderLayout()) {{ - add(new CenterPanel(), BorderLayout.CENTER); - add(bottomPanel, BorderLayout.SOUTH); + window.setContentPane(new BorderPanel() {{ + setCenter(new CenterPanel()); + setBottom(bottomPanel); }}); @@ -115,7 +126,7 @@ public void windowClosing(WindowEvent e) { }); } }); - window.setSize(1000, 600); + window.setSize(Util.screenSize(1000, 600)); window.setLocationRelativeTo(null); window.setVisible(true); } diff --git a/src/main/java/cn/enaium/joe/Main.java b/src/main/java/cn/enaium/joe/Main.java index 34561a7..5417ffd 100644 --- a/src/main/java/cn/enaium/joe/Main.java +++ b/src/main/java/cn/enaium/joe/Main.java @@ -16,6 +16,7 @@ package cn.enaium.joe; +import cn.enaium.joe.config.extend.ApplicationConfig; import cn.enaium.joe.jar.Jar; import cn.enaium.joe.util.*; import com.formdev.flatlaf.FlatDarkLaf; @@ -77,8 +78,6 @@ private static void launch() { System.setErr(new TinyLogPrintStream(System.err, STDERR)); Logger.info("DIR:{}", System.getProperty("user.dir")); - FlatDarkLaf.setup(); - UIManager.put("Tree.paintLines", true); new JavaOctetEditor().run(); } diff --git a/src/main/java/cn/enaium/joe/compiler/Compiler.java b/src/main/java/cn/enaium/joe/compiler/Compiler.java new file mode 100644 index 0000000..b9207bf --- /dev/null +++ b/src/main/java/cn/enaium/joe/compiler/Compiler.java @@ -0,0 +1,77 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.compiler; + +import javax.tools.*; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * @author Enaium + * @since 1.4.0 + */ +public class Compiler { + private final Map javaFileObjectMap = new HashMap<>(); + + private DiagnosticListener listener; + + public void addSource(String name, String content) { + javaFileObjectMap.put(name, new VirtualJavaFileObject(name, content)); + } + + public Map getClasses() { + return javaFileObjectMap.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().getBytecode())); + } + + @SuppressWarnings("unchecked") + public boolean compile() { + JavaCompiler compiler = ToolProvider.getSystemJavaCompiler(); + VirtualFileManager fileManager = new VirtualFileManager(compiler.getStandardFileManager(null, null, StandardCharsets.UTF_8)); + try { + JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, ((DiagnosticListener) (Object) listener), null, null, javaFileObjectMap.values()); + Boolean b = task.call(); + return b != null && b; + } catch (Exception e) { + return false; + } + } + + public void setListener(DiagnosticListener listener) { + this.listener = listener; + } + + private final class VirtualFileManager extends ForwardingJavaFileManager { + public VirtualFileManager(JavaFileManager fileManager) { + super(fileManager); + } + + @Override + public JavaFileObject getJavaFileForOutput(JavaFileManager.Location location, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException { + if (JavaFileObject.Kind.CLASS == kind) { + VirtualJavaFileObject virtualJavaFileObject = new VirtualJavaFileObject(className, null); + javaFileObjectMap.put(className, virtualJavaFileObject); + return virtualJavaFileObject; + } else { + return super.getJavaFileForOutput(location, className, kind, sibling); + } + } + } +} diff --git a/src/main/java/cn/enaium/joe/compiler/VirtualJavaFileObject.java b/src/main/java/cn/enaium/joe/compiler/VirtualJavaFileObject.java new file mode 100644 index 0000000..0e1e2b8 --- /dev/null +++ b/src/main/java/cn/enaium/joe/compiler/VirtualJavaFileObject.java @@ -0,0 +1,52 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.compiler; + +import javax.tools.SimpleJavaFileObject; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; + +/** + * @author Enaium + * @since 1.4.0 + */ +public class VirtualJavaFileObject extends SimpleJavaFileObject { + + private final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + + private final String content; + + protected VirtualJavaFileObject(String className, String content) { + super(URI.create("string:///" + className.replace(".", "/") + Kind.SOURCE.extension), Kind.SOURCE); + this.content = content; + } + + public byte[] getBytecode() { + return outputStream.toByteArray(); + } + + @Override + public ByteArrayOutputStream openOutputStream() { + return outputStream; + } + + @Override + public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException { + return content; + } +} diff --git a/src/main/java/cn/enaium/joe/config/ConfigManager.java b/src/main/java/cn/enaium/joe/config/ConfigManager.java index fe1e663..47a6d10 100644 --- a/src/main/java/cn/enaium/joe/config/ConfigManager.java +++ b/src/main/java/cn/enaium/joe/config/ConfigManager.java @@ -16,14 +16,12 @@ package cn.enaium.joe.config; -import cn.enaium.joe.config.extend.ApplicationConfig; -import cn.enaium.joe.config.extend.CFRConfig; -import cn.enaium.joe.config.extend.FernFlowerConfig; -import cn.enaium.joe.config.extend.ProcyonConfig; +import cn.enaium.joe.config.extend.*; import cn.enaium.joe.config.value.*; import cn.enaium.joe.util.MessageUtil; import com.google.gson.*; +import javax.swing.*; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; @@ -44,6 +42,7 @@ public ConfigManager() { addByInstance(new CFRConfig()); addByInstance(new FernFlowerConfig()); addByInstance(new ProcyonConfig()); + addByInstance(new KeymapConfig()); } @SuppressWarnings("unchecked") @@ -93,8 +92,9 @@ public Map> getConfigMap(Class config) { return map; } + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); private Gson gson() { - return new GsonBuilder().setPrettyPrinting().create(); + return GSON; } public void load() { @@ -106,14 +106,13 @@ public void load() { File file = new File(System.getProperty("."), config.getName() + ".json"); if (file.exists()) { JsonObject jsonObject = gson().fromJson(Files.readString(file.toPath()), JsonObject.class); - for (Field configField : klass.getDeclaredFields()) { configField.setAccessible(true); if (!jsonObject.has(configField.getName())) { continue; } - if (!jsonObject.has(configField.getName())) { + if (!jsonObject.get(configField.getName()).isJsonObject()) { continue; } @@ -126,26 +125,7 @@ public void load() { Object valueObject = configField.get(config); if (valueObject instanceof Value) { Value value = (Value) valueObject; - if (value instanceof EnableValue) { - ((EnableValue) value).setValue(valueJsonElement.getAsBoolean()); - } else if (value instanceof IntegerValue) { - ((IntegerValue) value).setValue(valueJsonElement.getAsInt()); - } else if (value instanceof ModeValue) { - ModeValue modeValue = (ModeValue) value; - if (modeValue.getMode().contains(valueJsonElement.getAsString())) { - modeValue.setValue(valueJsonElement.getAsString()); - } else { - modeValue.setValue(modeValue.getMode().get(0)); - } - } else if (value instanceof StringSetValue) { - Set strings = new HashSet<>(); - for (JsonElement jsonElement : valueJsonElement.getAsJsonArray()) { - strings.add(jsonElement.getAsString()); - } - ((StringSetValue) value).setValue(strings); - } else if (value instanceof StringValue) { - ((StringValue) value).setValue(valueJsonElement.getAsString()); - } + value.decode(valueJsonElement); } } } diff --git a/src/main/java/cn/enaium/joe/config/extend/ApplicationConfig.java b/src/main/java/cn/enaium/joe/config/extend/ApplicationConfig.java index 0539e4e..546a23d 100644 --- a/src/main/java/cn/enaium/joe/config/extend/ApplicationConfig.java +++ b/src/main/java/cn/enaium/joe/config/extend/ApplicationConfig.java @@ -19,6 +19,7 @@ import cn.enaium.joe.annotation.NoUI; import cn.enaium.joe.config.Config; import cn.enaium.joe.config.value.EnableValue; +import cn.enaium.joe.config.value.IntegerValue; import cn.enaium.joe.config.value.ModeValue; import cn.enaium.joe.config.value.StringSetValue; @@ -37,6 +38,7 @@ public class ApplicationConfig extends Config { public EnableValue compactMiddlePackage = new EnableValue("Compact Middle Package", true, "Only Hierarchical Mode"); @NoUI public final StringSetValue loadRecent = new StringSetValue("Load Recent", new HashSet<>(), ""); + public IntegerValue scale = new IntegerValue("Scale", 0, "UI scale,it doesn't scale if value 0"); public ApplicationConfig() { super("Application"); diff --git a/src/main/java/cn/enaium/joe/config/extend/KeymapConfig.java b/src/main/java/cn/enaium/joe/config/extend/KeymapConfig.java new file mode 100644 index 0000000..f3262ac --- /dev/null +++ b/src/main/java/cn/enaium/joe/config/extend/KeymapConfig.java @@ -0,0 +1,45 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.config.extend; + +import cn.enaium.joe.config.Config; +import cn.enaium.joe.config.value.KeyValue; + +import javax.swing.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; + +/** + * @author Enaium + * @since 1.4.0 + */ +public class KeymapConfig extends Config { + + public KeyValue edit = new KeyValue("Edit", KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "Edit method instruction"); + public KeyValue clone = new KeyValue("Clone", KeyStroke.getKeyStroke(KeyEvent.VK_D, InputEvent.CTRL_DOWN_MASK), "Clone method instruction"); + public KeyValue remove = new KeyValue("Remove", KeyStroke.getKeyStroke(KeyEvent.VK_Y, InputEvent.CTRL_DOWN_MASK), "Remove method instruction"); + public KeyValue copy = new KeyValue("Copy", KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.CTRL_DOWN_MASK), "Copy method instruction text"); + public KeyValue insertBefore = new KeyValue("InsertBefore", KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK | InputEvent.ALT_DOWN_MASK), "Insert method instruction before current"); + public KeyValue insertAfter = new KeyValue("InsertAfter", KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, InputEvent.CTRL_DOWN_MASK), "Insert method instruction after current"); + public KeyValue moveUp = new KeyValue("Move Up", KeyStroke.getKeyStroke(KeyEvent.VK_UP, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK), "Move method instruction up"); + public KeyValue moveDown = new KeyValue("Move Up", KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, InputEvent.CTRL_DOWN_MASK | InputEvent.SHIFT_DOWN_MASK), "Move method instruction down"); + public KeyValue save = new KeyValue("Save", KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK), "Save something"); + + public KeymapConfig() { + super("Keymap"); + } +} diff --git a/src/main/java/cn/enaium/joe/config/value/EnableValue.java b/src/main/java/cn/enaium/joe/config/value/EnableValue.java index 8507e19..07d4817 100644 --- a/src/main/java/cn/enaium/joe/config/value/EnableValue.java +++ b/src/main/java/cn/enaium/joe/config/value/EnableValue.java @@ -16,6 +16,8 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; + /** * @author Enaium * @since 0.7.0 @@ -24,4 +26,9 @@ public class EnableValue extends Value { public EnableValue(String name, Boolean value, String description) { super(Boolean.class, name, value, description); } + + @Override + public void decode(JsonElement jsonElement) { + this.setValue(jsonElement.getAsBoolean()); + } } diff --git a/src/main/java/cn/enaium/joe/config/value/IntegerValue.java b/src/main/java/cn/enaium/joe/config/value/IntegerValue.java index c76c6d5..958657a 100644 --- a/src/main/java/cn/enaium/joe/config/value/IntegerValue.java +++ b/src/main/java/cn/enaium/joe/config/value/IntegerValue.java @@ -16,6 +16,8 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; + /** * @author Enaium * @since 0.7.0 @@ -24,4 +26,9 @@ public class IntegerValue extends Value { public IntegerValue(String name, Integer value, String description) { super(Integer.class, name, value, description); } + + @Override + public void decode(JsonElement jsonElement) { + this.setValue(jsonElement.getAsInt()); + } } diff --git a/src/main/java/cn/enaium/joe/config/value/KeyValue.java b/src/main/java/cn/enaium/joe/config/value/KeyValue.java new file mode 100644 index 0000000..c1492da --- /dev/null +++ b/src/main/java/cn/enaium/joe/config/value/KeyValue.java @@ -0,0 +1,22 @@ +package cn.enaium.joe.config.value; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; + +import javax.swing.KeyStroke; + +public class KeyValue extends Value { + public KeyValue(String name, KeyStroke value, String description) { + super(KeyStroke.class, name, value, description); + } + + @Override + public void decode(JsonElement jsonElement) { + if (jsonElement.isJsonObject()){ + JsonObject jsonObject = jsonElement.getAsJsonObject(); + this.setValue(KeyStroke.getKeyStroke(jsonObject.get("keyCode").getAsInt(), jsonObject.get("modifiers").getAsInt(), jsonObject.get("onKeyRelease").getAsBoolean())); + } else { + this.setValue(KeyStroke.getKeyStroke(jsonElement.getAsString())); + } + } +} diff --git a/src/main/java/cn/enaium/joe/config/value/ModeValue.java b/src/main/java/cn/enaium/joe/config/value/ModeValue.java index ce099e1..a1a2b91 100644 --- a/src/main/java/cn/enaium/joe/config/value/ModeValue.java +++ b/src/main/java/cn/enaium/joe/config/value/ModeValue.java @@ -16,6 +16,7 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; import com.google.gson.annotations.Expose; import java.util.List; @@ -41,4 +42,13 @@ public List getMode() { public void setMode(List mode) { this.mode = mode; } + + @Override + public void decode(JsonElement jsonElement) { + if (this.getMode().contains(jsonElement.getAsString())) { + this.setValue(jsonElement.getAsString()); + } else { + this.setValue(this.getMode().getFirst()); + } + } } diff --git a/src/main/java/cn/enaium/joe/config/value/StringSetValue.java b/src/main/java/cn/enaium/joe/config/value/StringSetValue.java index 48519a7..83114d7 100644 --- a/src/main/java/cn/enaium/joe/config/value/StringSetValue.java +++ b/src/main/java/cn/enaium/joe/config/value/StringSetValue.java @@ -16,9 +16,11 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; +import java.util.HashSet; import java.util.Set; /** @@ -30,4 +32,13 @@ public class StringSetValue extends Value>{ public StringSetValue(String name, Set value, String description) { super(TYPE, name, value, description); } + + @Override + public void decode(JsonElement jsonElement) { + Set strings = new HashSet<>(); + for (JsonElement element : jsonElement.getAsJsonArray()) { + strings.add(element.getAsString()); + } + this.setValue(strings); + } } diff --git a/src/main/java/cn/enaium/joe/config/value/StringValue.java b/src/main/java/cn/enaium/joe/config/value/StringValue.java index 3902e04..52268cf 100644 --- a/src/main/java/cn/enaium/joe/config/value/StringValue.java +++ b/src/main/java/cn/enaium/joe/config/value/StringValue.java @@ -16,6 +16,8 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; + /** * @author Enaium * @since 0.7.0 @@ -24,4 +26,9 @@ public class StringValue extends Value { public StringValue(String name, String value, String description) { super(String.class, name, value, description); } + + @Override + public void decode(JsonElement jsonElement) { + this.setValue(jsonElement.getAsString()); + } } diff --git a/src/main/java/cn/enaium/joe/config/value/Value.java b/src/main/java/cn/enaium/joe/config/value/Value.java index f281580..21e0a1a 100644 --- a/src/main/java/cn/enaium/joe/config/value/Value.java +++ b/src/main/java/cn/enaium/joe/config/value/Value.java @@ -16,6 +16,8 @@ package cn.enaium.joe.config.value; +import com.google.gson.JsonElement; + import java.lang.reflect.Type; import java.util.HashSet; import java.util.Set; @@ -24,7 +26,7 @@ * @author Enaium * @since 0.7.0 */ -public class Value { +public abstract class Value { private transient final Type type; private final String name; private T value; @@ -62,6 +64,8 @@ public void setValue(T value) { } } + public abstract void decode(JsonElement jsonElement); + public void addListener(ConfigValueListener listener) { this.listeners.add(listener); } diff --git a/src/main/java/cn/enaium/joe/dialog/ConfigDialog.java b/src/main/java/cn/enaium/joe/dialog/ConfigDialog.java index 36b348a..9494588 100644 --- a/src/main/java/cn/enaium/joe/dialog/ConfigDialog.java +++ b/src/main/java/cn/enaium/joe/dialog/ConfigDialog.java @@ -25,10 +25,11 @@ import net.miginfocom.swing.MigLayout; import javax.swing.*; -import javax.swing.border.EmptyBorder; import javax.swing.event.DocumentEvent; import javax.swing.event.DocumentListener; import java.awt.*; +import java.awt.event.KeyAdapter; +import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.lang.reflect.Field; @@ -85,6 +86,7 @@ public void changedUpdate(DocumentEvent e) { } else if (o instanceof IntegerValue) { IntegerValue integerValue = (IntegerValue) o; add(new JSpinner() {{ + setValue(integerValue.getValue()); addChangeListener(e -> integerValue.setValue(Integer.parseInt(getValue().toString()))); }}, "wrap"); } else if (o instanceof EnableValue) { @@ -110,6 +112,20 @@ public void changedUpdate(DocumentEvent e) { }); } }}, "wrap"); + } else if (o instanceof KeyValue) { + KeyValue keyValue = (KeyValue) o; + add(new JButton(keyValue.getValue().toString()) {{ + addKeyListener(new KeyAdapter() { + @Override + public void keyPressed(KeyEvent e) { + if (e.getKeyChar() != 65535) { + KeyStroke newKey = KeyStroke.getKeyStroke(e.getKeyCode(), e.getModifiers()); + keyValue.setValue(newKey); + setText(newKey.toString()); + } + } + }); + }}, "wrap"); } } } catch (IllegalAccessException e) { diff --git a/src/main/java/cn/enaium/joe/dialog/ContactDialog.java b/src/main/java/cn/enaium/joe/dialog/ContactDialog.java index e779b35..9e8fe04 100644 --- a/src/main/java/cn/enaium/joe/dialog/ContactDialog.java +++ b/src/main/java/cn/enaium/joe/dialog/ContactDialog.java @@ -51,6 +51,7 @@ public void addLink(JPanel panel, String name, Icon icon, String link) { panel.add(new JLabel(icon)); panel.add(new JLabel(name)); panel.add(new JLabel("" + link + "") {{ + setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR)); addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent e) { diff --git a/src/main/java/cn/enaium/joe/dialog/Dialog.java b/src/main/java/cn/enaium/joe/dialog/Dialog.java index 0561b83..d7994d8 100644 --- a/src/main/java/cn/enaium/joe/dialog/Dialog.java +++ b/src/main/java/cn/enaium/joe/dialog/Dialog.java @@ -16,15 +16,17 @@ package cn.enaium.joe.dialog; +import cn.enaium.joe.util.Util; import com.formdev.flatlaf.extras.FlatSVGIcon; import javax.swing.*; +import java.awt.*; -public class Dialog extends JFrame { +public class Dialog extends JDialog { public Dialog(String title) { - super(title); + setTitle(title); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); - setSize(800, 500); + setSize(Util.screenSize(800, 500)); setLocationRelativeTo(getOwner()); setIconImage(new FlatSVGIcon("icons/logo.svg").getImage()); } diff --git a/src/main/java/cn/enaium/joe/dialog/OptionDialog.java b/src/main/java/cn/enaium/joe/dialog/OptionDialog.java new file mode 100644 index 0000000..c367472 --- /dev/null +++ b/src/main/java/cn/enaium/joe/dialog/OptionDialog.java @@ -0,0 +1,99 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.dialog; + +import cn.enaium.joe.gui.panel.BorderPanel; +import cn.enaium.joe.gui.panel.confirm.ConfirmPanel; +import cn.enaium.joe.util.Util; + +import javax.swing.*; +import javax.swing.border.EmptyBorder; +import java.awt.*; + +/** + * @author Enaium + * @since 1.4.0 + */ +public class OptionDialog extends Dialog { + + public OptionDialog(String title, Object message, int type, Runnable confirm, Runnable cancel) { + super(title); + setContentPane(new BorderPanel() {{ + setBorder(new EmptyBorder(10, 10, 10, 10)); + JPanel bottom = new JPanel(new FlowLayout(FlowLayout.RIGHT)); + if (message != null) { + bottom.add(new JButton(UIManager.getString("OptionPane.okButton.textAndMnemonic")) {{ + addActionListener(e -> { + if (confirm != null) { + confirm.run(); + } + dispose(); + }); + }}); + + if (cancel != null) { + bottom.add(new JButton(UIManager.getString("OptionPane.cancelButton.textAndMnemonic")) {{ + addActionListener(e -> { + cancel.run(); + dispose(); + }); + }}); + } + + Component content; + if (message instanceof String) { + content = (new JLabel(message.toString())); + } else if (message instanceof ConfirmPanel) { + content = ((Component) message); + } else { + content = ((Component) message); + } + setLeft(new JLabel(getIconForType(type))); + setCenter(content); + } + setBottom(bottom); + }}); + setModal(true); + pack(); + setMinimumSize(getSize()); + } + + public OptionDialog(String title, Object message, int type) { + this(title, message, type, null, null); + } + + private Icon getIconForType(int messageType) { + if (messageType < 0 || messageType > 3) + return null; + String propertyName = null; + switch (messageType) { + case 0: + propertyName = "OptionPane.errorIcon"; + break; + case 1: + propertyName = "OptionPane.informationIcon"; + break; + case 2: + propertyName = "OptionPane.warningIcon"; + break; + case 3: + propertyName = "OptionPane.questionIcon"; + break; + } + return (Icon) UIManager.get(propertyName); + } +} diff --git a/src/main/java/cn/enaium/joe/gui/component/FileTree.java b/src/main/java/cn/enaium/joe/gui/component/FileTree.java index c8ca6e4..b71b08e 100644 --- a/src/main/java/cn/enaium/joe/gui/component/FileTree.java +++ b/src/main/java/cn/enaium/joe/gui/component/FileTree.java @@ -167,7 +167,7 @@ public void refresh(Jar jar) { FolderTreeNode folderTreeNode = new FolderTreeNode(s); if (split.length == i + 1) { - folderTreeNode = new FileTreeNode(s, stringEntry.getValue()); + folderTreeNode = new FileTreeNode(s,stringEntry.getKey()); } if (prev == null) { @@ -226,7 +226,7 @@ public void refresh(Jar jar) { name = name.substring(name.lastIndexOf("/") + 1); } - FileTreeNode classTreeNode = new FileTreeNode(name, stringEntry.getValue()); + FileTreeNode classTreeNode = new FileTreeNode(name, stringEntry.getKey()); if (folderName.isEmpty()) { resourceRoot.add(classTreeNode); diff --git a/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java b/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java index 877c2fe..3c88a2b 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/CodeAreaPanel.java @@ -21,6 +21,7 @@ import cn.enaium.joe.util.StringUtil; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Theme; +import org.fife.ui.rtextarea.RTextArea; import org.fife.ui.rtextarea.RTextScrollPane; import org.fife.ui.rtextarea.SearchContext; import org.fife.ui.rtextarea.SearchEngine; @@ -38,15 +39,13 @@ */ public class CodeAreaPanel extends BorderPanel implements ActionListener { - private final RSyntaxTextArea textArea; - private final JTextField searchField; - private final JCheckBox regexCB; - private final JCheckBox matchCaseCB; + private final RSyntaxTextArea textArea = new RSyntaxTextArea(); + private final JTextField searchField = new JTextField(30); + private final JCheckBox regexCB = new JCheckBox("Regex"); + private final JCheckBox matchCaseCB = new JCheckBox("Match Case"); public CodeAreaPanel() { - textArea = new RSyntaxTextArea(); textArea.setCodeFoldingEnabled(true); - textArea.setEditable(false); Theme theme; try { theme = Theme.load(getClass().getResourceAsStream("/org/fife/ui/rsyntaxtextarea/themes/monokai.xml")); @@ -55,8 +54,14 @@ public CodeAreaPanel() { } theme.apply(textArea); + Font defaultFont = RTextArea.getDefaultFont(); + Font font = defaultFont.deriveFont((float) UIManager.getFont("defaultFont").getSize()); + textArea.setFont(font); + textArea.setPaintMatchedBracketPair(true); + textArea.setAnimateBracketMatching(false); + + JToolBar toolBar = new JToolBar(); - searchField = new JTextField(30); toolBar.add(searchField); final JButton nextButton = new JButton(LangUtil.i18n("button.findNext")); nextButton.setActionCommand("FindNext"); @@ -67,13 +72,12 @@ public CodeAreaPanel() { prevButton.setActionCommand("FindPrev"); prevButton.addActionListener(this); toolBar.add(prevButton); - regexCB = new JCheckBox("Regex"); toolBar.add(regexCB); - matchCaseCB = new JCheckBox("Match Case"); toolBar.add(matchCaseCB); toolBar.setVisible(false); setTop(toolBar); setCenter(new RTextScrollPane(textArea) {{ + getGutter().setLineNumberFont(font); KeyStrokeUtil.register(textArea, KeyStroke.getKeyStroke(KeyEvent.VK_F, InputEvent.CTRL_DOWN_MASK), () -> { if (!StringUtil.isBlank(textArea.getSelectedText())) { searchField.setText(textArea.getSelectedText()); diff --git a/src/main/java/cn/enaium/joe/gui/panel/InheritPanel.java b/src/main/java/cn/enaium/joe/gui/panel/InheritPanel.java index 018d226..aaf454e 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/InheritPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/InheritPanel.java @@ -21,6 +21,7 @@ import cn.enaium.joe.gui.panel.file.tabbed.tab.classes.ClassTabPanel; import cn.enaium.joe.gui.panel.file.tree.FileTreeCellRenderer; import cn.enaium.joe.gui.panel.file.tree.node.ClassTreeNode; +import cn.enaium.joe.gui.panel.file.tree.node.PackageTreeNode; import cn.enaium.joe.jar.Jar; import cn.enaium.joe.util.ASMUtil; import cn.enaium.joe.util.JTreeUtil; @@ -30,6 +31,11 @@ import javax.swing.*; import javax.swing.tree.DefaultTreeModel; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; import java.io.IOException; import java.util.Map; import java.util.Set; @@ -47,6 +53,23 @@ public InheritPanel() { JTree inheritance = new JTree() {{ setModel(new DefaultTreeModel(null)); setCellRenderer(new FileTreeCellRenderer()); + addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + if (e.getClickCount() == 2) { + if (getSelectionPath() != null) { + Object lastPathComponent = getSelectionPath().getLastPathComponent(); + if (lastPathComponent instanceof PackageTreeNode) { + PackageTreeNode packageTreeNode = (PackageTreeNode) lastPathComponent; + if (packageTreeNode instanceof ClassTreeNode) { + ClassNode classNode = ((ClassTreeNode) packageTreeNode).classNode; + JavaOctetEditor.getInstance().fileTabbedPanel.addTab(classNode.name.substring(classNode.name.lastIndexOf("/") + 1), new ClassTabPanel(classNode)); + } + } + } + } + } + }); }}; JavaOctetEditor.getInstance().event.register(FileTabbedSelectEvent.class, (Consumer) event -> { if (event.getSelect() instanceof ClassTabPanel) { diff --git a/src/main/java/cn/enaium/joe/gui/panel/confirm/ListValueEditPanel.java b/src/main/java/cn/enaium/joe/gui/panel/confirm/ListValueEditPanel.java index 9742da3..0fe76f0 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/confirm/ListValueEditPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/confirm/ListValueEditPanel.java @@ -46,7 +46,7 @@ public ListValueEditPanel(List objects) { setConfirm(() -> { objects.clear(); for (String s : jTextArea.getText().replaceAll("^\\s+", "").split("\n")) { - objects.add(ASMUtil.toType(type, s)); + objects.add(ASMUtil.valueOf(type, s)); } }); } diff --git a/src/main/java/cn/enaium/joe/gui/panel/confirm/ValueEditPanel.java b/src/main/java/cn/enaium/joe/gui/panel/confirm/ValueEditPanel.java index d605a05..c2e1f8d 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/confirm/ValueEditPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/confirm/ValueEditPanel.java @@ -32,7 +32,7 @@ public ValueEditPanel(ObjectWrapper objectWrapper) { JTextField jTextField = new JTextField(objectWrapper.getWrapper().toString()); add(jTextField, BorderLayout.CENTER); setConfirm(() -> { - objectWrapper.setWrapper(ASMUtil.toType(objectWrapper.getWrapper().getClass(), jTextField.getText())); + objectWrapper.setWrapper(ASMUtil.valueOf(objectWrapper.getWrapper().getClass(), jTextField.getText())); }); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ASMifierTablePanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ASMifierTablePanel.java index be1e5cb..5c566c8 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ASMifierTablePanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ASMifierTablePanel.java @@ -17,12 +17,10 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab.classes; import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.compiler.Compiler; +import cn.enaium.joe.config.extend.KeymapConfig; import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.util.*; -import javassist.ClassPool; -import javassist.CtClass; -import javassist.CtMethod; -import javassist.Loader; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; @@ -31,9 +29,7 @@ import javax.swing.*; import java.awt.*; -import java.awt.event.ActionEvent; import java.awt.event.InputEvent; -import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.io.PrintWriter; import java.io.StringWriter; @@ -46,34 +42,44 @@ public ASMifierTablePanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); CodeAreaPanel codeAreaPanel = new CodeAreaPanel() {{ - KeyStrokeUtil.register(getTextArea(), KeyStroke.getKeyStroke(KeyEvent.VK_S, InputEvent.CTRL_DOWN_MASK), () -> { + KeyStrokeUtil.register(getTextArea(), JavaOctetEditor.getInstance().config.getByClass(KeymapConfig.class).save.getValue(), () -> { try { - StringWriter stringWriter = new StringWriter(); - ClassReader classReader = new ClassReader(this.getClass().getName()); - classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(stringWriter)), 0); - ClassPool classPool = new ClassPool(); - classPool.appendSystemPath(); - classPool.importPackage("org.objectweb.asm.AnnotationVisitor"); - classPool.importPackage("org.objectweb.asm.Attribute"); - classPool.importPackage("org.objectweb.asm.ClassReader"); - classPool.importPackage("org.objectweb.asm.ClassWriter"); - classPool.importPackage("org.objectweb.asm.ConstantDynamic"); - classPool.importPackage("org.objectweb.asm.FieldVisitor"); - classPool.importPackage("org.objectweb.asm.Handle"); - classPool.importPackage("org.objectweb.asm.Label"); - classPool.importPackage("org.objectweb.asm.MethodVisitor"); - classPool.importPackage("org.objectweb.asm.Opcodes"); - classPool.importPackage("org.objectweb.asm.RecordComponentVisitor"); - classPool.importPackage("org.objectweb.asm.Type"); - classPool.importPackage("org.objectweb.asm.TypePath"); - CtClass ctClass = classPool.makeClass(ASMifier.class.getSimpleName()); - ctClass.addInterface(classPool.get("org.objectweb.asm.Opcodes")); - ctClass.addMethod(CtMethod.make("public static byte[] dump() throws Exception {" + getTextArea().getText() + "return classWriter.toByteArray();}", ctClass)); - byte[] dumps = (byte[]) new Loader(classPool).loadClass(ASMifier.class.getSimpleName()).getMethod("dump").invoke(null); - ClassNode newClassNode = new ClassNode(); - new ClassReader(dumps).accept(newClassNode, ClassReader.EXPAND_FRAMES); - ReflectUtil.setAll(classNode, newClassNode); - JOptionPane.showMessageDialog(null, LangUtil.i18n("success")); + String stringBuilder = "import org.objectweb.asm.AnnotationVisitor;" + + "import org.objectweb.asm.Attribute;" + + "import org.objectweb.asm.ClassReader;" + + "import org.objectweb.asm.ClassWriter;" + + "import org.objectweb.asm.ConstantDynamic;" + + "import org.objectweb.asm.FieldVisitor;" + + "import org.objectweb.asm.Handle;" + + "import org.objectweb.asm.Label;" + + "import org.objectweb.asm.MethodVisitor;" + + "import org.objectweb.asm.Opcodes;" + + "import org.objectweb.asm.RecordComponentVisitor;" + + "import org.objectweb.asm.ModuleVisitor;" + + "import org.objectweb.asm.Type;" + + "import org.objectweb.asm.TypePath;" + + "public class" + " " + ASMifier.class.getSimpleName() + " " + "implements Opcodes" + + "{" + + "public static byte[] dump() throws Exception {" + + getTextArea().getText() + + "return classWriter.toByteArray();" + + "}" + + "}"; + Compiler compiler = new Compiler(); + compiler.addSource(ASMifier.class.getSimpleName(), stringBuilder); + compiler.compile(); + + ClassLoader loader = new ClassLoader() { + @Override + protected Class findClass(String name) { + byte[] bytes = compiler.getClasses().get(ASMifier.class.getSimpleName()); + return defineClass(name, bytes, 0, bytes.length); + } + }; + + byte[] dumps = (byte[]) loader.loadClass(ASMifier.class.getSimpleName()).getMethod("dump").invoke(null); + ReflectUtil.copyAllMember(classNode, ASMUtil.acceptClassNode(new ClassReader(dumps))); + MessageUtil.info(LangUtil.i18n("success")); } catch (Throwable e) { MessageUtil.error(e); } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ClassTabPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ClassTabPanel.java index 6519e52..1e173b8 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ClassTabPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/ClassTabPanel.java @@ -16,11 +16,15 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab.classes; +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.event.Event; +import cn.enaium.joe.Instance; import cn.enaium.joe.util.LangUtil; import org.objectweb.asm.tree.ClassNode; import javax.swing.*; import java.awt.*; +import java.util.function.Consumer; /** * @author Enaium @@ -35,9 +39,20 @@ public ClassTabPanel(ClassNode classNode) { JTabbedPane jTabbedPane = new JTabbedPane(); jTabbedPane.setTabPlacement(JTabbedPane.BOTTOM); jTabbedPane.addTab(LangUtil.i18n("class.tab.bytecodeView"), new TraceBytecodeTabPanel(classNode)); - jTabbedPane.addTab(LangUtil.i18n("class.tab.decompileView"), new DecompileTabPanel(classNode)); + jTabbedPane.addTab(LangUtil.i18n("class.tab.decompileEdit"), new DecompileTabPanel(classNode)); jTabbedPane.addTab(LangUtil.i18n("class.tab.visitorEdit"), new ASMifierTablePanel(classNode)); jTabbedPane.addTab(LangUtil.i18n("class.tab.infoEdit"), new ClassInfoTabPanel(classNode)); + jTabbedPane.setSelectedIndex(Instance.INSTANCE.classTabIndex); + jTabbedPane.addChangeListener(e -> JavaOctetEditor.getInstance().event.call(new Change(Instance.INSTANCE.classTabIndex = jTabbedPane.getSelectedIndex()))); + JavaOctetEditor.getInstance().event.register(Change.class, (Consumer) event -> jTabbedPane.setSelectedIndex(event.index)); add(jTabbedPane); } + + private static class Change implements Event { + private final int index; + + public Change(int index) { + this.index = index; + } + } } \ No newline at end of file diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/DecompileTabPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/DecompileTabPanel.java index fa97d17..c4d3037 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/DecompileTabPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/DecompileTabPanel.java @@ -17,13 +17,18 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab.classes; import cn.enaium.joe.JavaOctetEditor; -import cn.enaium.joe.service.DecompileService; +import cn.enaium.joe.compiler.Compiler; +import cn.enaium.joe.config.extend.KeymapConfig; import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.task.DecompileTask; -import cn.enaium.joe.util.ASyncUtil; +import cn.enaium.joe.util.*; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; +import javax.swing.*; import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; /** * @author Enaium @@ -32,7 +37,23 @@ public class DecompileTabPanel extends ClassNodeTabPanel { public DecompileTabPanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); - CodeAreaPanel codeAreaPanel = new CodeAreaPanel(); + CodeAreaPanel codeAreaPanel = new CodeAreaPanel() {{ + KeyStrokeUtil.register(getTextArea(), JavaOctetEditor.getInstance().config.getByClass(KeymapConfig.class).save.getValue(), () -> { + try { + Compiler compiler = new Compiler(); + compiler.addSource(ASMUtil.getReferenceName(classNode), getTextArea().getText()); + compiler.compile(); + if (compiler.getClasses().get(ASMUtil.getReferenceName(classNode)).length != 0) { + ReflectUtil.copyAllMember(classNode, ASMUtil.acceptClassNode(new ClassReader(compiler.getClasses().get(ASMUtil.getReferenceName(classNode))))); + MessageUtil.info(LangUtil.i18n("success")); + } else { + MessageUtil.error(new RuntimeException("Compile failed,Please check console")); + } + } catch (Throwable e) { + MessageUtil.error(e); + } + }); + }}; codeAreaPanel.getTextArea().setSyntaxEditingStyle("text/java"); JavaOctetEditor.getInstance().task.submit(new DecompileTask(classNode)).thenAccept(it -> { codeAreaPanel.getTextArea().setText(it); diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/TraceBytecodeTabPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/TraceBytecodeTabPanel.java index c22b910..b4c62b9 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/TraceBytecodeTabPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/classes/TraceBytecodeTabPanel.java @@ -34,6 +34,7 @@ public TraceBytecodeTabPanel(ClassNode classNode) { super(classNode); setLayout(new BorderLayout()); CodeAreaPanel codeAreaPanel = new CodeAreaPanel(); + codeAreaPanel.getTextArea().setEditable(false); codeAreaPanel.getTextArea().setSyntaxEditingStyle("text/custom"); final StringWriter stringWriter = new StringWriter(); ASyncUtil.execute(() -> { diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/resources/TextTablePanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/resources/TextTablePanel.java index e82a802..5abfa01 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/resources/TextTablePanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tabbed/tab/resources/TextTablePanel.java @@ -16,15 +16,22 @@ package cn.enaium.joe.gui.panel.file.tabbed.tab.resources; +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.config.extend.KeymapConfig; import cn.enaium.joe.gui.panel.BorderPanel; import cn.enaium.joe.gui.panel.CodeAreaPanel; import cn.enaium.joe.gui.panel.file.tree.node.FileTreeNode; +import cn.enaium.joe.util.KeyStrokeUtil; +import cn.enaium.joe.util.LangUtil; +import cn.enaium.joe.util.MessageUtil; import org.fife.ui.rsyntaxtextarea.FileTypeUtil; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import javax.swing.*; import java.awt.*; +import java.awt.event.InputEvent; +import java.awt.event.KeyEvent; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -48,6 +55,11 @@ public TextTablePanel(FileTreeNode fileTreeNode) { } getTextArea().setSyntaxEditingStyle(syntax); getTextArea().setText(new String(fileTreeNode.getData(), StandardCharsets.UTF_8)); + + KeyStrokeUtil.register(getTextArea(), JavaOctetEditor.getInstance().config.getByClass(KeymapConfig.class).save.getValue(), () -> { + fileTreeNode.setData(getTextArea().getText().getBytes(StandardCharsets.UTF_8)); + MessageUtil.info(LangUtil.i18n("success")); + }); }}); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tree/CenterPanel.java b/src/main/java/cn/enaium/joe/gui/panel/file/tree/CenterPanel.java index a754811..d8b4662 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tree/CenterPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tree/CenterPanel.java @@ -43,7 +43,6 @@ public CenterPanel() { jViewport.setView(getSelectedComponent()); }); }}); - }}); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/file/tree/node/FileTreeNode.java b/src/main/java/cn/enaium/joe/gui/panel/file/tree/node/FileTreeNode.java index 1c603f3..4310eca 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/file/tree/node/FileTreeNode.java +++ b/src/main/java/cn/enaium/joe/gui/panel/file/tree/node/FileTreeNode.java @@ -16,19 +16,25 @@ package cn.enaium.joe.gui.panel.file.tree.node; +import cn.enaium.joe.JavaOctetEditor; + /** * @author Enaium */ public class FileTreeNode extends FolderTreeNode { - private final byte[] data; + private final String file; - public FileTreeNode(Object userObject, byte[] data) { + public FileTreeNode(Object userObject, String file) { super(userObject); - this.data = data; + this.file = file; } public byte[] getData() { - return data; + return JavaOctetEditor.getInstance().getJar().resources.get(file); + } + + public void setData(byte[] data) { + JavaOctetEditor.getInstance().getJar().resources.put(file, data); } } diff --git a/src/main/java/cn/enaium/joe/gui/panel/instruction/AbstractInstructionPanel.java b/src/main/java/cn/enaium/joe/gui/panel/instruction/AbstractInstructionPanel.java index fd1805d..124e7d5 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/instruction/AbstractInstructionPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/instruction/AbstractInstructionPanel.java @@ -18,6 +18,7 @@ import cn.enaium.joe.util.LangUtil; import cn.enaium.joe.util.OpcodeUtil; +import net.miginfocom.swing.MigLayout; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; @@ -34,34 +35,21 @@ */ public abstract class AbstractInstructionPanel extends JPanel { private final JComboBox opcode = new JComboBox<>(new DefaultComboBoxModel<>()); - - private final JPanel names = new JPanel(new GridLayout(0, 1)); - private final JPanel components = new JPanel(new GridLayout(0, 1)); - private Callable confirm = () -> false; public AbstractInstructionPanel(AbstractInsnNode instruction) { - setLayout(new BorderLayout()); + setLayout(new MigLayout("fillx", "[fill][fill]")); if (instruction.getOpcode() != -1) { DefaultComboBoxModel model = (DefaultComboBoxModel) opcode.getModel(); getOpcodes().forEach(model::addElement); model.setSelectedItem(OpcodeUtil.OPCODE.get(instruction.getOpcode())); addComponent(new JLabel(LangUtil.i18n("instruction.opcode")), opcode); } - - add(names, BorderLayout.WEST); - add(components, BorderLayout.CENTER); } public void addComponent(JComponent name, JComponent component) { - names.add(new JPanel(new BorderLayout()) {{ - setBorder(new EmptyBorder(10, 10, 10, 10)); - add(name, BorderLayout.CENTER); - }}); - components.add(new JPanel(new BorderLayout()) {{ - setBorder(new EmptyBorder(10, 10, 10, 10)); - add(component, BorderLayout.CENTER); - }}); + add(name); + add(component, "wrap"); } public void setConfirm(Callable callable) { diff --git a/src/main/java/cn/enaium/joe/gui/panel/method/MethodInstructionPanel.java b/src/main/java/cn/enaium/joe/gui/panel/method/MethodInstructionPanel.java index dd25d31..1e27b65 100644 --- a/src/main/java/cn/enaium/joe/gui/panel/method/MethodInstructionPanel.java +++ b/src/main/java/cn/enaium/joe/gui/panel/method/MethodInstructionPanel.java @@ -16,8 +16,11 @@ package cn.enaium.joe.gui.panel.method; +import cn.enaium.joe.JavaOctetEditor; +import cn.enaium.joe.config.extend.KeymapConfig; import cn.enaium.joe.gui.component.InstructionComboBox; import cn.enaium.joe.gui.panel.confirm.InstructionEditPanel; +import cn.enaium.joe.util.Util; import cn.enaium.joe.util.*; import cn.enaium.joe.wrapper.InstructionWrapper; import org.objectweb.asm.Handle; @@ -27,10 +30,6 @@ import javax.swing.*; import java.awt.*; import java.awt.datatransfer.StringSelection; -import java.awt.event.KeyEvent; -import java.awt.event.MouseAdapter; -import java.awt.event.MouseEvent; -import java.util.ArrayList; import java.util.HashMap; /** @@ -42,6 +41,7 @@ public MethodInstructionPanel(MethodNode methodNode) { super(new BorderLayout()); DefaultListModel instructionDefaultListModel = new DefaultListModel<>(); JList instructionJList = new JList<>(instructionDefaultListModel); + instructionJList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); instructionJList.setPrototypeCellValue(new InstructionWrapper(null)); instructionJList.setCellRenderer((list, value, index, isSelected, cellHasFocus) -> new JPanel(new BorderLayout()) {{ if (isSelected) { @@ -57,81 +57,65 @@ public MethodInstructionPanel(MethodNode methodNode) { } JPopupMenu jPopupMenu = new JPopupMenu(); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instruction.edit")) {{ - addActionListener(e -> { - InstructionWrapper selectedValue = instructionJList.getSelectedValue(); - if (selectedValue != null && !(selectedValue.getWrapper() instanceof LabelNode)) { - MessageUtil.confirm(new InstructionEditPanel(selectedValue.getWrapper()), LangUtil.i18n("popup.instruction.edit")); - } - }); - }}); + KeymapConfig keymapConfig = JavaOctetEditor.getInstance().config.getByClass(KeymapConfig.class); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instruction.clone")) {{ - addActionListener(e -> { - InstructionWrapper selectedValue = instructionJList.getSelectedValue(); - if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { - AbstractInsnNode clone; - if (selectedValue.getWrapper() instanceof LabelNode) { - clone = new LabelNode(); - } else { - clone = selectedValue.getWrapper().clone(new HashMap<>()); - } - - instructionDefaultListModel.add(instructionJList.getSelectedIndex() + 1, new InstructionWrapper(clone)); - methodNode.instructions.insert(selectedValue.getWrapper(), clone); - } - }); - }}); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.edit"), () -> { + InstructionWrapper selectedValue = instructionJList.getSelectedValue(); + if (selectedValue != null && !(selectedValue.getWrapper() instanceof LabelNode)) { + MessageUtil.confirm(new InstructionEditPanel(selectedValue.getWrapper()), LangUtil.i18n("popup.instructions.edit")); + } + }, keymapConfig.edit.getValue()); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.remove")) {{ - addActionListener(e -> { - InstructionWrapper selectedValue = instructionJList.getSelectedValue(); - if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { - MessageUtil.confirm(LangUtil.i18n("dialog.wantRemove"), LangUtil.i18n("button.remove"), () -> { - instructionDefaultListModel.remove(instructionJList.getSelectedIndex()); - methodNode.instructions.remove(selectedValue.getWrapper()); - }); - } - }); - }}); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.copyText")) {{ - addActionListener(e -> { - InstructionWrapper selectedValue = instructionJList.getSelectedValue(); - if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { - Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(HtmlUtil.remove(selectedValue.toString())), null); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.clone"), () -> { + InstructionWrapper selectedValue = instructionJList.getSelectedValue(); + if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { + AbstractInsnNode clone; + if (selectedValue.getWrapper() instanceof LabelNode) { + clone = new LabelNode(); + } else { + clone = selectedValue.getWrapper().clone(new HashMap<>()); } - }); - }}); - - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.insertBefore")) {{ - addActionListener(e -> { - insert(methodNode, instructionJList, true); - }); - }}); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.insertAfter")) {{ - addActionListener(e -> { - insert(methodNode, instructionJList, false); - }); - }}); + instructionDefaultListModel.add(instructionJList.getSelectedIndex() + 1, new InstructionWrapper(clone)); + methodNode.instructions.insert(selectedValue.getWrapper(), clone); + } + }, keymapConfig.clone.getValue()); + + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.remove"), () -> { + InstructionWrapper selectedValue = instructionJList.getSelectedValue(); + if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { + MessageUtil.confirm(LangUtil.i18n("dialog.wantRemove"), LangUtil.i18n("button.remove"), () -> { + instructionDefaultListModel.remove(instructionJList.getSelectedIndex()); + methodNode.instructions.remove(selectedValue.getWrapper()); + }); + } + }, keymapConfig.remove.getValue()); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.moveUp")) {{ - addActionListener(e -> { - moveInstruction(instructionJList, methodNode, true); - }); - }}); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.copyText"), () -> { + InstructionWrapper selectedValue = instructionJList.getSelectedValue(); + if (instructionJList.getSelectedIndex() != -1 || selectedValue != null) { + Toolkit.getDefaultToolkit().getSystemClipboard().setContents(new StringSelection(HtmlUtil.remove(selectedValue.toString())), null); + } + }, keymapConfig.copy.getValue()); - jPopupMenu.add(new JMenuItem(LangUtil.i18n("popup.instructions.moveDown")) {{ - addActionListener(e -> { - moveInstruction(instructionJList, methodNode, false); - }); - }}); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.insertBefore"), () -> insert(methodNode, instructionJList, true), keymapConfig.insertBefore.getValue()); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.insertAfter"), () -> insert(methodNode, instructionJList, false), keymapConfig.insertAfter.getValue()); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.moveUp"), () -> moveInstruction(instructionJList, methodNode, true), keymapConfig.insertAfter.getValue()); + addItem(instructionJList, jPopupMenu, LangUtil.i18n("popup.instructions.moveDown"), () -> moveInstruction(instructionJList, methodNode, false), keymapConfig.insertAfter.getValue()); JMenuUtil.addPopupMenu(instructionJList, () -> jPopupMenu, () -> instructionJList.getSelectedValue() != null); add(new JScrollPane(instructionJList), BorderLayout.CENTER); } + private void addItem(JList instructionJList, JPopupMenu jPopupMenu, String text, Runnable runnable, KeyStroke key) { + jPopupMenu.add(new JMenuItem(text) {{ + KeyStrokeUtil.register(instructionJList, key, runnable); + setAccelerator(key); + addActionListener(Util.ofAction(runnable)); + }}); + } + private static void moveInstruction(JList instructionJList, MethodNode methodNode, boolean up) { DefaultListModel instructionDefaultListModel = ((DefaultListModel) instructionJList.getModel()); InstructionWrapper selectedValue = instructionJList.getSelectedValue(); @@ -217,7 +201,7 @@ private static void insert(MethodNode methodNode, JList inst throw new RuntimeException(); } - MessageUtil.confirm(new InstructionEditPanel(abstractInsnNode), LangUtil.i18n("popup.instruction.edit"), () -> { + MessageUtil.confirm(new InstructionEditPanel(abstractInsnNode), LangUtil.i18n("popup.instructions.edit"), () -> { if (before) { methodNode.instructions.insertBefore(instructionJList.getSelectedValue().getWrapper(), abstractInsnNode); } else { diff --git a/src/main/java/cn/enaium/joe/gui/ui/VerticalLabelUI.java b/src/main/java/cn/enaium/joe/gui/ui/VerticalLabelUI.java index aa6f61d..cd5840f 100644 --- a/src/main/java/cn/enaium/joe/gui/ui/VerticalLabelUI.java +++ b/src/main/java/cn/enaium/joe/gui/ui/VerticalLabelUI.java @@ -70,10 +70,12 @@ public void paint(Graphics g, JComponent c) { Graphics2D g2 = (Graphics2D) g; AffineTransform tr = g2.getTransform(); + //don't rotate if (icon != null) { icon.paintIcon(c, g, paintIconR.x, paintIconR.y); } + //rotate if (clockwise) { g2.rotate(Math.PI / 2); g2.translate(0, -c.getWidth()); diff --git a/src/main/java/cn/enaium/joe/service/decompiler/CFRDecompiler.java b/src/main/java/cn/enaium/joe/service/decompiler/CFRDecompiler.java index d4c299f..6894bf1 100644 --- a/src/main/java/cn/enaium/joe/service/decompiler/CFRDecompiler.java +++ b/src/main/java/cn/enaium/joe/service/decompiler/CFRDecompiler.java @@ -54,6 +54,10 @@ public Pair getClassFileContent(String path) { String name = path.substring(0, path.length() - 6); if (name.equals(classNode.getInternalName())) { return Pair.make(classNode.getClassBytes(), name); + } else { + if (JavaOctetEditor.getInstance().getJar().classes.get(path) != null) { + return Pair.make(ClassNode.of(JavaOctetEditor.getInstance().getJar().classes.get(path)).getClassBytes(), name); + } } return null; } @@ -62,7 +66,6 @@ public Pair getClassFileContent(String path) { @Override public String getPossiblyRenamedPath(String path) {return path;} @Override public Collection addJar(String arg0) {return Collections.emptySet();} }); - return decompile(state, classNode); } diff --git a/src/main/java/cn/enaium/joe/task/TaskManager.java b/src/main/java/cn/enaium/joe/task/TaskManager.java index 6b64368..9b6252d 100644 --- a/src/main/java/cn/enaium/joe/task/TaskManager.java +++ b/src/main/java/cn/enaium/joe/task/TaskManager.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Logger; @@ -35,7 +36,7 @@ */ public class TaskManager { private final ExecutorService executorService = Executors.newCachedThreadPool(); - private final List, CompletableFuture>> task = new ArrayList<>(); + private final List, CompletableFuture>> task = new CopyOnWriteArrayList<>(); public CompletableFuture submit(AbstractTask abstractTask) { for (Pair, CompletableFuture> classCompletableFuturePair : task) { diff --git a/src/main/java/cn/enaium/joe/util/ASMUtil.java b/src/main/java/cn/enaium/joe/util/ASMUtil.java index 8033fc1..c4839ff 100644 --- a/src/main/java/cn/enaium/joe/util/ASMUtil.java +++ b/src/main/java/cn/enaium/joe/util/ASMUtil.java @@ -30,7 +30,15 @@ * @since 1.2.0 */ public class ASMUtil { - public static Object toType(Class type, String text) { + /** + * converts a string value to a value of that type + * + * @param type value type + * @param text value text + * @param value type + * @return value of type + */ + public static Object valueOf(Class type, String text) { try { Method valueOf = type.getMethod("valueOf", String.class); return valueOf.invoke(null, text); @@ -43,6 +51,13 @@ public static Object toType(Class type, String text) { } } + + /** + * accept as class node + * + * @param classReader class reader + * @return class node + */ public static ClassNode acceptClassNode(ClassReader classReader) { ClassNode classNode = new ClassNode(); try { @@ -57,6 +72,12 @@ public static ClassNode acceptClassNode(ClassReader classReader) { return classNode; } + /** + * get all parents, include interfaces + * + * @param classNode class node + * @return all interfaces and superclasses + */ public static Set getParentClass(ClassNode classNode) { Set parent = new HashSet<>(); if (classNode.superName != null && !classNode.superName.equals(Object.class.getName().replace(".", "/"))) { @@ -65,4 +86,14 @@ public static Set getParentClass(ClassNode classNode) { parent.addAll(classNode.interfaces); return parent; } + + /** + * replace all `/` to `.` + * + * @param classNode class node + * @return reference name + */ + public static String getReferenceName(ClassNode classNode) { + return classNode.name.replace("/", "."); + } } diff --git a/src/main/java/cn/enaium/joe/util/KeyStrokeUtil.java b/src/main/java/cn/enaium/joe/util/KeyStrokeUtil.java index d9a15e5..baf7665 100644 --- a/src/main/java/cn/enaium/joe/util/KeyStrokeUtil.java +++ b/src/main/java/cn/enaium/joe/util/KeyStrokeUtil.java @@ -20,6 +20,7 @@ import java.awt.event.ActionEvent; import java.awt.event.InputEvent; import java.awt.event.KeyEvent; +import java.lang.reflect.Field; import java.util.concurrent.atomic.AtomicInteger; /** @@ -30,16 +31,11 @@ public class KeyStrokeUtil { private static final AtomicInteger ATOMIC_INTEGER = new AtomicInteger(0); - public static void register(JComponent component, KeyStroke keyStroke, Runnable action) { + public static void register(JComponent component, KeyStroke keyStroke, Runnable runnable) { String key = String.valueOf(ATOMIC_INTEGER.incrementAndGet()); InputMap inputMap = component.getInputMap(JComponent.WHEN_FOCUSED); inputMap.put(keyStroke, key); ActionMap actionMap = component.getActionMap(); - actionMap.put(key, new AbstractAction() { - @Override - public void actionPerformed(ActionEvent e) { - action.run(); - } - }); + actionMap.put(key, Util.ofAction(runnable)); } } diff --git a/src/main/java/cn/enaium/joe/util/MessageUtil.java b/src/main/java/cn/enaium/joe/util/MessageUtil.java index a3927df..7ad5602 100644 --- a/src/main/java/cn/enaium/joe/util/MessageUtil.java +++ b/src/main/java/cn/enaium/joe/util/MessageUtil.java @@ -16,10 +16,15 @@ package cn.enaium.joe.util; +import cn.enaium.joe.dialog.OptionDialog; +import cn.enaium.joe.gui.panel.BorderPanel; import cn.enaium.joe.gui.panel.confirm.ConfirmPanel; import org.pmw.tinylog.Logger; import javax.swing.*; +import java.awt.*; +import java.io.PrintWriter; +import java.io.StringWriter; /** * @author Enaium @@ -38,8 +43,16 @@ public static void error(String readable, Throwable e) { } public static void error(Throwable e) { - Logger.error(e); - JOptionPane.showMessageDialog(null, e.toString(), LangUtil.i18n("error"), JOptionPane.ERROR_MESSAGE); + e.printStackTrace(); + new OptionDialog(LangUtil.i18n("error"), new BorderPanel() {{ + setTop(new JLabel(e.getMessage())); + setCenter(new JScrollPane(new JTextArea() {{ + StringWriter out = new StringWriter(); + e.printStackTrace(new PrintWriter(out)); + setText(out.toString()); + setEditable(false); + }})); + }}, JOptionPane.ERROR_MESSAGE).setVisible(true); } public static RuntimeException runtimeException(String readable, Throwable e) { @@ -53,24 +66,12 @@ public static RuntimeException runtimeException(Throwable e) { } public static void confirm(Object message, String title, Runnable yes, Runnable no) { - int i = JOptionPane.showConfirmDialog(null, message, title, JOptionPane.OK_CANCEL_OPTION); - if (i == JOptionPane.YES_OPTION) { - if (yes != null) { - yes.run(); - } - } else { - if (no != null) { - no.run(); - } - } + OptionDialog optionDialog = new OptionDialog(title, message, JOptionPane.INFORMATION_MESSAGE, yes, no); + optionDialog.setVisible(true); } public static void confirm(ConfirmPanel confirmPanel, String title) { - confirm(confirmPanel, title, () -> { - confirmPanel.getConfirm().run(); - }, () -> { - confirmPanel.getCancel().run(); - }); + confirm(confirmPanel, title, confirmPanel.getConfirm(), confirmPanel.getCancel()); } public static void confirm(ConfirmPanel confirmPanel, String title, Runnable yes) { @@ -86,12 +87,14 @@ public static void confirm(Object message, String title, Runnable yes) { public static void info(String message) { - JOptionPane.showMessageDialog(null, message, LangUtil.i18n("info"), JOptionPane.INFORMATION_MESSAGE); + OptionDialog info = new OptionDialog(LangUtil.i18n("info"), message, JOptionPane.INFORMATION_MESSAGE); + info.setVisible(true); Logger.info(message); } public static void warning(String message) { - JOptionPane.showMessageDialog(null, message, LangUtil.i18n("warning"), JOptionPane.WARNING_MESSAGE); + OptionDialog warning = new OptionDialog(LangUtil.i18n("warning"), message, JOptionPane.WARNING_MESSAGE); + warning.setVisible(true); Logger.warn(message); } } diff --git a/src/main/java/cn/enaium/joe/util/ReflectUtil.java b/src/main/java/cn/enaium/joe/util/ReflectUtil.java index 65cde6a..f5b71a1 100644 --- a/src/main/java/cn/enaium/joe/util/ReflectUtil.java +++ b/src/main/java/cn/enaium/joe/util/ReflectUtil.java @@ -24,11 +24,20 @@ * @since 1.0.0 */ public class ReflectUtil { - public static void setAll(T old, T t) throws NoSuchFieldException, IllegalAccessException { - for (Field oldField : old.getClass().getDeclaredFields()) { + /** + * copy all class member from the old class object to the new class object + * + * @param oldObject old object + * @param newObject new object + * @param type of two classes + * @throws NoSuchFieldException e + * @throws IllegalAccessException e + */ + public static void copyAllMember(T oldObject, T newObject) throws NoSuchFieldException, IllegalAccessException { + for (Field oldField : oldObject.getClass().getDeclaredFields()) { oldField.setAccessible(true); - Field declaredField = t.getClass().getDeclaredField(oldField.getName()); - oldField.set(old, declaredField.get(t)); + Field declaredField = newObject.getClass().getDeclaredField(oldField.getName()); + oldField.set(oldObject, declaredField.get(newObject)); } } diff --git a/src/main/java/cn/enaium/joe/util/Util.java b/src/main/java/cn/enaium/joe/util/Util.java index f2eba69..4395a1e 100644 --- a/src/main/java/cn/enaium/joe/util/Util.java +++ b/src/main/java/cn/enaium/joe/util/Util.java @@ -16,6 +16,9 @@ package cn.enaium.joe.util; +import javax.swing.*; +import java.awt.*; +import java.awt.event.ActionEvent; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; @@ -50,4 +53,22 @@ public static boolean isText(byte[] bytes) { } return true; } + + public static Dimension screenSize(int width, int height) { + return screenSize(new Dimension(width, height)); + } + + public static Dimension screenSize(Dimension dimension) { + Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); + return new Dimension((int) (dimension.width * screenSize.getWidth() / 1920), (int) (dimension.height * screenSize.getHeight() / 1080)); + } + + public static AbstractAction ofAction(Runnable runnable) { + return new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + runnable.run(); + } + }; + } } diff --git a/src/main/resources/lang/en_US.json b/src/main/resources/lang/en_US.json index 3d548e9..b9fde23 100644 --- a/src/main/resources/lang/en_US.json +++ b/src/main/resources/lang/en_US.json @@ -36,8 +36,8 @@ "menu.help.contact": "Contact", "popup.attach.properties": "Properties", "popup.attach.external": "External", - "popup.instruction.edit": "Edit", - "popup.instruction.clone": "Clone", + "popup.instructions.edit": "Edit", + "popup.instructions.clone": "Clone", "popup.instructions.remove": "Remove", "popup.instructions.copyText": "Copy Text", "popup.instructions.insertBefore": "Insert Before", @@ -64,7 +64,7 @@ "class.info.description": "Description:", "class.info.exceptions": "Exceptions:", "class.tab.bytecodeView": "BytecodeView", - "class.tab.decompileView": "DecompileView", + "class.tab.decompileEdit": "DecompileEdit", "class.tab.visitorEdit": "VisitorEdit", "class.tab.infoEdit": "InfoEdit", "button.search": "Search", diff --git a/src/main/resources/lang/zh_CN.json b/src/main/resources/lang/zh_CN.json index b449a95..be01105 100644 --- a/src/main/resources/lang/zh_CN.json +++ b/src/main/resources/lang/zh_CN.json @@ -36,8 +36,8 @@ "menu.help.contact": "联系", "popup.attach.external": "外部", "popup.attach.properties": "属性", - "popup.instruction.edit": "编辑", - "popup.instruction.clone": "克隆", + "popup.instructions.edit": "编辑", + "popup.instructions.clone": "克隆", "popup.instructions.remove": "移除", "popup.instructions.copyText": "复制文本", "popup.instructions.insertBefore": "前插入", @@ -64,7 +64,7 @@ "class.info.description": "描述:", "class.info.exceptions": "异常:", "class.tab.bytecodeView": "字节码查看", - "class.tab.decompileView": "反编译查看", + "class.tab.decompileEdit": "反编译编辑", "class.tab.visitorEdit": "访问者编辑", "class.tab.infoEdit": "信息编辑", "button.search": "搜索", diff --git a/src/test/java/cn/enaium/joe/asm/VisitorTest.java b/src/test/java/cn/enaium/joe/asm/VisitorTest.java index 7fcdf5b..1d90c6f 100644 --- a/src/test/java/cn/enaium/joe/asm/VisitorTest.java +++ b/src/test/java/cn/enaium/joe/asm/VisitorTest.java @@ -1,15 +1,20 @@ package cn.enaium.joe.asm; +import cn.enaium.joe.compiler.Compiler; +import cn.enaium.joe.util.ASMUtil; import cn.enaium.joe.util.ImagineBreakerHelper; import javassist.*; import org.junit.jupiter.api.Test; import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.util.ASMifier; import org.objectweb.asm.util.TraceClassVisitor; import java.io.*; import java.lang.reflect.InvocationTargetException; +import static org.junit.jupiter.api.Assertions.assertTrue; + /** * @author Enaium */ @@ -19,28 +24,17 @@ class VisitorTest { } @Test - public void test() throws IOException, CannotCompileException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NotFoundException { + public void test() throws IOException { StringWriter stringWriter = new StringWriter(); ClassReader classReader = new ClassReader(this.getClass().getName()); classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(stringWriter)), 0); - ClassPool classPool = ClassPool.getDefault(); - classPool.importPackage("org.objectweb.asm.AnnotationVisitor"); - classPool.importPackage("org.objectweb.asm.Attribute"); - classPool.importPackage("org.objectweb.asm.ClassReader"); - classPool.importPackage("org.objectweb.asm.ClassWriter"); - classPool.importPackage("org.objectweb.asm.ConstantDynamic"); - classPool.importPackage("org.objectweb.asm.FieldVisitor"); - classPool.importPackage("org.objectweb.asm.Handle"); - classPool.importPackage("org.objectweb.asm.Label"); - classPool.importPackage("org.objectweb.asm.MethodVisitor"); - classPool.importPackage("org.objectweb.asm.Opcodes"); - classPool.importPackage("org.objectweb.asm.RecordComponentVisitor"); - classPool.importPackage("org.objectweb.asm.Type"); - classPool.importPackage("org.objectweb.asm.TypePath"); - CtClass ctClass = classPool.makeClass(ASMifier.class.getSimpleName()); - ctClass.addInterface(classPool.get("org.objectweb.asm.Opcodes")); - String substring = stringWriter.toString().substring(stringWriter.toString().indexOf("{") + 1, stringWriter.toString().lastIndexOf("}")); - ctClass.addMethod(CtMethod.make(substring, ctClass)); - System.out.println(ctClass.toClass().getMethod("dump").invoke(null)); + Compiler compiler = new Compiler(); + String name = "asm." + this.getClass().getName() + "Dump"; + compiler.addSource(name, stringWriter.toString()); + assertTrue(compiler.compile()); + ClassNode classNode = ASMUtil.acceptClassNode(new ClassReader(compiler.getClasses().get(name))); + StringWriter out = new StringWriter(); + classNode.accept(new TraceClassVisitor(new PrintWriter(out))); + System.out.println(out); } } \ No newline at end of file diff --git a/src/test/java/cn/enaium/joe/compiler/CompilerTest.java b/src/test/java/cn/enaium/joe/compiler/CompilerTest.java new file mode 100644 index 0000000..db4cc09 --- /dev/null +++ b/src/test/java/cn/enaium/joe/compiler/CompilerTest.java @@ -0,0 +1,44 @@ +/* + * Copyright 2022 Enaium + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package cn.enaium.joe.compiler; + +import cn.enaium.joe.util.ASMUtil; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.util.TraceClassVisitor; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.StringWriter; + +/** + * @author Enaium + */ +class CompilerTest { + @Test + public void compile() throws IOException { + Compiler compiler = new Compiler(); + compiler.addSource("Test", "public class Test { public static void main(String[] args) { System.out.println(0xCAFEBABE); } }"); + Assertions.assertTrue(compiler.compile()); + ClassNode classNode = ASMUtil.acceptClassNode(new ClassReader(compiler.getClasses().get("Test"))); + StringWriter out = new StringWriter(); + classNode.accept(new TraceClassVisitor(new PrintWriter(out))); + System.out.println(out); + } +} \ No newline at end of file