Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
[versions]
acc-agent = "1.0.4"
assertj = "3.27.3"
asm = "9.8"
atlantafx = "2.0.1"
Expand All @@ -9,7 +10,7 @@ cdi-impl = "6.0.2.Final"
cfr = "0.152"
dex-translator = "1.1.1"
diffutils = "4.15"
docking = "1.2.3"
docking = "0.7.3"
downgrader = "1.1.2"
extra-collections = "1.6.0"
extra-observables = "1.3.0"
Expand Down Expand Up @@ -47,6 +48,8 @@ shadow = "8.3.5"
peterabeles-gversion = "1.10.3"

[libraries]
acc-agent = { module = "software.coley:javafx-access-agent", version.ref = "acc-agent" }

assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }

asm-core = { module = "org.ow2.asm:asm", version.ref = "asm" }
Expand All @@ -71,7 +74,7 @@ dex-translator = { module = "software.coley:dex-translator", version.ref = "dex-

diffutils = { module = "io.github.java-diff-utils:java-diff-utils", version.ref = "diffutils" }

docking = { module = "com.github.Col-E:tiwulfx-dock", version.ref = "docking" }
docking = { module = "software.coley:bento-fx", version.ref = "docking" }

downgrader = { module = "com.github.RaphiMC.JavaDowngrader:core", version.ref = "downgrader" }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,9 @@ default boolean doVisitFile(@Nonnull FileInfo file) {
default boolean doAcceptResult(@Nonnull Result<?> result) {
return true;
}

/**
* Called when the search query completes.
*/
default void onCompletion() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ public Results search(@Nonnull Workspace workspace, @Nonnull List<Query> queries
searchResource(results, service, feedback, resource, workspaceNode,
androidClassVisitor, jvmClassVisitor, fileVisitor);
ThreadUtil.blockUntilComplete(service);

// Notify feedback of search completion
feedback.onCompletion();

return results;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package software.coley.recaf.util.threading;

import jakarta.annotation.Nonnull;

/**
* Outlines a model to wrap multiple tasks into a single execution.
*
* @author Matt Coley
*/
public interface Batch {
/**
* Run all registered tasks. This will remove all executed tasks once completed.
*/
void execute();

/**
* @param runnable
* Task to execute.
*/
void add(@Nonnull Runnable runnable);

/**
* Clears tasks.
*/
void clear();
}
1 change: 1 addition & 0 deletions recaf-ui/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ dependencies {
implementation project(':recaf-core')
testImplementation(testFixtures(project(":recaf-core")))

implementation(libs.acc.agent)
implementation(libs.atlantafx)
implementation(libs.docking)
implementation(libs.bundles.ikonli)
Expand Down
29 changes: 29 additions & 0 deletions recaf-ui/src/main/java/software/coley/recaf/Main.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import jakarta.enterprise.inject.spi.Bean;
import org.slf4j.Logger;
import picocli.CommandLine;
import software.coley.fxaccess.AccessCheck;
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.cdi.EagerInitialization;
import software.coley.recaf.cdi.EagerInitializationExtension;
Expand Down Expand Up @@ -120,6 +121,7 @@ private static void initialize() {
initPlugins();
fireInitEvent();
} else {
initFxAccessAgent();
initTranslations();
initPlugins();
fireInitEvent();
Expand All @@ -140,6 +142,33 @@ private static void initScale() {
System.setProperty("glass.gtk.uiScale", String.valueOf(scale));
}

/**
* Configure the JavaFX access logging agent.
* The logging is only active when the agent is passed as a launch argument to Recaf.
* <br>
* Example usage: {@code -javaagent:javafx-access-agent.jar=software/;org/;com/;javafx/}
*/
private static void initFxAccessAgent() {
AccessCheck.addAccessCheckListener((className, methodName, lineNumber, threadName, calledMethodSignature) -> {
// Some kinds of operations are safe and can be ignored.
if (calledMethodSignature != null) {
// Skip on constructors
if (calledMethodSignature.contains("<"))
return;

// Skip on get operations
if (calledMethodSignature.contains("#get"))
return;

// Skip on things that will be operated on later
if (calledMethodSignature.contains("#setOn") || calledMethodSignature.contains("#addListener"))
return;
}

System.err.printf("[thread:%s] %s.%s (line %d) - %s\n", threadName, className, methodName, lineNumber, calledMethodSignature);
});
}

/**
* Publishes the {@link InitializationEvent} so that {@link EagerInitialization} annotated services marked to be run
* {@link InitializationStage#IMMEDIATE immediately} are initialized.
Expand Down
65 changes: 10 additions & 55 deletions recaf-ui/src/main/java/software/coley/recaf/RecafApplication.java
Original file line number Diff line number Diff line change
@@ -1,48 +1,31 @@
package software.coley.recaf;

import jakarta.annotation.Nonnull;
import javafx.application.Application;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.SplitPane;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import org.kordamp.ikonli.carbonicons.CarbonIcons;
import software.coley.recaf.cdi.UiInitializationEvent;
import software.coley.recaf.services.window.WindowManager;
import software.coley.recaf.services.workspace.WorkspaceCloseListener;
import software.coley.recaf.services.workspace.WorkspaceManager;
import software.coley.recaf.services.workspace.WorkspaceOpenListener;
import software.coley.recaf.ui.RecafTheme;
import software.coley.recaf.ui.config.KeybindingConfig;
import software.coley.recaf.ui.config.WindowScaleConfig;
import software.coley.recaf.ui.control.FontIconView;
import software.coley.recaf.ui.docking.DockingManager;
import software.coley.recaf.ui.docking.DockingRegion;
import software.coley.recaf.ui.docking.DockingTab;
import software.coley.recaf.ui.menubar.MainMenu;
import software.coley.recaf.ui.pane.LoggingPane;
import software.coley.recaf.ui.pane.WelcomePane;
import software.coley.recaf.ui.pane.WorkspaceRootPane;
import software.coley.recaf.ui.docking.DockingLayoutManager;
import software.coley.recaf.ui.window.RecafScene;
import software.coley.recaf.util.FxThreadUtil;
import software.coley.recaf.util.Icons;
import software.coley.recaf.util.Lang;
import software.coley.recaf.workspace.PathExportingManager;
import software.coley.recaf.workspace.model.Workspace;

/**
* JavaFX application entry point.
*
* @author Matt Coley
*/
public class RecafApplication extends Application implements WorkspaceOpenListener, WorkspaceCloseListener {
public class RecafApplication extends Application {
private final Recaf recaf = Bootstrap.get();
private final BorderPane root = new BorderPane();
private WorkspaceRootPane workspaceRootPane;
private WelcomePane welcomePane;

@Override
public void start(Stage stage) {
Expand All @@ -52,30 +35,21 @@ public void start(Stage stage) {
// Setup global style
setUserAgentStylesheet(new RecafTheme().getUserAgentStylesheet());

// Get components
MainMenu menu = recaf.get(MainMenu.class);
WelcomePane pane = recaf.get(WelcomePane.class);
Node logging = createLoggingWrapper();
workspaceRootPane = recaf.get(WorkspaceRootPane.class);
welcomePane = recaf.get(WelcomePane.class);
// Get services
DockingLayoutManager dockingLayoutManager = recaf.get(DockingLayoutManager.class);
KeybindingConfig keybindingConfig = recaf.get(KeybindingConfig.class);
WindowManager windowManager = recaf.get(WindowManager.class);
WorkspaceManager workspaceManager = recaf.get(WorkspaceManager.class);

// Get components
MainMenu menu = recaf.get(MainMenu.class);
LoggingPane logging = recaf.get(LoggingPane.class);

// Layout
SplitPane splitPane = new SplitPane(root, logging);
SplitPane.setResizableWithParent(logging, false);
splitPane.setOrientation(Orientation.VERTICAL);
splitPane.setDividerPositions(0.21); // Behaves inverse to expectation in these specific circumstances
BorderPane wrapper = new BorderPane();
wrapper.setTop(menu);
wrapper.setCenter(splitPane);
wrapper.setCenter(dockingLayoutManager.getRoot().getBackingRegion());
wrapper.getStyleClass().addAll("padded", "bg-inset");
root.setCenter(pane);

// Register listener
WorkspaceManager workspaceManager = recaf.get(WorkspaceManager.class);
workspaceManager.addWorkspaceOpenListener(this);
workspaceManager.addWorkspaceCloseListener(this);

// Display
WindowScaleConfig scaleConfig = recaf.get(WindowScaleConfig.class);
Expand Down Expand Up @@ -105,23 +79,4 @@ public void start(Stage stage) {
recaf.getContainer().getBeanContainer().getEvent().fire(new UiInitializationEvent());
}

@Nonnull
private Node createLoggingWrapper() {
LoggingPane logging = recaf.get(LoggingPane.class);
DockingRegion dockingPane = recaf.get(DockingManager.class).newRegion();
DockingTab tab = dockingPane.createTab(Lang.getBinding("logging.title"), logging);
tab.setGraphic(new FontIconView(CarbonIcons.TERMINAL));
tab.setClosable(false);
return dockingPane;
}

@Override
public void onWorkspaceClosed(@Nonnull Workspace workspace) {
FxThreadUtil.run(() -> root.setCenter(welcomePane));
}

@Override
public void onWorkspaceOpened(@Nonnull Workspace workspace) {
FxThreadUtil.run(() -> root.setCenter(workspaceRootPane));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import software.coley.recaf.analytics.logging.Logging;
import software.coley.recaf.services.Service;
import software.coley.recaf.ui.pane.WorkspaceInformationPane;
import software.coley.recaf.util.FxThreadUtil;
import software.coley.recaf.util.threading.ThreadPoolFactory;
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.resource.WorkspaceResource;
Expand Down Expand Up @@ -68,7 +69,7 @@ public CompletableFuture<Void> summarizeTo(@Nonnull Workspace workspace,
boolean lastSummarizerAppended = false;
for (ResourceSummarizer summarizer : new TreeSet<>(summarizers.values())) {
if (lastSummarizerAppended)
consumer.appendSummary(new Separator());
FxThreadUtil.run(() -> consumer.appendSummary(new Separator()));
try {
lastSummarizerAppended = summarizer.summarize(workspace, resource, consumer);
} catch (Throwable t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Label;
import software.coley.recaf.info.ClassInfo;
import software.coley.recaf.info.JvmClassInfo;
import software.coley.recaf.info.member.MethodMember;
import software.coley.recaf.services.cell.icon.IconProviderService;
Expand All @@ -17,7 +16,9 @@
import software.coley.recaf.services.info.summary.SummaryConsumer;
import software.coley.recaf.services.navigation.Actions;
import software.coley.recaf.ui.control.BoundLabel;
import software.coley.recaf.util.FxThreadUtil;
import software.coley.recaf.util.Lang;
import software.coley.recaf.util.threading.Batch;
import software.coley.recaf.workspace.model.Workspace;
import software.coley.recaf.workspace.model.resource.WorkspaceResource;

Expand All @@ -41,20 +42,23 @@ public class EntryPointSummarizer implements ResourceSummarizer {

@Inject
public EntryPointSummarizer(@Nonnull TextProviderService textService,
@Nonnull IconProviderService iconService,
@Nonnull Actions actions) {
@Nonnull IconProviderService iconService,
@Nonnull Actions actions) {
this.textService = textService;
this.iconService = iconService;
this.actions = actions;
}

@Override
public boolean summarize(@Nonnull Workspace workspace,
@Nonnull WorkspaceResource resource,
@Nonnull SummaryConsumer consumer) {
Label title = new BoundLabel(Lang.getBinding("service.analysis.entry-points"));
title.getStyleClass().addAll(Styles.TITLE_4);
consumer.appendSummary(title);
@Nonnull WorkspaceResource resource,
@Nonnull SummaryConsumer consumer) {
Batch batch = FxThreadUtil.batch();
batch.add(() -> {
Label title = new BoundLabel(Lang.getBinding("service.analysis.entry-points"));
title.getStyleClass().addAll(Styles.TITLE_4);
consumer.appendSummary(title);
});

// Visit JVM classes
int[] found = {0};
Expand All @@ -64,42 +68,45 @@ public boolean summarize(@Nonnull Workspace workspace,
.filter(this::isJvmEntry)
.toList();
if (!entryMethods.isEmpty()) {
Supplier<JvmClassInfo> classLookup = () -> Objects.requireNonNullElse(bundle.get(cls.getName()), cls);
found[0]++;
batch.add(() -> {
Supplier<JvmClassInfo> classLookup = () -> Objects.requireNonNullElse(bundle.get(cls.getName()), cls);

// Add entry for class
String classDisplay = textService.getJvmClassInfoTextProvider(workspace, resource, bundle, cls).makeText();
Node classIcon = iconService.getJvmClassInfoIconProvider(workspace, resource, bundle, cls).makeIcon();
Label classLabel = new Label(classDisplay, classIcon);
classLabel.setCursor(Cursor.HAND);
classLabel.setOnMouseEntered(e -> classLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));
classLabel.setOnMouseExited(e -> classLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));
classLabel.setOnMouseClicked(e -> actions.gotoDeclaration(workspace, resource, bundle, classLookup.get()));
consumer.appendSummary(classLabel);
// Add entry for class
String classDisplay = textService.getJvmClassInfoTextProvider(workspace, resource, bundle, cls).makeText();
Node classIcon = iconService.getJvmClassInfoIconProvider(workspace, resource, bundle, cls).makeIcon();
Label classLabel = new Label(classDisplay, classIcon);
classLabel.setCursor(Cursor.HAND);
classLabel.setOnMouseEntered(e -> classLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));
classLabel.setOnMouseExited(e -> classLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));
classLabel.setOnMouseClicked(e -> actions.gotoDeclaration(workspace, resource, bundle, classLookup.get()));
consumer.appendSummary(classLabel);

// Add entries for methods
for (MethodMember method : entryMethods) {
String methodDisplay = textService.getMethodMemberTextProvider(workspace, resource, bundle, cls, method).makeText();
Node methodIcon = iconService.getClassMemberIconProvider(workspace, resource, bundle, cls, method).makeIcon();
Label methodLabel = new Label(methodDisplay);
methodLabel.setCursor(Cursor.HAND);
methodLabel.setGraphic(methodIcon);
methodLabel.setPadding(new Insets(2, 2, 2, 15));
methodLabel.setOnMouseEntered(e -> methodLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));
methodLabel.setOnMouseExited(e -> methodLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));
methodLabel.setOnMouseClicked(e -> {
actions.gotoDeclaration(workspace, resource, bundle, classLookup.get())
.requestFocus(method);
});
consumer.appendSummary(methodLabel);
found[0]++;
}
// Add entries for methods
for (MethodMember method : entryMethods) {
String methodDisplay = textService.getMethodMemberTextProvider(workspace, resource, bundle, cls, method).makeText();
Node methodIcon = iconService.getClassMemberIconProvider(workspace, resource, bundle, cls, method).makeIcon();
Label methodLabel = new Label(methodDisplay);
methodLabel.setCursor(Cursor.HAND);
methodLabel.setGraphic(methodIcon);
methodLabel.setPadding(new Insets(2, 2, 2, 15));
methodLabel.setOnMouseEntered(e -> methodLabel.getStyleClass().add(Styles.TEXT_UNDERLINED));
methodLabel.setOnMouseExited(e -> methodLabel.getStyleClass().remove(Styles.TEXT_UNDERLINED));
methodLabel.setOnMouseClicked(e -> {
actions.gotoDeclaration(workspace, resource, bundle, classLookup.get())
.requestFocus(method);
});
consumer.appendSummary(methodLabel);
}
});
}
});
});

if (found[0] == 0) {
consumer.appendSummary(new BoundLabel(Lang.getBinding("service.analysis.entry-points.none")));
}
if (found[0] == 0)
batch.add(() -> consumer.appendSummary(new BoundLabel(Lang.getBinding("service.analysis.entry-points.none"))));

batch.execute();

return true;
}
Expand Down
Loading