org.netbeans.spi.java.queries.AccessibilityQueryImplementation

Let's say you want to create a plugin for NetBeans IDE that needs to do something special to Java classes in certain packages.

Here's an example.

Above is your application. As part of the build process, you want to let the user handle one of the packages, such as "org.demo.algorithms", differently to the other packages. You'd like to let the user right-click on the package and choose "Export". When they've done that, the package should be registered somewhere for special post-processing, while the user should be able to see a special icon indicating that the package has been exported, with the possibility of reverting the export.

How to do this?

The starting point is to read org.netbeans.spi.java.queries.AccessibilityQueryImplementation. As a simple demo, let's implement it as follows, which requires dependencies on File System API, Java Support APIs, Lookup API, and Project API:

import org.netbeans.spi.java.queries.AccessibilityQueryImplementation;
import org.netbeans.spi.project.ProjectServiceProvider;
import org.openide.filesystems.FileObject;
@ProjectServiceProvider(
        projectType = "org-netbeans-modules-java-j2seproject",
        service = AccessibilityQueryImplementation.class)
public class DemoAccessibilityImpl implements AccessibilityQueryImplementation {
    @Override
    public Boolean isPubliclyAccessible(FileObject pkg) {
        return pkg.getName().equals("algorithms");
    }
}

Now, when you install the above, you see this in our sample Java SE application:


Look at the unlock/lock icons that  have been merged into the package icons, based on the condition we set in our AccessibilityQueryImplementation.

Let's now make the unlock/lock icon appear based on whether a property "enableForJavaCard" has been set in the project.properties file of the project and, if so, whether the package has been registered in that property.

@ProjectServiceProvider(
        projectType = "org-netbeans-modules-java-j2seproject",
        service = AccessibilityQueryImplementation.class)
public class DemoAccessibilityImpl implements AccessibilityQueryImplementation {
    private final Project project;
    public DemoAccessibilityImpl(Project project) {
        this.project = project;
    }
    @Override
    public Boolean isPubliclyAccessible(FileObject pkg) {
        String packageName = pkg.getName();
        FileObject projectprop =
           project.getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH);
        EditableProperties ep;
        try {
            ep = loadProperties(projectprop);
            if (ep.containsKey("enableforJavaCard")) {
                return ep.get("enableforJavaCard").contains(packageName);
            }
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
        return false;
    }
    private static EditableProperties loadProperties(FileObject propsFO) throws IOException {
        InputStream propsIS = propsFO.getInputStream();
        EditableProperties props = new EditableProperties(true);
        try {
            props.load(propsIS);
        } finally {
            propsIS.close();
        }
        return props;
    }
}

Next, we need to let the user switch between unlock/lock icons, i.e., export/unexport icons. That's done via an Action registered in "Projects/package/Actions".

There are at least two ugly solutions that are part of the code below; firstly, how the package name is constructed, secondly, how the icons are refreshed (via introspection).

@ActionID(
        category = "Edit",
        id = "org.netbeans.access.PublishPackageAction"
)
@ActionRegistration(
        lazy = false,
        displayName = "#CTL_PublishPackageAction"
)
@ActionReference(
        path = "Projects/package/Actions",
        position = 0)
@Messages("CTL_PublishPackageAction=Publish")
public final class PublishPackageAction extends AbstractAction implements LookupListener {
    private DataObject context;
    private Lookup.Result<DataObject> dataObjects;
    private boolean publishable = true;
    public PublishPackageAction() {
        dataObjects = Utilities.actionsGlobalContext().lookupResult(DataObject.class);
        dataObjects.addLookupListener(
                WeakListeners.create(LookupListener.class, this, dataObjects));
    }
    @Override
    public void actionPerformed(ActionEvent ev) {
        Project project = FileOwnerQuery.getOwner(context.getPrimaryFile());
        FileObject projectprop =
            project.getProjectDirectory().getFileObject(AntProjectHelper.PROJECT_PROPERTIES_PATH);
        EditableProperties ep;
        try {
            ep = loadProperties(projectprop);
            FileObject pkg = context.getPrimaryFile();
            String path = pkg.getPath();
            String packageName =
                   path.substring(path.lastIndexOf("src/")).replace("src/", "").replace("/", ".");
            if (ep.containsKey("enableforJavaCard")) {
                if (publishable &&
                  !ep.get("enableforJavaCard").contains(packageName)) {
                    ep.setProperty("enableforJavaCard",
                       ep.get("enableforJavaCard") + packageName + ",");
                } else if (!publishable &&
                   ep.get("enableforJavaCard").contains(packageName)) {
                    ep.setProperty("enableforJavaCard",
                       ep.get("enableforJavaCard").replace(packageName + ",", ""));
                }
            } else if (publishable) {
                ep.setProperty("enableforJavaCard", packageName + ",");
            }
            storeProperties(projectprop, ep);
            Node node = context.getNodeDelegate();
            uglyNodeIconRefresh(node);
        } catch (IOException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
    private void uglyNodeIconRefresh(Node found) {
        try {
            Method m1 = Node.class.getDeclaredMethod("fireIconChange");
            m1.setAccessible(true);
            Method m2 = Node.class.getDeclaredMethod("fireOpenedIconChange");
            m2.setAccessible(true);
            m2.invoke(found);
            m1.invoke(found);
        } catch (NoSuchMethodException ex) {
            Exceptions.printStackTrace(ex);
        } catch (SecurityException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalAccessException ex) {
            Exceptions.printStackTrace(ex);
        } catch (IllegalArgumentException ex) {
            Exceptions.printStackTrace(ex);
        } catch (InvocationTargetException ex) {
            Exceptions.printStackTrace(ex);
        }
    }
    private static EditableProperties loadProperties(FileObject propsFO) throws IOException {
        InputStream propsIS = propsFO.getInputStream();
        EditableProperties props = new EditableProperties(true);
        try {
            props.load(propsIS);
        } finally {
            propsIS.close();
        }
        return props;
    }
    public static void storeProperties(FileObject propsFO, EditableProperties props) throws IOException {
        FileLock lock = propsFO.lock();
        try {
            OutputStream os = propsFO.getOutputStream(lock);
            try {
                props.store(os);
            } finally {
                os.close();
            }
        } finally {
            lock.releaseLock();
        }
    }
    @Override
    public void resultChanged(LookupEvent ev) {
        Collection<? extends DataObject> dos = dataObjects.allInstances();
        if (dos.size() == 1) {
            DataObject currentDataObject = dos.iterator().next();
            FileObject selectedPkgIter = currentDataObject.getPrimaryFile();
            if (selectedPkgIter.isFolder()) {
                Boolean isPublic = AccessibilityQuery.isPubliclyAccessible(selectedPkgIter);
                if (isPublic) {
                    putValue("popupText", "Unpublish " + currentDataObject.getPrimaryFile().getName());
                    publishable = false;
                } else {
                    putValue("popupText", "Publish " + currentDataObject.getPrimaryFile().getName());
                    publishable = true;
                }
                context = currentDataObject;
            }
        }
    }
}

And now you have an Action on packages for publishing/unpublishing a package so that it is added/removed from a property in the project.properties file.

Below, the user right-clicks on a package named "org.demo.actions", which has not been registered in the "enableforJavaCard" property, hence the "Publish" text is shown, together with the package name:

Here, the "enableforJavaCard" property includes the "org.demo.ui" package, hence the "Unpublish" text is shown, together with the package name:

What's the point of all this? Well, it's now possible to register packages for post-processing for Java Card application development. This provides a much simpler alternative to jCardSim, via the Femto project which is what Eduard Karel de Jong is working on. With the above solution, he now has a hook to separating packages for Java Card processing.

YouTube soundless demo showing the workflow and result:

If you can't see the above for some reason, go here: https://www.youtube.com/watch?v=DHSVxJA9abI

Comments:

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 2015
SunMonTueWedThuFriSat
   
4
5
11
12
25
26
27
28
29
30
  
       
Today