Skip to content

Commit

Permalink
Add class action to implement overridable methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Col-E committed Nov 11, 2024
1 parent 40ecd65 commit 702674f
Show file tree
Hide file tree
Showing 4 changed files with 145 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ private void populateJvmMenu(@Nonnull ContextMenu menu,
));
edit.infoItem("menu.edit.add.field", ADD_ALT, actions::addClassField);
edit.infoItem("menu.edit.add.method", ADD_ALT, actions::addClassMethod);
edit.infoItem("menu.edit.override.method", HEALTH_CROSS, actions::overrideClassMethod);
edit.infoItem("menu.edit.remove.field", CLOSE, actions::deleteClassFields).disableWhen(info.getFields().isEmpty());
edit.infoItem("menu.edit.remove.method", CLOSE, actions::deleteClassMethods).disableWhen(info.getMethods().isEmpty());
edit.infoItem("menu.edit.remove.annotation", CLOSE, actions::deleteClassAnnotations).disableWhen(info.getAnnotations().isEmpty());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
import software.coley.recaf.info.member.MethodMember;
import software.coley.recaf.path.*;
import software.coley.recaf.services.Service;
import software.coley.recaf.services.cell.CellConfigurationService;
import software.coley.recaf.services.cell.icon.IconProviderService;
import software.coley.recaf.services.cell.text.TextProviderService;
import software.coley.recaf.services.inheritance.InheritanceGraph;
import software.coley.recaf.services.mapping.IntermediateMappings;
import software.coley.recaf.services.mapping.MappingApplier;
import software.coley.recaf.services.mapping.MappingResults;
Expand All @@ -39,6 +41,7 @@
import software.coley.recaf.ui.control.popup.ItemListSelectionPopup;
import software.coley.recaf.ui.control.popup.ItemTreeSelectionPopup;
import software.coley.recaf.ui.control.popup.NamePopup;
import software.coley.recaf.ui.control.popup.OverrideMethodPopup;
import software.coley.recaf.ui.docking.DockingManager;
import software.coley.recaf.ui.docking.DockingRegion;
import software.coley.recaf.ui.docking.DockingTab;
Expand Down Expand Up @@ -88,7 +91,9 @@ public class Actions implements Service {
private final WindowFactory windowFactory;
private final TextProviderService textService;
private final IconProviderService iconService;
private final CellConfigurationService cellConfigurationService;
private final PathExportingManager pathExportingManager;
private final Instance<InheritanceGraph> graphProvider;
private final Instance<MappingApplier> applierProvider;
private final Instance<JvmClassPane> jvmPaneProvider;
private final Instance<AndroidClassPane> androidPaneProvider;
Expand All @@ -114,7 +119,9 @@ public Actions(@Nonnull ActionsConfig config,
@Nonnull WindowFactory windowFactory,
@Nonnull TextProviderService textService,
@Nonnull IconProviderService iconService,
@Nonnull CellConfigurationService cellConfigurationService,
@Nonnull PathExportingManager pathExportingManager,
@Nonnull Instance<InheritanceGraph> graphProvider,
@Nonnull Instance<MappingApplier> applierProvider,
@Nonnull Instance<JvmClassPane> jvmPaneProvider,
@Nonnull Instance<AndroidClassPane> androidPaneProvider,
Expand All @@ -137,7 +144,9 @@ public Actions(@Nonnull ActionsConfig config,
this.windowFactory = windowFactory;
this.textService = textService;
this.iconService = iconService;
this.cellConfigurationService = cellConfigurationService;
this.pathExportingManager = pathExportingManager;
this.graphProvider= graphProvider;
this.applierProvider = applierProvider;
this.jvmPaneProvider = jvmPaneProvider;
this.androidPaneProvider = androidPaneProvider;
Expand Down Expand Up @@ -1992,6 +2001,42 @@ public void addClassMethod(@Nonnull Workspace workspace,
}).forMethod(info).show();
}

/**
* Prompts the user for method declaration info, to add it to the given class.
*
* @param workspace
* Containing workspace.
* @param resource
* Containing resource.
* @param bundle
* Containing bundle.
* @param info
* Class to update.
*/
public void overrideClassMethod(@Nonnull Workspace workspace,
@Nonnull WorkspaceResource resource,
@Nonnull JvmClassBundle bundle,
@Nonnull JvmClassInfo info) {
InheritanceGraph graph = graphProvider.get();
if (graph == null)
return;
new OverrideMethodPopup(this, cellConfigurationService, graph, workspace, info, (methodOwner, method) -> {
ClassWriter writer = new ClassWriter(0);
info.getClassReader().accept(new MemberStubAddingVisitor(writer, method), 0);
JvmClassInfo updatedInfo = info.toJvmClassBuilder()
.adaptFrom(writer.toByteArray())
.build();
bundle.put(updatedInfo);

// Open the assembler with the new method
try {
openAssembler(PathNodes.memberPath(workspace, resource, bundle, updatedInfo, method));
} catch (IncompletePathException e) {
logger.error("Failed to open assembler for new method", e);
}
}).show();
}

/**
* Makes the given methods no-op.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package software.coley.recaf.ui.control.popup;

import atlantafx.base.controls.Spacer;
import jakarta.annotation.Nonnull;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.control.Button;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.HBox;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import software.coley.recaf.info.ClassInfo;
import software.coley.recaf.info.member.MethodMember;
import software.coley.recaf.path.ClassMemberPathNode;
import software.coley.recaf.path.ClassPathNode;
import software.coley.recaf.path.PathNode;
import software.coley.recaf.path.PathNodes;
import software.coley.recaf.path.WorkspacePathNode;
import software.coley.recaf.services.cell.CellConfigurationService;
import software.coley.recaf.services.inheritance.InheritanceGraph;
import software.coley.recaf.services.inheritance.InheritanceVertex;
import software.coley.recaf.services.navigation.Actions;
import software.coley.recaf.ui.control.ActionButton;
import software.coley.recaf.ui.control.FontIconView;
import software.coley.recaf.ui.control.PathNodeTree;
import software.coley.recaf.ui.window.RecafScene;
import software.coley.recaf.ui.window.RecafStage;
import software.coley.recaf.util.Lang;
import software.coley.recaf.workspace.model.Workspace;

import java.util.function.BiConsumer;

import static org.kordamp.ikonli.carbonicons.CarbonIcons.CHECKMARK;
import static org.kordamp.ikonli.carbonicons.CarbonIcons.CLOSE;

/**
* Popup to show a selection of method to override from parent classes.
*
* @author Matt Coley
*/
public class OverrideMethodPopup extends RecafStage {
private final PathNodeTree tree;

public OverrideMethodPopup(@Nonnull Actions actions, @Nonnull CellConfigurationService configurationService,
@Nonnull InheritanceGraph graph, @Nonnull Workspace workspace,
@Nonnull ClassInfo targetClass, @Nonnull BiConsumer<ClassInfo, MethodMember> memberConsumer) {
WorkspacePathNode rootPath = PathNodes.workspacePath(workspace);
TreeItem<PathNode<?>> rootItem = new TreeItem<>(rootPath);
tree = new PathNodeTree(configurationService, actions);
tree.setShowRoot(false);
tree.setRoot(rootItem);
tree.getStyleClass().add("border-muted");

// Setup tree contents
InheritanceVertex vertex = graph.getVertex(targetClass.getName());
if (vertex != null) {
vertex.allParents().forEach(parent -> {
ClassPathNode parentPath = workspace.findClass(parent.getName());
if (parentPath == null)
return;
TreeItem<PathNode<?>> parentItem = new TreeItem<>(parentPath);
rootItem.getChildren().add(parentItem);
for (MethodMember method : parent.getValue().getMethods()) {
if (method.hasPrivateModifier() || method.hasFinalModifier() || method.hasNativeModifier())
continue;
parentItem.getChildren().add(new TreeItem<>(parentPath.child(method)));
}
parentItem.setExpanded(true);
});
}

Button acceptButton = new ActionButton(new FontIconView(CHECKMARK, Color.LAWNGREEN), () -> accept(memberConsumer));
Button cancelButton = new ActionButton(new FontIconView(CLOSE, Color.RED), this::hide);
acceptButton.disableProperty().bind(tree.getSelectionModel().selectedItemProperty().map(i -> !(i.getValue() instanceof ClassMemberPathNode)));
HBox buttons = new HBox(acceptButton, new Spacer(), cancelButton);
VBox layout = new VBox(tree, buttons);
layout.setSpacing(10);
layout.setAlignment(Pos.TOP_CENTER);
layout.setPadding(new Insets(10));
setMinWidth(500);
setMinHeight(230);
setMaxHeight(460);
titleProperty().bind(Lang.getBinding("dialog.title.override-method"));
setScene(new RecafScene(layout, 500, 350));
}

private void accept(@Nonnull BiConsumer<ClassInfo, MethodMember> memberConsumer) {
TreeItem<PathNode<?>> selectedItem = tree.getSelectionModel().getSelectedItem();
if (selectedItem != null && selectedItem.getValue() instanceof ClassMemberPathNode memberPath) {
ClassInfo owner = memberPath.getValueOfType(ClassInfo.class);
MethodMember method = (MethodMember) memberPath.getValue();
if (owner != null)
memberConsumer.accept(owner, method);
}
hide();
}
}
2 changes: 2 additions & 0 deletions recaf-ui/src/main/resources/translations/en_US.lang
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ menu.edit=Edit
menu.edit.add.field=Add field
menu.edit.add.method=Add method
menu.edit.add.annotation=Add annotation
menu.edit.override.method=Override method
menu.edit.remove.field=Remove fields
menu.edit.remove.method=Remove methods
menu.edit.remove.annotation=Remove annotations
Expand Down Expand Up @@ -244,6 +245,7 @@ dialog.header.move-package=Move the package into to a new parent package.
## Add members
dialog.title.add-field=Add field
dialog.title.add-method=Add method
dialog.title.override-method=Override method
dialog.input.name=Name
dialog.input.desc=Descriptor
dialog.warn.illegal-name=Illegal name
Expand Down

0 comments on commit 702674f

Please sign in to comment.