Over a million developers have joined DZone.

How to use Guarded Sections in Editors on the NetBeans Platform

· Java Zone

Discover how AppDynamics steps in to upgrade your performance game and prevent your enterprise from these top 10 Java performance problems, brought to you in partnership with AppDynamics.

I was asked to implement a non-editable section, like the ones that in Java files do not allow to edit the code by Matisse. I had not to let the user edit some variables whose syntax was like ${ABC}.

I copied the code that is used for the BeanInfo, especially "BIEditorSupport.java". Following are the steps to create a new file type and to add Guarded Sections to it. Of course, it can be customized at your own will.

I was also asked to add popup actions to the code to edit the content of the Guarded Sections, probably I will write about it in another tutorial.

Thanks to Matteo Di Giovinazzo and Paolo Repele for their code review.

Note: The complete code sample can be downloaded here.

  1. Create a new "NetBeans Platform Application". Call it "GuardedSectionSuite", any other name is fine. In order to do this, in the File menu select "New Project..." / "NetBeans Modules" / "NetBeans Platform Application". Finish the wizard and then right-click on the suite node, select "Properties", then select "Libraries". Within the "ide" cluster, check the "Editor Guarded Sections " node.

  2. Add a new module. Expand the suite node. Right-click on the "Modules" node. Select "Add New...". Call the module "GuardedSectionModule", for example. Add the new module to the suite. Click "Next". Be sure that the "Generate XML Layer" checkbox is selected. Click "Finish". Right-click on the new module node and select "Properties" and then "Libraries". Click on "Add Dependency" and then add dependencies on "General Queries API", "Editor Guarded Sections", "UI Utilities API", if you do not add this you get problem when launching the suite.

  3. Recognize a new file type. Right-click on the new module node and select "New"/"Other". Select "Module Development" and "File Type". Fill the textfield, the extension is the extension used to recognize the file and the MIME type will be the identifier for that file. Click "Next". Again, fill the fields, the icon is unimportant. Click "Finish".

  4. Write the code. Edit "ScantiDataObject.java" Replace this line:
    cookies.add((Node.Cookie)DataEditorSupport.create(this, getPrimaryEntry(), cookies));

    with this one

    cookies.add(new ScantiDesignEditorSupport(this, getCookieSet()));
    Create a new java class and call it "ScantiDesignEditorSupport". This class will take care of saving and loading the file and it will indirectly call the "GuardedSectionProvider" in these cases. Remember to change it if you have a MIME type different from "text/x-scanti". Copy this code inside "ScantiDesignEditorSupport":
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.OutputStream;
    import java.io.Reader;
    import java.io.Writer;
    import java.nio.charset.Charset;
    import javax.swing.text.BadLocationException;
    import javax.swing.text.EditorKit;
    import javax.swing.text.StyledDocument;
    import org.netbeans.api.editor.guards.GuardedSectionManager;
    import org.netbeans.api.queries.FileEncodingQuery;
    import org.netbeans.spi.editor.guards.GuardedEditorSupport;
    import org.netbeans.spi.editor.guards.GuardedSectionsFactory;
    import org.netbeans.spi.editor.guards.GuardedSectionsProvider;
    import org.openide.cookies.EditCookie;
    import org.openide.cookies.EditorCookie;
    import org.openide.cookies.OpenCookie;
    import org.openide.cookies.PrintCookie;
    import org.openide.cookies.SaveCookie;
    import org.openide.filesystems.FileLock;
    import org.openide.filesystems.FileObject;
    import org.openide.loaders.DataObject;
    import org.openide.loaders.MultiDataObject;
    import org.openide.nodes.CookieSet;
    import org.openide.text.DataEditorSupport;
    import org.openide.windows.CloneableOpenSupport;

    public class ScantiDesignEditorSupport extends DataEditorSupport
    implements OpenCookie, EditCookie, EditorCookie, PrintCookie, EditorCookie.Observable {

    private MyGuardedEditor guardedEditor;

    public ScantiDesignEditorSupport(DataObject obj, CookieSet cookieSet) {
    super(obj, new Environment(obj, cookieSet));
    setMIMEType("text/x-scanti");
    }

    /**
    * gets the GuardedSectionManager from a given document
    * Useful to get a list of Guarded Sections inside a document
    * @param doc the document
    * @return a GuardedSectionManager
    */
    public GuardedSectionManager getGuardedSectionManager(StyledDocument doc) {
    return GuardedSectionManager.getInstance(doc);
    }

    public GuardedSectionManager getGuardedSectionManager() {
    try {
    StyledDocument doc = openDocument();
    return GuardedSectionManager.getInstance(doc);
    } catch (IOException ex) {
    throw new IllegalStateException("cannot open document", ex); // NOI18N
    }
    }

    @Override
    protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit)
    throws IOException, BadLocationException {
    guardedEditor = null;

    GuardedSectionsProvider guardedProvider = getGuardedProvider(doc);

    if (guardedProvider != null) {
    guardedEditor.document = doc;
    Charset c = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile());
    Reader reader = guardedProvider.createGuardedReader(stream, c);
    try {
    kit.read(reader, doc, 0);
    } finally {
    reader.close();
    }
    } else {
    super.loadFromStreamToKit(doc, stream, kit);
    }
    }

    private GuardedSectionsProvider getGuardedProvider(StyledDocument doc) {
    GuardedSectionsProvider guardedProvider = null;
    if (guardedEditor == null) {
    guardedEditor = new MyGuardedEditor(doc);
    String mimeType = ((DataEditorSupport.Env) env).getMimeType();
    GuardedSectionsFactory gFactory = GuardedSectionsFactory.find(mimeType);
    if (gFactory != null) {
    guardedProvider = gFactory.create(guardedEditor);
    }
    }
    return guardedProvider;
    }

    @Override
    protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream)
    throws IOException, BadLocationException {
    GuardedSectionsProvider guardedProvider = getGuardedProvider(doc);
    if (guardedProvider != null) {
    Charset c = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile());
    Writer writer = guardedProvider.createGuardedWriter(stream, c);
    try {
    kit.write(writer, doc, 0, doc.getLength());
    } finally {
    writer.close();
    }
    } else {
    super.saveFromKitToStream(doc, kit, stream);
    }
    }

    @Override
    public void saveDocument() throws IOException {
    super.saveDocument();
    }

    @Override
    protected boolean notifyModified() {
    if (!super.notifyModified()) {
    return false;
    }
    ((Environment) this.env).addSaveCookie();
    return true;
    }

    @Override
    protected void notifyUnmodified() {
    super.notifyUnmodified();
    ((Environment) this.env).removeSaveCookie();
    }

    @Override
    protected void notifyClosed() {
    super.notifyClosed();
    }

    static class MyGuardedEditor implements GuardedEditorSupport {

    protected StyledDocument document;

    public MyGuardedEditor(StyledDocument document) {
    this.document = document;
    }

    @Override
    public StyledDocument getDocument() {
    return document;
    }
    }

    static ScantiDesignEditorSupport findEditor(DataObject dobj) {
    return dobj.getLookup().lookup(ScantiDesignEditorSupport.class);
    }

    private static final class Environment extends DataEditorSupport.Env {

    private static final long serialVersionUID = -1;
    private final transient CookieSet cookieSet;
    private transient SaveSupport saveCookie = null;

    private final class SaveSupport implements SaveCookie {

    @Override
    public void save() throws java.io.IOException {
    DataObject dobj = getDataObject();
    ((DataEditorSupport) findCloneableOpenSupport()).saveDocument();
    dobj.setModified(false);
    }
    }

    public Environment(DataObject obj, CookieSet cookieSet) {
    super(obj);
    this.cookieSet = cookieSet;
    }

    @Override
    protected FileObject getFile() {
    return this.getDataObject().getPrimaryFile();
    }

    @Override
    protected FileLock takeLock() throws java.io.IOException {
    return ((MultiDataObject) this.getDataObject()).getPrimaryEntry().takeLock();
    }

    public
    @Override
    CloneableOpenSupport findCloneableOpenSupport() {
    return findEditor(this.getDataObject());
    }

    public void addSaveCookie() {
    DataObject javaData = this.getDataObject();
    if (javaData.getCookie(SaveCookie.class) == null) {
    if (this.saveCookie == null) {
    this.saveCookie = new SaveSupport();
    }
    this.cookieSet.add(this.saveCookie);
    javaData.setModified(true);
    }
    }

    public void removeSaveCookie() {
    DataObject javaData = this.getDataObject();
    if (javaData.getCookie(SaveCookie.class) != null) {
    this.cookieSet.remove(this.saveCookie);
    javaData.setModified(false);
    }
    }
    }
    }
  5. Create a new Java class. Call it "ScantiGuardedSectionsFactory". Paste this code inside:
    import org.netbeans.spi.editor.guards.GuardedEditorSupport;
    import org.netbeans.spi.editor.guards.GuardedSectionsFactory;
    import org.netbeans.spi.editor.guards.GuardedSectionsProvider;

    public class ScantiGuardedSectionsFactory extends GuardedSectionsFactory{

    @Override
    public GuardedSectionsProvider create(GuardedEditorSupport ges) {
    return new ScantiGuardedSectionsProvider(ges);
    }

    }
  6. Finally, create the Java class that does the job. Call it "ScantiGuardedSectionsProvider". The "readSection" method reads the content of the file and extracts the sections from it. The "Result" object that is returned contains an array of chars and a list of sections. I think that the displayed text could be changed by returning a different array. The same thing can be said for the "writeSections" method. This is useful if you want to mark your section with some text that you do not want to be displayed to the user.
    import java.util.ArrayList;
    import java.util.List;
    import java.util.logging.Level;
    import java.util.logging.Logger;
    import javax.swing.text.BadLocationException;
    import org.netbeans.api.editor.guards.GuardedSection;
    import org.netbeans.api.editor.guards.SimpleSection;
    import org.netbeans.spi.editor.guards.GuardedEditorSupport;
    import org.netbeans.spi.editor.guards.support.AbstractGuardedSectionsProvider;
    import org.openide.util.Exceptions;


    public class ScantiGuardedSectionsProvider extends AbstractGuardedSectionsProvider {

    public ScantiGuardedSectionsProvider(GuardedEditorSupport editor) {
    super(editor);
    }

    @Override
    public char[] writeSections(List list, char[] chars) {
    return chars;
    }

    @Override
    public Result readSections(char[] chars) {
    List sections = new ArrayList();

    StringBuilder sb = new StringBuilder();

    int start = 0;
    int len = 0;
    for (int i = 0; i < chars.length; i++) {
    char c = chars[i];

    switch (c) {
    case '}': // I have found the closing brace
    if (len == 2) {
    try {
    Logger.getLogger(ScantiGuardedSectionsProvider.class.getName()).log(Level.INFO, "Appending Section {0}", sb.toString());
    SimpleSection section = createSimpleSection(sb.toString(), start, i);
    sections.add(section);
    } catch (BadLocationException ex) {
    Exceptions.printStackTrace(ex);
    }
    sb = new StringBuilder();

    len = 0;
    }
    break;
    case '$': // I have found the first character
    if (len == 0) {
    len++;
    }
    start = i;
    break;

    case '{': // secondcharacter
    if (len == 1) {
    len++;
    }
    break;

    default: // I create the variable name
    if (len == 2) {
    sb.append(c);
    }
    break;


    } // end of switch
    }

    Result res = new Result(chars, sections);

    return res;
    }

    }
  7. Edit the "layer.xml" to register the factory. Add these tags:
    <folder name="Editors">
        <folder name="text">
            <folder name="x-scanti">
                <file name="org-mycompany-guardedsection-ScantiGuardedSections.instance">
                    <attr name="instanceCreate" newvalue="org.mycompany.guardedsection.ScantiGuardedSectionsFactory"/>
                    <attr name="instanceOf" stringvalue="org.netbeans.spi.editor.guards.GuardedSectionsFactory"/>
                </file>
            </folder>
        </folder>
    </folder>
  8. Run the module. Create a new file and write some text in it, making sure that there is something like ${ABC} in it. Save the file with the ".scanti" extension, close it and re-open it, and the guarded sections will be applied.

 

The Java Zone is brought to you in partnership with AppDynamics. AppDynamics helps you gain the fundamentals behind application performance, and implement best practices so you can proactively analyze and act on performance problems as they arise, and more specifically with your Java applications. Start a Free Trial.

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 }}