Annotation Processor for Superclass Sensitive Actions
- by Geertjan
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>