Annotation Processor for Superclass Sensitive Actions

Someone creating superclass sensitive actions should need to specify only the following things:

  • The condition under which the popup menu item should be available, i.e., the condition under which the action is relevant. And, for superclass sensitive actions, the condition is the name of a superclass. I.e., if I'm creating an action that should only be invokable if the class implements "org.openide.windows.TopComponent",  then that fully qualified name is the condition.
  • The position in the list of Java class popup menus where the new menu item should be found, relative to the existing menu items.
  • The display name.
  • The path to the action folder where the new action is registered in the Central Registry.
  • The code that should be executed when the action is invoked.

In other words, the code for the enablement (which, in this case, means the visibility of the popup menu item when you right-click on the Java class) should be handled generically, under the hood, and not every time all over again in each action that needs this special kind of enablement.

So, here's the usage of my newly created @SuperclassBasedActionAnnotation, where you should note that the DataObject must be in the Lookup, since the action will only be available to be invoked when you right-click on a Java source file (i.e., text/x-java) in an explorer view:

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import org.netbeans.sbas.annotations.SuperclassBasedActionAnnotation;
import org.openide.awt.StatusDisplayer;
import org.openide.loaders.DataObject;
import org.openide.util.NbBundle;
import org.openide.util.Utilities;

@SuperclassBasedActionAnnotation(
        position=30, 
        displayName="#CTL_BrandTopComponentAction",
        path="File",
        type="org.openide.windows.TopComponent")
@NbBundle.Messages("CTL_BrandTopComponentAction=Brand")
public class BrandTopComponentAction implements ActionListener {
    
    private final DataObject context;

    public BrandTopComponentAction() {
        context = Utilities.actionsGlobalContext().lookup(DataObject.class);
    }

    @Override
    public void actionPerformed(ActionEvent ev) {
        String message = context.getPrimaryFile().getPath();
        StatusDisplayer.getDefault().setStatusText(message);
    }
    
}

That implies I've created (in a separate module to where it is used) a new annotation. Here's the definition:

package org.netbeans.sbas.annotations;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface SuperclassBasedActionAnnotation {

    String type();
    
    String path();

    int position();
    
    String displayName();
    
}

And here's the processor:

package org.netbeans.sbas.annotations;

import java.util.Set;
import javax.annotation.processing.Processor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import org.openide.filesystems.annotations.LayerBuilder.File;
import org.openide.filesystems.annotations.LayerGeneratingProcessor;
import org.openide.filesystems.annotations.LayerGenerationException;
import org.openide.util.lookup.ServiceProvider;

@ServiceProvider(service = Processor.class)
@SupportedAnnotationTypes("org.netbeans.sbas.annotations.SuperclassBasedActionAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_6)    
public class SuperclassBasedActionProcessor extends LayerGeneratingProcessor {

    @Override
    protected boolean handleProcess(Set annotations, RoundEnvironment roundEnv) throws LayerGenerationException {
         Elements elements = processingEnv.getElementUtils();
        for (Element e : roundEnv.getElementsAnnotatedWith(SuperclassBasedActionAnnotation.class)) {
            TypeElement clazz = (TypeElement) e;
            SuperclassBasedActionAnnotation mpm = clazz.getAnnotation(SuperclassBasedActionAnnotation.class);
            String teName = elements.getBinaryName(clazz).toString();
            String originalFile = "Actions/" + mpm.path() + "/" + teName.replace('.', '-') + ".instance";
            File actionFile = layer(e).file(
                    originalFile).
                    bundlevalue("displayName", mpm.displayName()).
                    methodvalue("instanceCreate", "org.netbeans.sbas.annotations.SuperclassSensitiveAction", "create").
                    stringvalue("type", mpm.type()).
                    newvalue("delegate", teName);
            actionFile.write();
            File javaPopupFile = layer(e).file(
                    "Loaders/text/x-java/Actions/" + teName.replace('.', '-') + ".shadow").
                    stringvalue("originalFile", originalFile).
                    intvalue("position", mpm.position());
            javaPopupFile.write();
        }
        return true;
    }
    
}

The "SuperclassSensitiveAction" referred to in the code above is unchanged from how I had it in yesterday's blog entry.

When I build the module containing two action listeners that use my new annotation, the generated layer file looks as follows, which is identical to the layer file entries I hard coded yesterday:

<folder name="Actions">
    <folder name="File">
        <file name="org-netbeans-sbas-impl-ActionListenerSensitiveAction.instance">
            <attr name="displayName" stringvalue="Process Action Listener"/>
            <attr methodvalue="org.netbeans.sbas.annotations.SuperclassSensitiveAction.create" name="instanceCreate"/>
            <attr name="type" stringvalue="java.awt.event.ActionListener"/>
            <attr name="delegate" newvalue="org.netbeans.sbas.impl.ActionListenerSensitiveAction"/>
        </file>
        <file name="org-netbeans-sbas-impl-BrandTopComponentAction.instance">
            <attr bundlevalue="org.netbeans.sbas.impl.Bundle#CTL_BrandTopComponentAction" name="displayName"/>
            <attr methodvalue="org.netbeans.sbas.annotations.SuperclassSensitiveAction.create" name="instanceCreate"/>
            <attr name="type" stringvalue="org.openide.windows.TopComponent"/>
            <attr name="delegate" newvalue="org.netbeans.sbas.impl.BrandTopComponentAction"/>
        </file>
    </folder>
</folder>
<folder name="Loaders">
    <folder name="text">
        <folder name="x-java">
            <folder name="Actions">
                <file name="org-netbeans-sbas-impl-ActionListenerSensitiveAction.shadow">
                    <attr name="originalFile" stringvalue="Actions/File/org-netbeans-sbas-impl-ActionListenerSensitiveAction.instance"/>
                    <attr intvalue="10" name="position"/>
                </file>
                <file name="org-netbeans-sbas-impl-BrandTopComponentAction.shadow">
                    <attr name="originalFile" stringvalue="Actions/File/org-netbeans-sbas-impl-BrandTopComponentAction.instance"/>
                    <attr intvalue="30" name="position"/>
                </file>
            </folder>
        </folder>
    </folder>
</folder>
Comments:

Hello,
I followed you example but I can't get ANY custom processor running.. Trying to put some "debug strings" in the code but nothing... I just see:

Note: Attempting to workaround javac bug #6512707
Note: eu.margiel.SuperclassBasedActionProcessor to be registered as a javax.annotation.processing.Processor
warning: No processor claimed any of these annotations: [javax.annotation.processing.SupportedSourceVersion, javax.annotation.processing.SupportedAnnotationTypes, eu.margiel.SuperclassBasedActionAnnotation]

is there any precondition that I am missing or anything like this?

Posted by Michal Margiel on July 06, 2012 at 05:38 AM PDT #

The annotation needs to be defined in one module. In another module, use the annotation. Don't define and use within the same module.

Posted by Geertjan on July 06, 2012 at 06:27 AM PDT #

now it worked. thanks, I think that this info should be somwhere inside this post (I couldn't find it anywhere).

anyway - I have one more question, I wanna use my custom LayerGeneratingProcessor to generate Top Menu (so layer.xml) accordingly to User roles - which can change while using our app on some actions.

Is it possible to regenerate layer.xml during runtime? Now we are doing that by loading multiple layer files - but I hate this solution since it is very messy. I would like to have kind of builder where in one place I would like to define which menus are available to which roles.
Something like...

@MenuBuilder
public class MyMenuBuilder{
public Menu create(){
return new Menu()
.item(new MenuItem("My Item", Action1.class).forRoles(ADMIN))
.item(new MenuItem("My Item 2", Action1.class).forRoles(CONTRIBUTOR));
}

}

Posted by Michal Margiel on July 06, 2012 at 06:54 AM PDT #

Any idea how I can achieve that?

Posted by Michal Margiel on July 10, 2012 at 07:28 AM PDT #

Don't think that's possible. I'd recommend writing to dev@platform.netbeans.org with this and other questions, sorry, don't know the answer to this.

Posted by Geertjan on July 10, 2012 at 10:15 AM PDT #

I still confused about your opinion "When I build the module containing two action listeners that use my new annotation, the generated layer file looks as follows".
Can you describe what this exactly,because I have try your example but I can't get it running,this makes me wonder how it can works,since I have tried for 2 nights,

regard,
listyo
http://www.advantoday.com

Posted by listyo on July 29, 2012 at 06:38 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
24
25
26
27
28
29
30
   
       
Today