How to Implement Project Type "Copy", "Move", "Rename", and "Delete"

You've followed the NetBeans Project Type Tutorial and now you'd like to let the user copy, move, rename, and delete the projects conforming to your project type. When they right-click a project, they should see the relevant menu items and those menu items should provide dialogs for user interaction, followed by event handling code to deal with the current operation.

Right now, at the end of the tutorial, the "Copy" and "Delete" menu items are present but disabled, while the "Move" and "Rename" menu items are absent:


The NetBeans Project API provides a built-in mechanism out of the box that you can leverage for project-level "Copy", "Move", "Rename", and "Delete" actions. All the functionality is there for you to use, while all that you need to do is a bit of enablement and configuration, which is described below.

To get started, read the following from the NetBeans Project API:

Now, let's do some work. For each of the menu items we're interested in, we need to do the following:

  • Provide enablement and invocation handling in an ActionProvider implementation.
  • Provide appropriate OperationImplementation classes.
  • Add the new classes to the Project Lookup.
  • Make the Actions visible on the Project Node.
  • Run the application and verify the Actions work as you'd like.

Here we go:

  1. Create an ActionProvider. Here you specify the Actions that should be supported, the conditions under which they should be enabled, and what should happen when they're invoked, using lots of default code that lets you reuse the functionality provided by the NetBeans Project API:
    class CustomerActionProvider implements ActionProvider {
        @Override
        public String[] getSupportedActions() {
            return new String[]{
                ActionProvider.COMMAND_RENAME,
                ActionProvider.COMMAND_MOVE,
                ActionProvider.COMMAND_COPY,
                ActionProvider.COMMAND_DELETE
            };
        }
        @Override
        public void invokeAction(String string, Lookup lkp) throws IllegalArgumentException {
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_RENAME)) {
                DefaultProjectOperations.performDefaultRenameOperation(
                        CustomerProject.this,
                        "");
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_MOVE)) {
                DefaultProjectOperations.performDefaultMoveOperation(
                        CustomerProject.this);
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_COPY)) {
                DefaultProjectOperations.performDefaultCopyOperation(
                        CustomerProject.this);
            }
            if (string.equalsIgnoreCase(ActionProvider.COMMAND_DELETE)) {
                DefaultProjectOperations.performDefaultDeleteOperation(
                        CustomerProject.this);
            }
        }
        @Override
        public boolean isActionEnabled(String command, Lookup lookup) throws IllegalArgumentException {
            if ((command.equals(ActionProvider.COMMAND_RENAME))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_MOVE))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_COPY))) {
                return true;
            } else if ((command.equals(ActionProvider.COMMAND_DELETE))) {
                return true;
            }
            return false;
        }
    }

    Importantly, to round off this step, add "new CustomerActionProvider()" to the "getLookup" method of the project. If you were to run the application right now, all the Actions we're interested in would be enabled (if they are visible, as described in step 4 below) but when you invoke any of them you'd get an error message because each of the DefaultProjectOperations above looks in the Lookup of the Project for the presence of an implementation of a class for handling the operation. That's what we're going to do in the next step.

  2. Provide Implementations of Project Operations. For each of our operations, the NetBeans Project API lets you implement classes to handle the operation. The dialogs for interacting with the project are provided by the NetBeans project system, but what happens with the folders and files during the operation can be influenced via the operations. Below are the simplest possible implementations, i.e., here we assume we want nothing special to happen. Each of the below needs to be in the Lookup of the Project in order for the operation invocation to succeed.
    private final class CustomerProjectMoveOrRenameOperation implements MoveOrRenameOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyRenaming() throws IOException {
        }
        @Override
        public void notifyRenamed(String nueName) throws IOException {
        }
        @Override
        public void notifyMoving() throws IOException {
        }
        @Override
        public void notifyMoved(Project original, File originalPath, String nueName) throws IOException {
        }
    }
    
    private final class CustomerProjectCopyOperation implements CopyOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyCopying() throws IOException {
        }
        @Override
        public void notifyCopied(Project prjct, File file, String string) throws IOException {
        }
    }
    
    private final class CustomerProjectDeleteOperation implements DeleteOperationImplementation {
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public List<FileObject> getDataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyDeleting() throws IOException {
        }
        @Override
        public void notifyDeleted() throws IOException {
        }
    }

    Also make sure to put the above methods into the Project Lookup.

  3. Check the Lookup of the Project. The "getLookup()" method of the project should now include the classes you created above, as shown in bold below:
    @Override
    public Lookup getLookup() {
        if (lkp == null) {
            lkp = Lookups.fixed(new Object[]{
                this,
                new Info(),
                new CustomerProjectLogicalView(this),
                new CustomerCustomizerProvider(this),
                new CustomerActionProvider(),
                new CustomerProjectMoveOrRenameOperation(),
                new CustomerProjectCopyOperation(),
                new CustomerProjectDeleteOperation(),
                new ReportsSubprojectProvider(this),
            });
        }
        return lkp;
    }
  4. Make Actions Visible on the Project Node. The NetBeans Project API gives you a number of CommonProjectActions, including for the actions we're dealing with. Make sure the items in bold below are in the "getActions" method of the project node:
    @Override
    public Action[] getActions(boolean arg0) {
        return new Action[]{
            CommonProjectActions.newFileAction(),
            CommonProjectActions.copyProjectAction(),
            CommonProjectActions.moveProjectAction(),
            CommonProjectActions.renameProjectAction(),
            CommonProjectActions.deleteProjectAction(),
            CommonProjectActions.customizeProjectAction(),
            CommonProjectActions.closeProjectAction()
        };
    }
  5. Run the Application. When you run the application, you should see this:

Let's now try out the various actions:

  • Copy. When you invoke the Copy action, you'll see the dialog below. Provide a new project name and location and then the copy action is performed when the Copy button is clicked below:

    The message you see above, in red, might not be relevant to your project type. When you right-click the application and choose Branding, you can find the string in the Resource Bundles tab, as shown below:

    However, note that the message will be shown in red, no matter what the text is, hence you can really only put something like a warning message there. If you have no text at all, it will also look odd.

    If the project has subprojects, the copy operation will not automatically copy the subprojects. Take a look here and here for similar more complex scenarios.

  • Move. When you invoke the Move action, the dialog below is shown:

  • Rename. The Rename Project dialog below is shown when you invoke the Rename action:

    I tried it and both the display name and the folder on disk are changed.

  • Delete. When you invoke the Delete action, you'll see this dialog:

    The checkbox is not checkable, in the default scenario, and when the dialog above is confirmed, the project is simply closed, i.e., the node hierarchy is removed from the application.

    However, if you truly want to let the user delete the project on disk, pass the Project to the DeleteOperationImplementation and then add the children of the Project you want to delete to the getDataFiles method:

    private final class CustomerProjectDeleteOperation implements DeleteOperationImplementation {
        private final CustomerProject project;
        private CustomerProjectDeleteOperation(CustomerProject project) {
            this.project = project;
        }
        @Override
        public List<FileObject> getDataFiles() {
           List<FileObject> files = new ArrayList<FileObject>();
           FileObject[] projectChildren = project.getProjectDirectory().getChildren();
           for (FileObject fileObject : projectChildren) {
              addFile(project.getProjectDirectory(), fileObject.getNameExt(), files);
           }
           return files;
        }
        private void addFile(FileObject projectDirectory, String fileName, List<FileObject> result) {
           FileObject file = projectDirectory.getFileObject(fileName);
           if (file != null) {
              result.add(file);
           }
        }
        @Override
        public List<FileObject> getMetadataFiles() {
            return new ArrayList<FileObject>();
        }
        @Override
        public void notifyDeleting() throws IOException {
        }
        @Override
        public void notifyDeleted() throws IOException {
        }
    }

    Now the user will be able to check the checkbox, causing the method above to be called in the DeleteOperationImplementation:

Hope this answers some questions or at least gets the discussion started. Before asking questions about this topic, please take the steps above and only then attempt to apply them to your own scenario.

Useful implementations to look at:

http://kickjava.com/src/org/netbeans/modules/j2ee/clientproject/AppClientProjectOperations.java.htm

https://kenai.com/projects/nbandroid/sources/mercurial/content/project/src/org/netbeans/modules/android/project/AndroidProjectOperations.java

Comments:

Hi Geertjan,

thanks for your great post!

Have you tried only changing the project name without changing the project folder name? I tried it here with the CustomerProject and this has no effect. I think that's the case because in the CustomerProject implementation described in the tutorial, the ProjectInformation implementation gets it's name and display name directly from the project folder name. Am I right, that to fully support this, one has to implement some kind of meta file to store the project display name?

Best regards,
Patrick

Posted by Patrick Nowak on June 24, 2013 at 11:49 PM PDT #

Read here and implement your own Rename as shown: http://hg.netbeans.org/core-main/file/9f93bb822774/maven/src/org/netbeans/modules/maven/ActionProviderImpl.java#l224

Posted by Geertjan on June 25, 2013 at 12:24 AM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Geertjan Wielenga (@geertjanw) is a Principal Product Manager in the Oracle Developer Tools group living & working in Amsterdam. He is a Java technology enthusiast, evangelist, trainer, speaker, and writer. He blogs here daily.

The focus of this blog is mostly on NetBeans (a development tool primarily for Java programmers), with an occasional reference to NetBeans, and sometimes diverging to topics relating to NetBeans. And then there are days when NetBeans is mentioned, just for a change.

Search

Archives
« April 2014
SunMonTueWedThuFriSat
  
12
13
14
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today