diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicDirectoryContextMenuProviderFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicDirectoryContextMenuProviderFactory.java index 06ca933a5..aa57c5757 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicDirectoryContextMenuProviderFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicDirectoryContextMenuProviderFactory.java @@ -55,6 +55,8 @@ public ContextMenuProvider getDirectoryContextMenuProvider(@Nonnull ContextSourc refactor.directoryItem("menu.refactor.move", STACKED_MOVE, actions::moveDirectory); refactor.directoryItem("menu.refactor.rename", TAG_EDIT, actions::renameDirectory); + builder.directoryItem("menu.export.directory", EXPORT, actions::exportDirectory); + // TODO: implement operations // - Search references } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicPackageContextMenuProviderFactory.java b/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicPackageContextMenuProviderFactory.java index 73464763b..7933a3157 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicPackageContextMenuProviderFactory.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/cell/context/BasicPackageContextMenuProviderFactory.java @@ -62,6 +62,8 @@ public ContextMenuProvider getPackageContextMenuProvider(@Nonnull ContextSource var refactor = jvmBuilder.submenu("menu.refactor", PAINT_BRUSH); refactor.directoryItem("menu.refactor.move", STACKED_MOVE, actions::movePackage); refactor.directoryItem("menu.refactor.rename", TAG_EDIT, actions::renamePackage); + + jvmBuilder.directoryItem("menu.export.package", EXPORT, actions::exportPackage); } } diff --git a/recaf-ui/src/main/java/software/coley/recaf/services/navigation/Actions.java b/recaf-ui/src/main/java/software/coley/recaf/services/navigation/Actions.java index 78cd45b40..4689aa242 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/services/navigation/Actions.java +++ b/recaf-ui/src/main/java/software/coley/recaf/services/navigation/Actions.java @@ -1,6 +1,7 @@ package software.coley.recaf.services.navigation; import jakarta.annotation.Nonnull; +import jakarta.annotation.Nullable; import jakarta.enterprise.context.ApplicationScoped; import jakarta.enterprise.inject.Instance; import jakarta.inject.Inject; @@ -25,7 +26,14 @@ import software.coley.recaf.info.member.ClassMember; import software.coley.recaf.info.member.FieldMember; import software.coley.recaf.info.member.MethodMember; -import software.coley.recaf.path.*; +import software.coley.recaf.path.ClassMemberPathNode; +import software.coley.recaf.path.ClassPathNode; +import software.coley.recaf.path.DirectoryPathNode; +import software.coley.recaf.path.FilePathNode; +import software.coley.recaf.path.IncompletePathException; +import software.coley.recaf.path.LineNumberPathNode; +import software.coley.recaf.path.PathNode; +import software.coley.recaf.path.PathNodes; import software.coley.recaf.services.Service; import software.coley.recaf.services.cell.CellConfigurationService; import software.coley.recaf.services.cell.icon.IconProviderService; @@ -58,16 +66,45 @@ import software.coley.recaf.ui.pane.editing.media.ImageFilePane; import software.coley.recaf.ui.pane.editing.media.VideoFilePane; import software.coley.recaf.ui.pane.editing.text.TextFilePane; -import software.coley.recaf.ui.pane.search.*; +import software.coley.recaf.ui.pane.search.AbstractSearchPane; +import software.coley.recaf.ui.pane.search.ClassReferenceSearchPane; +import software.coley.recaf.ui.pane.search.MemberReferenceSearchPane; +import software.coley.recaf.ui.pane.search.NumberSearchPane; +import software.coley.recaf.ui.pane.search.StringSearchPane; import software.coley.recaf.ui.window.RecafScene; -import software.coley.recaf.util.*; -import software.coley.recaf.util.visitors.*; +import software.coley.recaf.util.ClipboardUtil; +import software.coley.recaf.util.EscapeUtil; +import software.coley.recaf.util.FxThreadUtil; +import software.coley.recaf.util.Lang; +import software.coley.recaf.util.SceneUtils; +import software.coley.recaf.util.StringUtil; +import software.coley.recaf.util.visitors.ClassAnnotationRemovingVisitor; +import software.coley.recaf.util.visitors.FieldAnnotationRemovingVisitor; +import software.coley.recaf.util.visitors.FieldPredicate; +import software.coley.recaf.util.visitors.MemberCopyingVisitor; +import software.coley.recaf.util.visitors.MemberRemovingVisitor; +import software.coley.recaf.util.visitors.MemberStubAddingVisitor; +import software.coley.recaf.util.visitors.MethodAnnotationRemovingVisitor; +import software.coley.recaf.util.visitors.MethodNoopingVisitor; +import software.coley.recaf.util.visitors.MethodPredicate; import software.coley.recaf.workspace.PathExportingManager; +import software.coley.recaf.workspace.model.BasicWorkspace; import software.coley.recaf.workspace.model.Workspace; -import software.coley.recaf.workspace.model.bundle.*; +import software.coley.recaf.workspace.model.bundle.AndroidClassBundle; +import software.coley.recaf.workspace.model.bundle.BasicFileBundle; +import software.coley.recaf.workspace.model.bundle.BasicJvmClassBundle; +import software.coley.recaf.workspace.model.bundle.Bundle; +import software.coley.recaf.workspace.model.bundle.ClassBundle; +import software.coley.recaf.workspace.model.bundle.FileBundle; +import software.coley.recaf.workspace.model.bundle.JvmClassBundle; import software.coley.recaf.workspace.model.resource.WorkspaceResource; +import software.coley.recaf.workspace.model.resource.WorkspaceResourceBuilder; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -119,9 +156,9 @@ public Actions(@Nonnull ActionsConfig config, @Nonnull WindowFactory windowFactory, @Nonnull TextProviderService textService, @Nonnull IconProviderService iconService, - @Nonnull CellConfigurationService cellConfigurationService, + @Nonnull CellConfigurationService cellConfigurationService, @Nonnull PathExportingManager pathExportingManager, - @Nonnull Instance graphProvider, + @Nonnull Instance graphProvider, @Nonnull Instance applierProvider, @Nonnull Instance jvmPaneProvider, @Nonnull Instance androidPaneProvider, @@ -146,7 +183,7 @@ public Actions(@Nonnull ActionsConfig config, this.iconService = iconService; this.cellConfigurationService = cellConfigurationService; this.pathExportingManager = pathExportingManager; - this.graphProvider= graphProvider; + this.graphProvider = graphProvider; this.applierProvider = applierProvider; this.jvmPaneProvider = jvmPaneProvider; this.androidPaneProvider = androidPaneProvider; @@ -1656,6 +1693,62 @@ public void exportClass(@Nonnull Workspace workspace, pathExportingManager.export(info); } + /** + * Exports a package, prompting the user to select a location to save the file to. + * + * @param workspace + * Containing workspace. + * @param resource + * Containing resource. + * @param bundle + * Containing bundle. + * @param packageName + * Name of package to export. + */ + public void exportPackage(@Nonnull Workspace workspace, + @Nonnull WorkspaceResource resource, + @Nonnull JvmClassBundle bundle, + @Nullable String packageName) { + JvmClassBundle bundleCopy = new BasicJvmClassBundle(); + bundle.valuesAsCopy().forEach(cls -> { + if (packageName == null && cls.getPackageName() == null) + bundleCopy.put(cls); + else if (cls.getName().startsWith(packageName + "/")) + bundleCopy.put(cls); + }); + WorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withJvmClassBundle(bundleCopy).build(); + Workspace workspaceCopy = new BasicWorkspace(resourceCopy); + pathExportingManager.export(workspaceCopy, "package", false); + } + + /** + * Exports a directory, prompting the user to select a location to save the file to. + * + * @param workspace + * Containing workspace. + * @param resource + * Containing resource. + * @param bundle + * Containing bundle. + * @param directoryName + * Name of directory to export. + */ + public void exportDirectory(@Nonnull Workspace workspace, + @Nonnull WorkspaceResource resource, + @Nonnull FileBundle bundle, + @Nullable String directoryName) { + FileBundle bundleCopy = new BasicFileBundle(); + bundle.valuesAsCopy().forEach(cls -> { + if (directoryName == null && cls.getDirectoryName() == null) + bundleCopy.put(cls); + else if (cls.getName().startsWith(directoryName + "/")) + bundleCopy.put(cls); + }); + WorkspaceResource resourceCopy = new WorkspaceResourceBuilder().withFileBundle(bundleCopy).build(); + Workspace workspaceCopy = new BasicWorkspace(resourceCopy); + pathExportingManager.export(workspaceCopy, "directory", false); + } + /** * Prompts the user (if configured, otherwise prompt is skipped) to delete the class. * @@ -2002,7 +2095,7 @@ public void addClassMethod(@Nonnull Workspace workspace, } /** - * Prompts the user for method declaration info, to add it to the given class. + * Prompts the user to select a method to override. * * @param workspace * Containing workspace. @@ -2014,9 +2107,9 @@ public void addClassMethod(@Nonnull Workspace workspace, * Class to update. */ public void overrideClassMethod(@Nonnull Workspace workspace, - @Nonnull WorkspaceResource resource, - @Nonnull JvmClassBundle bundle, - @Nonnull JvmClassInfo info) { + @Nonnull WorkspaceResource resource, + @Nonnull JvmClassBundle bundle, + @Nonnull JvmClassInfo info) { InheritanceGraph graph = graphProvider.get(); if (graph == null) return; diff --git a/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java b/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java index 694a04f0b..4fa932a3f 100644 --- a/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java +++ b/recaf-ui/src/main/java/software/coley/recaf/workspace/PathExportingManager.java @@ -15,10 +15,19 @@ import software.coley.recaf.info.Info; import software.coley.recaf.info.JvmClassInfo; import software.coley.recaf.services.workspace.WorkspaceManager; -import software.coley.recaf.services.workspace.io.*; +import software.coley.recaf.services.workspace.io.PathWorkspaceExportConsumer; +import software.coley.recaf.services.workspace.io.WorkspaceCompressType; +import software.coley.recaf.services.workspace.io.WorkspaceExportOptions; +import software.coley.recaf.services.workspace.io.WorkspaceExporter; +import software.coley.recaf.services.workspace.io.WorkspaceOutputType; import software.coley.recaf.ui.config.ExportConfig; import software.coley.recaf.ui.config.RecentFilesConfig; -import software.coley.recaf.util.*; +import software.coley.recaf.util.DirectoryChooserBuilder; +import software.coley.recaf.util.ErrorDialogs; +import software.coley.recaf.util.FileChooserBuilder; +import software.coley.recaf.util.Icons; +import software.coley.recaf.util.Lang; +import software.coley.recaf.util.StringUtil; import software.coley.recaf.workspace.model.Workspace; import software.coley.recaf.workspace.model.resource.WorkspaceDirectoryResource; import software.coley.recaf.workspace.model.resource.WorkspaceFileResource; @@ -69,18 +78,36 @@ public void exportCurrent() { * Workspace to export. */ public void export(@Nonnull Workspace workspace) { + export(workspace, "workspace", true); + } + + /** + * Export the given workspace. + * + * @param workspace + * Workspace to export. + * @param contentDescription + * Description string for what is within the workspace. + * @param alertWhenEmpty + * {@code true} to warn users before exporting that no changes are present in the workspace. + * {@code false} to skip the warning. + */ + public void export(@Nonnull Workspace workspace, @Nonnull String contentDescription, boolean alertWhenEmpty) { + WorkspaceResource primaryResource = workspace.getPrimaryResource(); + // Check if the user hasn't made any changes. Plenty of people have not understood that their changes weren't // saved for one reason or another (the amount of people seeing a red flash thinking that is fine is crazy) - WorkspaceResource primaryResource = workspace.getPrimaryResource(); - boolean noChangesFound = exportConfig.getWarnNoChanges().getValue() && primaryResource.bundleStreamRecursive() - .allMatch(b -> b.getDirtyKeys().isEmpty()); - if (noChangesFound) { - Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Lang.get("dialog.file.nochanges"), ButtonType.YES, ButtonType.NO); - alert.setTitle(Lang.get("dialog.title.nochanges")); - Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); - stage.getIcons().add(Icons.getImage(Icons.LOGO)); - if (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES) - return; + if (alertWhenEmpty) { + boolean noChangesFound = exportConfig.getWarnNoChanges().getValue() && primaryResource.bundleStreamRecursive() + .allMatch(b -> b.getDirtyKeys().isEmpty()); + if (noChangesFound) { + Alert alert = new Alert(Alert.AlertType.CONFIRMATION, Lang.get("dialog.file.nochanges"), ButtonType.YES, ButtonType.NO); + alert.setTitle(Lang.get("dialog.title.nochanges")); + Stage stage = (Stage) alert.getDialogPane().getScene().getWindow(); + stage.getIcons().add(Icons.getImage(Icons.LOGO)); + if (alert.showAndWait().orElse(ButtonType.NO) != ButtonType.YES) + return; + } } // Prompt a path for the user to write to. @@ -117,9 +144,9 @@ public void export(@Nonnull Workspace workspace) { WorkspaceExporter exporter = createResourceExporter(primaryResource, exportPath); try { exporter.export(workspace); - logger.info("Exported workspace to path '{}'", exportPath); + logger.info("Exported " + contentDescription + " to path '{}'", exportPath); } catch (IOException ex) { - logger.error("Failed to export workspace to path '{}'", exportPath, ex); + logger.error("Failed to export " + contentDescription + " to path '{}'", exportPath, ex); ErrorDialogs.show( Lang.getBinding("dialog.error.exportworkspace.title"), Lang.getBinding("dialog.error.exportworkspace.header"), diff --git a/recaf-ui/src/main/resources/translations/en_US.lang b/recaf-ui/src/main/resources/translations/en_US.lang index 2c4711f05..b1af65b94 100644 --- a/recaf-ui/src/main/resources/translations/en_US.lang +++ b/recaf-ui/src/main/resources/translations/en_US.lang @@ -52,6 +52,8 @@ menu.edit.changeversion.up=Upgrade menu.edit.changeversion.down=Downgrade menu.export.class=Export class menu.export.file=Export file +menu.export.package=Export package +menu.export.directory=Export directory menu.help=Help menu.help.discord=Discord menu.help.docs=Online user documentation