How to Save a Project

The question of the day comes from Tom Wheeler, as well as others in the past, in particular Klaus Martinschitz (here): "How do I save a project?" The same question can be asked in other ways too: "What does 'ProjectState' do?" and "How does ProjectFactory.saveProject" work?

Note: If the above paragraph is meaningless to you, then you should probably forget about reading the rest of this blog entry, lest you get very confused.

The two classes that are important in this context are:

When defining a ProjectFactory, you need to do something with the "saveProject" method. That method is useful if you have settings that you'd like to save, i.e., settings that are specific to your project.

Here's an example. Let's say that whenever the user copies a project, a properties file belonging to the project should have a special key updated with a timestamp. I.e., each project contains a project.properties file with a key named "copied". The value of that key is set to a timestamp whenever the copy action completes. (Maybe useful for the user to see when the project was copied.)

The code that follows is derived from the famous PovRay tutorial (which should be released sometime this millenium as a full-blown NetBeans Platform course, so until then it is incomplete, please don't refer to it too much, since it is, after all, incomplete).

The steps below build on top of NetBeans Project Type Module Tutorial.

  1. We need to make sure that the project.properties file is in the Lookup of the project. Add these methods to the class that implements org.netbeans.api.project.Project:
    private Properties loadProperties() {
        FileObject fob = projectDir.getFileObject(DemoProjectFactory.PROJECT_DIR + "/project.properties");
        Properties properties = new NotifyProperties(state);
        if (fob != null) {
            try {
                properties.load(fob.getInputStream());
            } catch (Exception e) {
                Exceptions.printStackTrace(e);
            }
        }
        return properties;
    }
    
    private static class NotifyProperties extends Properties {
        private final ProjectState state;
        NotifyProperties(ProjectState state) {
            this.state = state;
        }
        @Override
        public Object put(Object key, Object val) {
            Object result = super.put(key, val);
            if (((result == null) != (val == null)) || (result != null && val != null && !val.equals(result))) {
                state.markModified();
            }
            return result;
        }
    }

    Whenever "put" is called on our project.properties, the project state will be marked as modified.

  2. Add loadProperties() to the getLookup() of the Project class, i.e., the same class where you defined the above. Also declare an instance of ProjectState and add that to the Lookup of the Project class. That's the state that is passed into the NotifyProperties class that you see above.

  3. Next, somewhere in your code (in this case, in the method that will be invoked when the copy operation completes), you need to call that "put" on the project.properties:
    @Override
    public void notifyCopied(Project arg0, File arg1, String arg2) throws IOException {
        Properties props = (Properties) project.getLookup().lookup(Properties.class);
        Calendar cal = Calendar.getInstance();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        props.put("copied", sdf.format(cal.getTime()));
    }

    Here you can see that the properties object is retrieved from the Lookup and that the "put" is then called on it. We assign the current time to the key that we know will be in the project.properties file.

  4. Because the "put" is called on the properties file, the project state is marked as modified (read the code in step 1). Now read the ProjectState.markModified Javadoc and notice that, as a result of ProjectState.markModified, the saveProject is triggered, back in the ProjectFactory.

  5. And now you need to do something when the saveProject is triggered! Put a message in there and you will see that it is called when "put" is called on the properties file. Then check that the project exists, check that the file exists (if not, create it), retrieve the Properties file from the project's Lookup, and then write its content to the file on disk:
    @Override
    public void saveProject(final Project project) throws IOException, ClassCastException {
        
        //First check that the project folder is there,
        //otherwise the project cannot be saved:
        FileObject projectRoot = project.getProjectDirectory();
        if (projectRoot.getFileObject(PROJECT_DIR) == null) {
            throw new IOException("Project dir " + projectRoot.getPath() +
                    " deleted," +
                    " cannot save project");
        }
        
        //Force the creation of the project folder if it was deleted:
        ((DemoProject) project).getTextFolder(true);
    
        //Find the Properties file, creating it if necessary:
        String propsPath = PROJECT_DIR + "/project.properties";
        FileObject propertiesFile = projectRoot.getFileObject(propsPath);
        if (propertiesFile == null) {
            //Recreate the Properties file, if needed:
            propertiesFile = projectRoot.createData(propsPath);
        }
        
        //Get the properties file from the project's Lookup,
        //where the properties file now has a changed key:
        Properties properties = (Properties) project.getLookup().lookup(Properties.class);
        
        //Get the file from the FileObject obtained above:
        File f = FileUtil.toFile(propertiesFile);
        
        //Write the project Lookup's properties list to the file defined above:
        properties.store(new FileOutputStream(f), "Description of the property list");
    
    }

It's as easy as that. No Save button will be enabled, in fact, nothing will be enabled visually, but internally things will get saved, "things" being whatever it is that you've set to be saved in ProjectFactory.saveProject. The "things" are supposed to be changes to your project's metadata so, if your project doesn't have metadata (e.g., ancillary files such as project.properties or XML files), or the metadata doesn't need to change, there's no need to implement ProjectFactory.saveProject at all.

Now read the Javadoc for the ProjectManager class because there are several methods that relate to modification of files there.

Comments:

Thank you so much, Geertjan!

To be clear, my question was not "How do I save a project?" but rather 'What does it mean to "save" a project?' I could work out from the Javadoc \*how\* to save a project, but not what my implementation should do. The documentation for ProjectFactory.saveProject only says "Save a project to disk" without any explanation of what that means.

I implemented a custom project type a few years ago and I followed Tim's original (and excellent) POV Ray tutorial then. I remembering not really understanding ProjectFactory.saveProject then, though there were lots of other things I didn't understand back then either.

Now that I'm creating a new project type again, this is the only part of the API I didn't understand, so I really appreciate your explanation.

Posted by Tom Wheeler on September 08, 2009 at 06:56 AM PDT #

You comment sparked a bit of discussion about the way project saving is handled in the tutorial (which will be updated), and so forth.

Basically the gist is this:
- You can use ProjectState and ProjectManager.saveProject() at your discretion - you can also ignore them if you have your own mechanism of saving the project
- Bug in the tutorial: ProjectState.markModified() \*may\* cause the project to be saved on shutdown, but does not queue any call to ProjectManager.saveProject(). So, you can use ProjectManager.saveProject() as a way to trigger a call to MyProjectFactory.saveProject(), or you can just ignore it if you implement your own saving of project metadata.

"Saving" basically means sync'ing the in-memory metadata with the on-disk representation of it. In the POV-Ray tutorial, it will be updated so that NotifyProperties implements FileSystem.AtomicAction, and on a call put(), queues a Runnable on a background thread which will call ProjectManager.saveProject(); PovProjectFactory will call NotifyProperties.save(), which in turn will call projectPropertiesFile.getFileSystem().runAtomicAction(this) to actually do the save (NotifyProperties will also listen on the file, so the AtomicAction allows it to reload if !FileEvent.firedFrom(this) in the case the user edited the file manually and cached data should be discarded). So, for the tutorial it probably could be handled (slightly) more simply by not touching ProjectState and ProjectManager.saveProject() at all, but then we wouldn't be teaching those concepts.

Anyway, basically, you can use them or not at your discretion.

-Tim

Posted by Tim Boudreau on September 10, 2009 at 11:58 AM PDT #

Hi Geertjan,

Thanks for your blog entry! I am trying to save my project properties the same way, but it just won't work. I'm probably doing something wrong, so you might give me a hint...

The problem is that my project will only open if my properties file is empty. Javadoc tells me that the markModified() method may not be called during ProjectFactory.loadProject, but if I load a project with an unempty file, put(key,value) in NotifyProperties will trigger that markModified() method, returning an "Assertion Error".

Do you know what I'm missing here?

Thanks

Posted by Philippe Kruschitz on September 30, 2009 at 05:54 PM 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
23
24
25
26
27
28
29
30
   
       
Today