Over a million developers have joined DZone.

Determining Project Folders For A Project Suite

· Java Zone

Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code! Brought to you in partnership with ZeroTurnaround.

The purpose of this article is to elaborate on some of my work with Geertjan Wielenga's excellent Dead Code Detector module for NetBeans.

You can read about it here: https://blogs.oracle.com/geertjan/entry/dead_code_detection_for_all.

The plugin is based on the Dead Code Detector library by Emeric Vernat. This library can be found here: https://java.net/projects/dcd/pages/Home.

As I was nearing the release of a NetBeans Platform application I've been working on, I realized my modules were probably full of code that had been superseded, deprecated, or just plain not used. Answering my query on the NetBeans Forum, not only did Geertjan write blog entries about the library, he also authored a plugin for it.

The Problem

The source for Geertjan's plugin is readily available here: https://java.net/projects/nb-api-samples/sources/api-samples/show/versions/7.3/misc/NB-DCD The plugin works by collecting the project folders of all the selected projects and adding them to the directories to be scanned by the Dead Code Detector:

public final class DetectDeadCodeAction implements ActionListener {
    private final List context;
    public DetectDeadCodeAction(List<Project> context) {
        this.context = context;
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        List directories = new ArrayList<File>();
        for (Project project : context) {
            directories.add(FileUtil.toFile(project.getProjectDirectory()));
        }
        // Call the Dead Code Detector with the list of directories.
    }
}

In working with a NetBeans platform project, I'm mainly interested in two kinds of projects; modules and suites. The above approach works in most cases where all the modules for a suite reside under the suite's directory:

  • /suiteA/moduleA
  • /suiteA/moduleB
  • /suiteA/moduleC

By adding the /suiteA directory to the directories to be scanned, the directories for modules A, B, and C are also scanned.

But what happens when some of your modules reside in a different directory?

  • /suiteA/moduleX
  • /some/other/folder/moduleY
  • /yet/another/folder/moduleZ

Simply adding /suiteA will pick up module X, but modules Y and Z won't be scanned. NetBeans project suites can include modules that reside elsewhere, so the action has to be modified to do a few things. First, we need to determine if a project is a suite. If it is a suite, we need to find all the modules for the suite and add their directories to be scanned.

Is It A Suite?

There are probably other ways to do this, but the solution I ended up implemented looks at the project.xml file. You can generally assume that a project.xml is valid and well-formed if a user is able to load and select the project in the IDE.

Here's what a project.xml file looks like:

<project xmlns="http://www.netbeans.org/ns/project/1">
    <type>org.netbeans.modules.apisupport.project.suite</type>
    <configuration>
        <data xmlns="http://www.netbeans.org/ns/nb-module-suite-project/1">
            <name>DISPLAY NAME OF SUITE</name>
        </data>
    </configuration>
</project>

So if the project type is "org.netbeans.module.apisupport.project.suite", we've identified a project suite.

public static final String SUITE = "<type>org.netbeans.modules.apisupport.project.suite</type>";

public boolean isProjectSuite(final Project project) {
    boolean returnValue = false;
    final FileObject projectDir = project.getProjectDirectory();
    final FileObject projectXml = projectDir.getFileObject(
            "nbproject/project.xml");
    if (projectXml != null) {
        try {
            final InputStream inStream = projectXml.getInputStream();
            final String xmlData = IOUtils.toString(inStream, "UTF-8");
            IOUtils.closeQuietly(inStream);
            returnValue = xmlData.contains(SUITE);
        } catch (IOException ioEx) {
            Exceptions.printStackTrace(ioEx);
        }
    }
    return returnValue;
}

The code simply makes sure that project.xml exists, reads the entire file into a String (with the help of the Apache Commons IO Bundle) and checks that the type is indeed a suite. Alternatively, we could load it using DOM libraries and look for the <type> element via XPath or something, but that seemed like overkill in this case.

Module Directories For A Suite

So once a project is identified as a suite, how do we determine where each of the modules live? The answer lies in the project.properties file:

modules=\
    ${project.org.netbeans.moduleA}:\
    ${project.org.netbeans.moduleB}:\
    ${project.org.netbeans.moduleC}

project.org.netbeans.moduleA=moduleA
project.org.netbeans.moduleB=moduleB
project.org.netbeans.moduleC=../../other/folder/moduleC

So each of the property keys that start with "project" have values that represent a relative path to that project's directory. Perfect!

private Set<File> getProjectsForSuite(final Project project) {
    final Set<File> projectDirs = new HashSet<File>();
    final FileObject suiteDir = project.getProjectDirectory();
    final FileObject suiteProps = suiteDir.getFileObject(
            "nbproject/project.properties");
    if (suiteProps != null) {
        try {
            final InputStream inStream = suiteProps.getInputStream();
            final Properties props = new Properties();
            props.load(inStream);
            IOUtils.closeQuietly(inStream);           
            for (String key : props.stringPropertyNames()) {
                if (key.startsWith("project.")) {
                    final String relPath = props.getProperty(key);
                    final FileObject projectDir =
                            suiteDir.getFileObject(relPath);
                    if (projectDir != null) {
                        projectDirs.add(FileUtil.toFile(projectDir));
                    } else {
                        LOG.log(Level.INFO, "Null path for: {0}", relPath);
                    }
                }
            }
        } catch (IOException ioEx) {
            Exceptions.printStackTrace(ioEx);
        }
    }       
    return projectDirs;
}

In the code above, we're loading the properties file into a Properties object. We iterate through all the keys and look for those that start with "project." We take the suite directory and the value of the "project." keys. FileObject has built-in support for relative paths, so deriving a new FileObject from the suite directory and the key value is trivial. If the directory indeed exists, we add it to the Set of directories returned by the method.

Putting It All Together

Here's a modified version of Geertjan's actionPerformed method:

public final class DetectDeadCodeAction implements ActionListener {
    private final List<Project> context;
    public DetectDeadCodeAction(List<Project> context) {
        this.context = context;
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        List<File> directories = new ArrayList<File>();
        for (Project project : context) {
            if (isProjectSuite(project)) {
                directories.addAll(getProjectsForSuite(project));
            } else {
                directories.add(FileUtil.toFile(project.getProjectDirectory()));
            }
        }
        // Call the Dead Code Detector with the list of directories.
    }
}

Going Further

The Dead Code Detector does not work against source files. It works against compiled code; JARs and class files. It's conceivable that your build/target directory may not exist in the same directory hierarchy as your source directory. There may be a use case for specifying the build/target directory for a project. Both Ant and Maven can specify the location of their source and build/target directories, so there may be a case to cater to those project types as well. As it stands, Ant and Maven projects generally have both source and build/target directories located in a sub directory of the project, so the action should work as-is for most use cases.

The Dead Code Detector is an exciting project that works well (and fast) to find potentially dead code across your projects. Congratulations to Emeric for his work on this project and congratulations to Geertjan for rapidly developing a plugin that integrates nicely with the IDE!

The Java Zone is brought to you in partnership with ZeroTurnaround. Check out this 8-step guide to see how you can increase your productivity by skipping slow application redeploys and by implementing application profiling, as you code!

Topics:

The best of DZone straight to your inbox.

SEE AN EXAMPLE
Please provide a valid email address.

Thanks for subscribing!

Awesome! Check your inbox to verify your email so you can start receiving the latest in tech news and resources.
Subscribe

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}