feat: add events support (#1832)

This commit is contained in:
Skylot
2023-04-23 16:47:29 +01:00
parent df313dbfec
commit 683cd76cc5
30 changed files with 331 additions and 1 deletions
@@ -29,6 +29,7 @@ import jadx.api.metadata.annotations.NodeDeclareRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.input.ICodeLoader;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.pass.JadxPass;
@@ -44,6 +45,7 @@ import jadx.core.dex.nodes.RootNode;
import jadx.core.dex.visitors.SaveCode;
import jadx.core.export.ExportGradleTask;
import jadx.core.plugins.JadxPluginManager;
import jadx.core.plugins.events.JadxEventsImpl;
import jadx.core.utils.DecompilerScheduler;
import jadx.core.utils.Utils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -94,6 +96,7 @@ public final class JadxDecompiler implements Closeable {
private ProtoXMLParser protoXmlParser;
private final IDecompileScheduler decompileScheduler = new DecompilerScheduler();
private final JadxEventsImpl events = new JadxEventsImpl();
private final List<ICodeLoader> customCodeLoaders = new ArrayList<>();
private final Map<JadxPassType, List<JadxPass>> customPasses = new HashMap<>();
@@ -129,6 +132,7 @@ public final class JadxDecompiler implements Closeable {
LOG.info("reloading (passes only) ...");
customPasses.clear();
root.resetPasses();
events.reset();
loadPlugins();
root.mergePasses(customPasses);
root.restartVisitors();
@@ -159,6 +163,7 @@ public final class JadxDecompiler implements Closeable {
resources = null;
binaryXmlParser = null;
protoXmlParser = null;
events.reset();
}
@Override
@@ -660,6 +665,10 @@ public final class JadxDecompiler implements Closeable {
return decompileScheduler;
}
public IJadxEvents events() {
return events;
}
public void addCustomCodeLoader(ICodeLoader customCodeLoader) {
customCodeLoaders.add(customCodeLoader);
}
@@ -99,6 +99,11 @@ public final class JavaClass implements JavaNode {
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return cls;
}
/**
* Internal API. Not Stable!
*/
@@ -5,6 +5,7 @@ import java.util.List;
import org.jetbrains.annotations.ApiStatus;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.info.AccessInfo;
import jadx.core.dex.instructions.args.ArgType;
import jadx.core.dex.nodes.FieldNode;
@@ -74,6 +75,11 @@ public final class JavaField implements JavaNode {
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return field;
}
/**
* Internal API. Not Stable!
*/
@@ -10,6 +10,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.AType;
import jadx.core.dex.attributes.nodes.MethodOverrideAttr;
import jadx.core.dex.info.AccessInfo;
@@ -116,6 +117,11 @@ public final class JavaMethod implements JavaNode {
return false;
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return mth;
}
/**
* Internal API. Not Stable!
*/
@@ -3,9 +3,12 @@ package jadx.api;
import java.util.List;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
public interface JavaNode {
ICodeNodeRef getCodeNodeRef();
String getName();
String getFullName();
@@ -8,6 +8,7 @@ import org.jetbrains.annotations.ApiStatus.Internal;
import org.jetbrains.annotations.NotNull;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.info.PackageInfo;
import jadx.core.dex.nodes.PackageNode;
@@ -75,6 +76,11 @@ public final class JavaPackage implements JavaNode, Comparable<JavaPackage> {
return !Objects.equals(parent, aliasParent);
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return pkgNode;
}
@Internal
public PackageNode getPkgNode() {
return pkgNode;
@@ -7,6 +7,7 @@ import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.Nullable;
import jadx.api.metadata.ICodeAnnotation;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.metadata.annotations.VarNode;
import jadx.api.metadata.annotations.VarRef;
import jadx.core.dex.instructions.args.ArgType;
@@ -37,6 +38,11 @@ public class JavaVariable implements JavaNode {
return varNode.getName();
}
@Override
public ICodeNodeRef getCodeNodeRef() {
return varNode;
}
@ApiStatus.Internal
public VarNode getVarNode() {
return varNode;
@@ -6,6 +6,7 @@ public interface ICodeAnnotation {
CLASS,
FIELD,
METHOD,
PKG,
VAR,
VAR_REF,
DECLARATION,
@@ -6,6 +6,7 @@ import org.jetbrains.annotations.Nullable;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
@@ -30,6 +31,11 @@ public interface JadxPluginContext {
*/
void registerInputsHashSupplier(Supplier<String> supplier);
/**
* Subscribe and send events
*/
IJadxEvents events();
@Nullable
JadxGuiContext getGuiContext();
}
@@ -0,0 +1,6 @@
package jadx.api.plugins.events;
public interface IJadxEvent {
JadxEventType<? extends IJadxEvent> getType();
}
@@ -0,0 +1,18 @@
package jadx.api.plugins.events;
import java.util.function.Consumer;
public interface IJadxEvents {
/**
* Send an event object.
* For public event types check {@link JadxEvents} class.
*/
void send(IJadxEvent event);
/**
* Register listener for specific event.
* For public event types check {@link JadxEvents} class.
*/
<E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener);
}
@@ -0,0 +1,9 @@
package jadx.api.plugins.events;
public abstract class JadxEventType<T extends IJadxEvent> {
public static <E extends IJadxEvent> JadxEventType<E> create() {
return new JadxEventType<>() {
};
}
}
@@ -0,0 +1,16 @@
package jadx.api.plugins.events;
import jadx.api.plugins.events.types.NodeRenamedByUser;
import static jadx.api.plugins.events.JadxEventType.create;
/**
* Typed and extendable enumeration of event types
*/
public class JadxEvents {
/**
* Notify about renames done by user (GUI only).
*/
public static final JadxEventType<NodeRenamedByUser> NODE_RENAMED_BY_USER = create();
}
@@ -0,0 +1,41 @@
package jadx.api.plugins.events.types;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.events.IJadxEvent;
import jadx.api.plugins.events.JadxEventType;
import jadx.api.plugins.events.JadxEvents;
public class NodeRenamedByUser implements IJadxEvent {
private final ICodeNodeRef node;
private final String oldName;
private final String newName;
public NodeRenamedByUser(ICodeNodeRef node, String oldName, String newName) {
this.node = node;
this.oldName = oldName;
this.newName = newName;
}
public ICodeNodeRef getNode() {
return node;
}
public String getOldName() {
return oldName;
}
public String getNewName() {
return newName;
}
@Override
public JadxEventType<NodeRenamedByUser> getType() {
return JadxEvents.NODE_RENAMED_BY_USER;
}
@Override
public String toString() {
return "NodeRenamedByUser{" + node + ", '" + oldName + "' -> '" + newName + "'}";
}
}
@@ -9,6 +9,7 @@ public class Consts {
public static final boolean DEBUG_EXC_HANDLERS = false;
public static final boolean DEBUG_FINALLY = false;
public static final boolean DEBUG_ATTRIBUTES = false;
public static final boolean DEBUG_EVENTS = true;
public static final String CLASS_OBJECT = "java.lang.Object";
public static final String CLASS_STRING = "java.lang.String";
@@ -8,11 +8,14 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import jadx.api.JavaPackage;
import jadx.api.metadata.ICodeNodeRef;
import jadx.core.dex.attributes.nodes.LineAttrNode;
import jadx.core.dex.info.PackageInfo;
import static jadx.core.utils.StringUtils.containsChar;
public class PackageNode implements IPackageUpdate, IDexNode, Comparable<PackageNode> {
public class PackageNode extends LineAttrNode
implements IPackageUpdate, IDexNode, ICodeNodeRef, Comparable<PackageNode> {
private final RootNode root;
private final PackageInfo pkgInfo;
@@ -194,6 +197,11 @@ public class PackageNode implements IPackageUpdate, IDexNode, Comparable<Package
return "package";
}
@Override
public AnnType getAnnType() {
return AnnType.PKG;
}
@Override
public RootNode root() {
return root;
@@ -12,6 +12,7 @@ import jadx.api.JadxDecompiler;
import jadx.api.plugins.JadxPlugin;
import jadx.api.plugins.JadxPluginContext;
import jadx.api.plugins.JadxPluginInfo;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.gui.JadxGuiContext;
import jadx.api.plugins.input.JadxCodeInput;
import jadx.api.plugins.options.JadxPluginOptions;
@@ -95,6 +96,11 @@ public class PluginContext implements JadxPluginContext, Comparable<PluginContex
return "";
}
@Override
public IJadxEvents events() {
return decompiler.events();
}
@Override
public @Nullable JadxGuiContext getGuiContext() {
return guiContext;
@@ -0,0 +1,34 @@
package jadx.core.plugins.events;
import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import jadx.api.plugins.events.IJadxEvent;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.events.JadxEventType;
import jadx.core.Consts;
public class JadxEventsImpl implements IJadxEvents {
private static final Logger LOG = LoggerFactory.getLogger(JadxEventsImpl.class);
private final JadxEventsManager manager = new JadxEventsManager();
@Override
public void send(IJadxEvent event) {
if (Consts.DEBUG_EVENTS) {
LOG.debug("Sending event: {}", event);
}
manager.send(event);
}
@Override
public <E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener) {
manager.addListener(eventType, listener);
}
public void reset() {
manager.reset();
}
}
@@ -0,0 +1,61 @@
package jadx.core.plugins.events;
import java.util.ArrayList;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import org.jetbrains.annotations.NotNull;
import jadx.api.plugins.events.IJadxEvent;
import jadx.api.plugins.events.JadxEventType;
/**
* Handle events sending and receiving
*/
public class JadxEventsManager {
private final Map<JadxEventType<?>, List<Consumer<IJadxEvent>>> listeners = new IdentityHashMap<>();
private final ExecutorService eventsThreadPool;
public JadxEventsManager() {
// TODO: allow to change threading strategy
this.eventsThreadPool = Executors.newSingleThreadExecutor(makeThreadFactory());
}
@SuppressWarnings("unchecked")
public synchronized <E extends IJadxEvent> void addListener(JadxEventType<E> eventType, Consumer<E> listener) {
listeners.computeIfAbsent(eventType, et -> new ArrayList<>())
.add((Consumer<IJadxEvent>) listener);
}
public synchronized void send(IJadxEvent event) {
List<Consumer<IJadxEvent>> consumers = listeners.get(event.getType());
if (consumers != null) {
for (Consumer<IJadxEvent> consumer : consumers) {
eventsThreadPool.execute(() -> consumer.accept(event));
}
}
}
public synchronized void reset() {
listeners.clear();
}
private static ThreadFactory makeThreadFactory() {
return new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(0);
@Override
public Thread newThread(@NotNull Runnable r) {
return new Thread(r, "jadx-events-thread-" + threadNumber.incrementAndGet());
}
};
}
}
@@ -0,0 +1,11 @@
package jadx.gui.events;
import jadx.api.plugins.events.JadxEventType;
import jadx.gui.events.types.TreeUpdate;
import static jadx.api.plugins.events.JadxEventType.create;
public class JadxGuiEvents {
public static final JadxEventType<TreeUpdate> TREE_UPDATE = create();
}
@@ -0,0 +1,24 @@
package jadx.gui.events.types;
import jadx.api.plugins.events.IJadxEvent;
import jadx.api.plugins.events.JadxEventType;
import jadx.gui.events.JadxGuiEvents;
import jadx.gui.treemodel.JRoot;
public class TreeUpdate implements IJadxEvent {
private final JRoot jRoot;
public TreeUpdate(JRoot jRoot) {
this.jRoot = jRoot;
}
public JRoot getJRoot() {
return jRoot;
}
@Override
public JadxEventType<TreeUpdate> getType() {
return JadxGuiEvents.TREE_UPDATE;
}
}
@@ -11,6 +11,8 @@ import jadx.gui.ui.MainWindow;
public interface JRenameNode {
JavaNode getJavaNode();
String getTitle();
String getName();
@@ -78,11 +78,14 @@ import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Level;
import jadx.api.JadxArgs;
import jadx.api.JadxDecompiler;
import jadx.api.JavaNode;
import jadx.api.ResourceFile;
import jadx.api.plugins.events.IJadxEvents;
import jadx.api.plugins.utils.CommonFileUtils;
import jadx.core.Jadx;
import jadx.core.export.TemplateFile;
import jadx.core.plugins.events.JadxEventsImpl;
import jadx.core.utils.ListUtils;
import jadx.core.utils.StringUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
@@ -1639,4 +1642,15 @@ public class MainWindow extends JFrame {
public RenameMappingsGui getRenameMappings() {
return renameMappings;
}
/**
* Events instance if decompiler not yet available
*/
private final IJadxEvents fallbackEvents = new JadxEventsImpl();
public IJadxEvents events() {
return wrapper.getCurrentDecompiler()
.map(JadxDecompiler::events)
.orElse(fallbackEvents);
}
}
@@ -34,6 +34,8 @@ import org.slf4j.LoggerFactory;
import jadx.api.JavaNode;
import jadx.api.data.ICodeRename;
import jadx.api.data.impl.JadxCodeData;
import jadx.api.metadata.ICodeNodeRef;
import jadx.api.plugins.events.types.NodeRenamedByUser;
import jadx.core.utils.Utils;
import jadx.gui.jobs.TaskStatus;
import jadx.gui.settings.JadxProject;
@@ -126,13 +128,21 @@ public class RenameDialog extends JDialog {
private void processRename(String newName, Set<ICodeRename> renames) {
ICodeRename rename = node.buildCodeRename(newName, renames);
renames.remove(rename);
String oldName = node.getName();
if (newName.isEmpty()) {
node.removeAlias();
sendRenameEvent(oldName, node.getJavaNode().getName());
} else {
renames.add(rename);
sendRenameEvent(oldName, newName);
}
}
private void sendRenameEvent(String oldName, String newName) {
ICodeNodeRef nodeRef = node.getJavaNode().getCodeNodeRef();
mainWindow.events().send(new NodeRenamedByUser(nodeRef, oldName, newName));
}
private void updateCodeRenames(Consumer<Set<ICodeRename>> updater) {
JadxProject project = mainWindow.getProject();
JadxCodeData codeData = project.getCodeData();
@@ -35,6 +35,11 @@ public class JRenamePackage implements JRenameNode {
this.name = name;
}
@Override
public JavaNode getJavaNode() {
return refPkg;
}
@Override
public String getTitle() {
return fullName;
@@ -22,6 +22,7 @@ sourceSets {
main {
kotlin.srcDirs(
"scripts",
"scripts/gui",
"context"
)
}
@@ -0,0 +1,11 @@
import jadx.api.plugins.events.JadxEvents
/**
* Log events
*/
val jadx = getJadxInstance()
jadx.events.addListener(JadxEvents.NODE_RENAMED_BY_USER) { rename ->
log.info { "Rename from '${rename.oldName}' to '${rename.newName}' for node ${rename.node}" }
}
@@ -7,6 +7,7 @@ import jadx.api.JadxArgs
import jadx.api.JadxDecompiler
import jadx.api.JavaClass
import jadx.api.plugins.JadxPluginContext
import jadx.api.plugins.events.IJadxEvents
import jadx.api.plugins.pass.JadxPass
import jadx.plugins.script.runtime.data.Debug
import jadx.plugins.script.runtime.data.Decompile
@@ -50,6 +51,9 @@ class JadxScriptInstance(
val gui: Gui by lazy { Gui(this, scriptData.pluginContext.guiContext) }
val debug: Debug by lazy { Debug(this) }
val events: IJadxEvents
get() = scriptData.pluginContext.events()
val args: JadxArgs
get() = decompiler.args