How to use Guarded Sections in Editors on the NetBeans Platform
Join the DZone community and get the full member experience.
Join For FreeI 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.
- 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.
- 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.
- 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".
- 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);
}
}
}
} - 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);
}
} - 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;
}
} - 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> - 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.
NetBeans
Opinions expressed by DZone contributors are their own.
Comments