NetBeans Manifest File Syntax Highlighting Module Tutorial
http://wiki.netbeans.org/How_to_create_support_for_a_new_language
This tutorial shows you how to enhance the IDE by adding syntax highlighting for Manifest files. At the end of this tutorial, you will have a NetBeans plug-in module file (NBM) that you can share with others. When it is installed via the NetBeans Update Center, the 'name', 'colon', and 'value' of manifest entries will have distinct colors, as illustrated below:
In addition, you will be able to use the Options window to modify the colors.
The following topics are covered in this tutorial:
- Creating Tokens
- Creating Syntax
- Giving the IDE a Reading Test
- Creating the Editor Kit
- Creating the Syntax Coloring
- Creating the Color Restorer
- Creating Options
- Displaying the Options in the Options Window
- Instantiating the Options
- Registering the Options in the NetBeans System Filesystem
Once the software is installed, this tutorial can be completed in 60 minutes.
For more information on creating NetBeans plug-in modules, see the NetBeans Development Project home on the NetBeans website. If you have questions, visit the NetBeans Developer FAQ or use the feedback link at the top of this page.
Getting to Know the Sample
Before you start writing the module, you have to make sure you have all of the necessary software. In addition, you might want to play with the sample before building it yourself.
Installing the Software
Before you begin, you need to install the following software on your computer:
- NetBeans IDE 5.x (download)
- Java Standard Development Kit (JDK™) version 1.4.2 (download) or 5.0 (download)
Installing the Sample
Take the following steps to install the sample:
Introducing the Sample
Now that you know what the user interface of the Manifest Support plug-in module looks like, let's have a quick look at what each of the source files does.
Introducing the Sources
The Manifest Support sample consists of main files and supporting files.
Main Files. The module's main files are in the org.netbeans.modules.manifestsupport package:
FileDescriptionBundle.properties Localization information. ManifestEditorKit.java Extends the NbEditorKit class. ManifestSettingsInitializer.java Extends the Settings.AbstractInitializer class. Includes an inner class than extends the SettingsUtil.TokenColoringInitializer class. ManifestSyntax.java Extends the Syntax class. ManifestTokenContext.java Extends the TokenContext class. RestoreColoring.java Extends the ModuleInstall class. layer.xml This is the NetBeans System Filesystem configuration file. It registers the following information in the NetBeans System Filesystem: - Ability to distinguish Manifest file data objects (via the .mf file extension and text/x-java-jar-manifest MIME Type).
- Actions that appear in the right-click pop-up menu.
- A dummy template for creating new Manifest files is registered in the New File wizard.
- Editor kit, containing syntax highlighting information.
- Options settings file for instantiation of the relevant information in the Options window.
- Coloring information for the Options window, one set for the NetBeans profile and another set for the City Lights profile.
Supporting Files.
Data Object Files. The module's files that distinguish Manifest files from other files are in the org.netbeans.modules.manifestsupport.dataobject package:
For information on these files, see the NetBeans DataLoader Module Tutorial.
Options Window Files. The module's files that install information in the Options window are in the org.netbeans.modules.manifestsupport.options package:
FileDescriptionBundle.properties Localization information. ManifestOptions.java Extends the BaseOptions class. ManifestOptions.settings Instantiation data for the Options window. ManifestOptionsBeanInfo.java Extends the BaseOptionsBeanInfo class. mfOptions.gif GIF file displayed in the node for the Manifest Editor in the Classic View of the Options window. Resources. The module's resources are in the org.netbeans.modules.manifestsupport.resources package and in the Unit Test Packages node:
FileDescriptionCityLights-Properties-fontsColors.xml Colors for the CityLights profile in the Fonts & Colors page of the Options window. ManifestExample Example used in the Fonts & Colors page of the Options window. NetBeans-Manifest-fontsColors.xml Colors for the NetBeans profile in the Fonts & Colors page of the Options window. ManifestSyntaxTest.java JUnit test for testing the Manifest file's tokens. Important Files. The Projects window is typically used to display a project's packages. However, to simplify working with some of the more important files in the project, you can access them through the Important Files node:
For basic information each of the Important Files, see the Introduction to NetBeans Module Development.
Setting Up the Module Project
Before you start writing the module, you have to make sure you that your project is set up correctly. NetBeans IDE 5.x provides a wizard that sets up all the basic files needed for a module.
Creating the Module Project
The IDE creates the ManifestSupport project. The project contains all of your sources and project metadata, such as the project's Ant build script. The project opens in the IDE. You can view its logical structure in the Projects window (Ctrl-1) and its file structure in the Files window (Ctrl-2).
Specifying the Module's Dependencies
You will need to subclass several classes that belong to NetBeans APIs. Each has to be declared as a Module dependency. Use the Project Properties dialog box for this purpose.
- In the Projects window, right-click the ManifestSupport project node and choose Properties. In the Project Properties dialog box, click Libraries.
- Datasystems API
- Editor API
- Editor Library API
- File System API
- Module System API
- Nodes API
- Settings Options API
- Text API
- Utilities API
- Window System API
You should now see the following:
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://www.netbeans.org/ns/project/1"> <type>org.netbeans.modules.apisupport.project</type> <configuration> <data xmlns="http://www.netbeans.org/ns/nb-module-project/2"> <code-name-base>org.netbeans.modules.manifestsupport</code-name-base> <standalone/> <module-dependencies> <dependency> <code-name-base>org.netbeans.modules.editor</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <release-version>3</release-version> <specification-version>1.25.0.1</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.netbeans.modules.editor.lib</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <release-version>1</release-version> <specification-version>1.8.0.1</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.modules</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.4</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.options</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.3</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.util</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.5</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.filesystems</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.3</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.loaders</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>5.7</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.nodes</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.6</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.text</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.7</specification-version> </run-dependency> </dependency> <dependency> <code-name-base>org.openide.windows</code-name-base> <build-prerequisite/> <compile-dependency/> <run-dependency> <specification-version>6.3</specification-version> </run-dependency> </dependency> </module-dependencies> <public-packages/> </data> </configuration> </project>
Creating the Main Files
Creating the module starts with ensuring that the IDE recognizes Manifest files. By default, it does not recognize them and, therefore, treats them as text files. Work through the NetBeans DataLoader Module Tutorial before going further—make sure that you put the files you create in a package called org.netbeans.modules.manifestsupport.dataobject. Before you go further with this tutorial, the IDE should be able to recognize Manifest files. At the end of the NetBeans DataLoader Module Tutorial, you should have a new MIME Type, too: text/x-java-jar-manifest. This MIME Type is used throughout this tutorial.
Once you have a dataloader specifically for Manifest files, you need to identify items in the Manifest file that you want the IDE to be able to distinguish from each other. Each distinct item is called a token. Once you have created tokens, you need to tell the IDE how to interpret the text it finds—it needs to be told which piece of text constitutes which token. In other words, you need to create a syntax. Next, you associate the tokens with colors. In the process, you create an editor specifically for Manifest files.
Creating Tokens
Do the following:
package org.netbeans.modules.manifestsupport; import org.netbeans.editor.BaseTokenID; import org.netbeans.editor.TokenContext; import org.netbeans.editor.TokenContextPath; import org.netbeans.editor.Utilities; public class ManifestTokenContext extends TokenContext { // Numeric-ids for token categories public static final int NAME_ID = 1; public static final int COLON_ID = 2; public static final int VALUE_ID = 3; public static final int END_OF_LINE_ID = 4; // Token-ids public static final BaseTokenID NAME = new BaseTokenID("name", NAME_ID); public static final BaseTokenID COLON = new BaseTokenID("colon", COLON_ID); public static final BaseTokenID VALUE = new BaseTokenID("value", VALUE_ID); public static final BaseTokenID END_OF_LINE = new BaseTokenID("end-of-line", END_OF_LINE_ID); // Context instance declaration public static final ManifestTokenContext context = new ManifestTokenContext(); public static final TokenContextPath contextPath = context.getContextPath(); /** * Construct a new ManifestTokenContext */ private ManifestTokenContext() { super("mf-"); try { addDeclaredTokenIDs(); } catch (Exception e) { Utilities.annotateLoggable(e); } } }
Creating Syntax
Do the following:
package org.netbeans.modules.manifestsupport; import org.netbeans.editor.Syntax; import org.netbeans.editor.TokenID; import org.openide.ErrorManager; public class ManifestSyntax extends Syntax { /** * The logger for this class. It can be used for tracing the class activity, * logging debug messages, etc. */ private static final ErrorManager LOGGER = ErrorManager.getDefault().getInstance("org.netbeans.modules." + "manifestsupport.ManifestSyntax"); /** * Used to avoing calling the log() or notify() method if the message * wouldn't be loggable anyway. */ private static final boolean LOG = LOGGER.isLoggable(ErrorManager.INFORMATIONAL); // The states for the lexical analyzer private static final int ISI_NAME = 1; // inside the name part private static final int ISA_AFTER_NAME = ISI_NAME + 1; // immediately after the name part private static final int ISA_AFTER_COLON = ISA_AFTER_NAME + 1; // immediately after the // colon between the name // and the value private static final int ISI_VALUE = ISA_AFTER_COLON + 1; // inside the value part protected TokenID parseToken() { TokenID result = doParseToken(); if (LOG) { LOGGER.log(ErrorManager.INFORMATIONAL, "parseToken: " + result); } return result; } private TokenID doParseToken() { char actChar; while (offset < stopOffset) { actChar = buffer[offset]; switch (state) { case INIT: switch (actChar) { case ':': state = ISA_AFTER_COLON; offset++; return ManifestTokenContext.COLON; case '\n': state = INIT; offset++; return ManifestTokenContext.END_OF_LINE; default: state = ISI_NAME; } break; case ISI_NAME: switch (actChar) { case ':': case '\n': state = ISA_AFTER_NAME; return ManifestTokenContext.NAME; } break; case ISA_AFTER_NAME: switch (actChar) { case ':': state = ISA_AFTER_COLON; offset++; return ManifestTokenContext.COLON; case '\n': state = INIT; offset++; return ManifestTokenContext.END_OF_LINE; default: assert false; } break; case ISA_AFTER_COLON: switch (actChar) { case '\n': state = INIT; offset++; return ManifestTokenContext.END_OF_LINE; default: state = ISI_VALUE; } break; case ISI_VALUE: switch (actChar) { case '\n': state = INIT; return ManifestTokenContext.VALUE; } break; } offset++; } /* * At this stage there's no more text in the scanned buffer. * It is valid to return a token here only if this is the last * buffer (otherwise the token could continue in the next buffer). */ if (lastBuffer) { switch (state) { case ISI_NAME: state = INIT; return ManifestTokenContext.NAME; case ISI_VALUE: state = INIT; return ManifestTokenContext.VALUE; } } return null; } }
For example, for names the IDE starts in the initial state and the first time it encounters a valid character for a name, it enters the ISI_NAME state. It stays in this state until it encounters a \r, \n or : character, which cannot be part of a name. When it encounters such a character, the IDE knows that the characters it just traversed make up a name token. The code runs within a while loop. At the end there is a break statement, which increases the offset in the text. The return statement in the code avoids increasing the offset and ensures that the parsing of the next token, after a name, will start with this character (it will likely be a colon, which is a meaningful token itself). The break statement, on the other hand, ensures that offset is increased. When all the characters up to the colon are tested, the IDE knows whether the cursor is inside a name or not.
Giving the IDE a Reading Test
Do the following:
import java.util.Arrays; import java.util.Iterator; import java.util.List; import junit.framework.TestCase; import org.netbeans.editor.Syntax; import org.netbeans.editor.TokenID; import org.netbeans.modules.manifestsupport.*; import junit.framework.TestCase; public class ManifestSyntaxTest extends TestCase { public ManifestSyntaxTest(String testName) { super(testName); } public void testNextToken() { doParse("Manifest-Version: 1.0", new TokenID[] { ManifestTokenContext.NAME, ManifestTokenContext.COLON, ManifestTokenContext.VALUE, }); doParse("Manifest-Version: 1.0\n\n" + "OpenIDE-Module: org.netbeans.modules.manifestsupport\n", new TokenID[] { ManifestTokenContext.NAME, ManifestTokenContext.COLON, ManifestTokenContext.VALUE, ManifestTokenContext.END_OF_LINE, ManifestTokenContext.END_OF_LINE, ManifestTokenContext.NAME, ManifestTokenContext.COLON, ManifestTokenContext.VALUE, ManifestTokenContext.END_OF_LINE, }); } public void doParse(String m, TokenID[] expected) { Syntax s = new ManifestSyntax(); s.load(null, m.toCharArray(), 0, m.length(), true, m.length()); TokenID token = null; Iterator i = Arrays.asList(expected).iterator(); do { token = s.nextToken(); if (token != null) { if (!i.hasNext()) { fail("More tokens returned than expected."); } else { assertSame("Tokens differ", i.next(), token); } } else { assertFalse("More tokens expected than returned.", i.hasNext()); } System.out.println(token); } while (token != null); } }
You can play with the testNextToken() method to see what the IDE does when the entered text does not comply with the specified tokens. For example, change the method by deleting one of the token IDs (ManifestTokenContext.VALUE), so that the method looks as follows:
public void testNextToken() { doParse("Manifest-Version: 1.0", new TokenID[] { ManifestTokenContext.NAME, ManifestTokenContext.COLON, }); }
Now there are more tokens than token IDs. (There are two token IDs here—ManifestTokenContext.NAME and ManifestTokenContext.COLON.) When you run the JUnit test, the message 'More tokens returned than expected.' is returned:
Now change the method by deleting the colon between Manifest-Version and 1.0, as shown below:
public void testNextToken() { doParse("Manifest-Version 1.0", new TokenID[] { ManifestTokenContext.NAME, ManifestTokenContext.COLON, ManifestTokenContext.VALUE, }); }
Now there are too few tokens. When you run the JUnit test, the message 'More tokens expected than returned.' is returned:
Finally, let's test to see that the IDE understands more than just when there are too many or too few tokens—it should also know when a token is in the wrong place. Add the token that specifies the end-of-line. For example, here \n is added right after the name and before the colon:
public void testNextToken() { doParse("Manifest-Version\n:1.0", new TokenID[] { ManifestTokenContext.NAME, ManifestTokenContext.COLON, ManifestTokenContext.VALUE, }); }
When you run the test, the following error is returned:
Now that we know that the IDE correctly converts manifest entries into tokens, lets create an editor to work with Manifest files and associate colors with tokens.
Creating the EditorKit
Do the following:
package org.netbeans.modules.manifestsupport; import javax.swing.text.Document; import org.netbeans.editor.BaseDocument; import org.netbeans.editor.Syntax; import org.netbeans.editor.SyntaxSupport; import org.netbeans.modules.editor.NbEditorKit; import org.openide.ErrorManager; public class ManifestEditorKit extends NbEditorKit { public static final String MIME_TYPE = "text/x-java-jar-manifest"; // NOI18N /** * Creates a new instance of ManifestEditorKit */ public ManifestEditorKit() { } /** * Create a syntax object suitable for highlighting Manifest file syntax */ public Syntax createSyntax(Document doc) { return new ManifestSyntax(); } /** * Retrieves the content type for this editor kit */ public String getContentType() { return MIME_TYPE; } }
Creating the Syntax Coloring
Do the following:
package org.netbeans.modules.manifestsupport;
import java.awt.Color;
import java.awt.Font;
import java.util.Map;
import org.netbeans.editor.BaseKit;
import org.netbeans.editor.Coloring;
import org.netbeans.editor.Settings;
import org.netbeans.editor.SettingsDefaults;
import org.netbeans.editor.SettingsNames;
import org.netbeans.editor.SettingsUtil;
import org.netbeans.editor.TokenCategory;
import org.netbeans.editor.TokenContext;
import org.netbeans.editor.TokenContextPath;
public class ManifestSettingsInitializer
extends Settings.AbstractInitializer {
public static final String NAME =
"manifest-settings-initializer"; // NOI18N
/**
* Constructor
*/
public ManifestSettingsInitializer() {
super(NAME);
}
/**
* Update map filled with the settings.
* @param kitClass kit class for which the settings are being updated.
* It is always non-null value.
* @param settingsMap map holding [setting-name, setting-value] pairs.
* The map can be empty if this is the first initializer
* that updates it or if no previous initializers updated it.
*/
public void updateSettingsMap(Class kitClass, Map settingsMap) {
if (kitClass == BaseKit.class) {
new ManifestTokenColoringInitializer().
updateSettingsMap(kitClass, settingsMap);
}
if (kitClass == ManifestEditorKit.class) {
SettingsUtil.updateListSetting(
settingsMap,
SettingsNames.TOKEN_CONTEXT_LIST,
new TokenContext[]
{ ManifestTokenContext.context }
);
}
}
/**
* Class for adding syntax coloring to the editor
*/
/** Properties token coloring initializer. */
private static class ManifestTokenColoringInitializer
extends SettingsUtil.TokenColoringInitializer {
/** Bold font. */
private static final Font boldFont =
SettingsDefaults.defaultFont.deriveFont(Font.BOLD);
/** Italic font. */
private static final Font italicFont =
SettingsDefaults.defaultFont.deriveFont(Font.ITALIC);
/** Key coloring. */
private static final Coloring keyColoring =
new Coloring(boldFont, Coloring.FONT_MODE_APPLY_STYLE,
Color.blue, null);
/** Value coloring. */
private static final Coloring valueColoring =
new Coloring(null, Color.magenta, null);
/** Colon coloring. */
private static final Coloring colonColoring =
new Coloring(null, Color.DARK_GRAY, null);
/** Empty coloring. */
private static final Coloring emptyColoring =
new Coloring(null, null, null);
/** Constructs PropertiesTokenColoringInitializer
. */
public ManifestTokenColoringInitializer() {
super(ManifestTokenContext.context);
}
/** Gets token coloring. */
public Object getTokenColoring(TokenContextPath tokenContextPath,
TokenCategory tokenIDOrCategory, boolean printingSet) {
if(!printingSet) {
int tokenID = tokenIDOrCategory.getNumericID();
if(tokenID == ManifestTokenContext.NAME_ID) {
return keyColoring;
} else if(tokenID == ManifestTokenContext.VALUE_ID) {
return valueColoring;
} else if(tokenID == ManifestTokenContext.COLON_ID) {
return colonColoring;
}
} else { // printing set
return SettingsUtil.defaultPrintColoringEvaluator;
}
return null;
}
} // End of class ManifestTokenColoringInitializer.
}
Creating the Color Restorer
Do the following:
package org.netbeans.modules.manifestsupport;
import org.netbeans.editor.LocaleSupport;
import org.netbeans.editor.Settings;
import org.openide.modules.ModuleInstall;
import org.openide.util.NbBundle;
public class RestoreColoring extends ModuleInstall {
/**
* Localizer
passed to editor.
*/
private static LocaleSupport.Localizer localizer;
/**
* Registers properties editor, installs options and copies settings.
* Overrides superclass method.
*/
public void restored() {
addInitializer();
installOptions();
}
/**
* Uninstalls properties options.
* And cleans up editor settings copy.
* Overrides superclass method.
*/
public void uninstalled() {
uninstallOptions();
}
/**
* Adds initializer and registers editor kit.
*/
public void addInitializer() {
Settings.addInitializer(new ManifestSettingsInitializer());
}
/**
* Installs properties editor and print options.
*/
public void installOptions() {
// Adds localizer.
LocaleSupport.addLocalizer(localizer = new LocaleSupport.Localizer() {
public String getString(String key) {
return NbBundle.getMessage(RestoreColoring.class, key);
}
});
}
/** Uninstalls properties editor and print options. */
public void uninstallOptions() {
// remove localizer
LocaleSupport.removeLocalizer(localizer);
}
}
OpenIDE-Module-Install: org/netbeans/modules/manifestsupport/RestoreColoring.class
Note that the OpenIDE-Module-Install key belongs at the end of the first section (it is highlighted in bold below):
Manifest-Version: 1.0 OpenIDE-Module: org.netbeans.modules.manifestsupport OpenIDE-Module-Layer: org/netbeans/modules/manifestsupport/layer.xml OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/manifestsupport/Bundle.properties OpenIDE-Module-Specification-Version: 1.0 OpenIDE-Module-Install: org/netbeans/modules/manifestsupport/RestoreColoring.class Name: org/netbeans/modules/manifestsupport/dataobject/ManifestDataLoader.class OpenIDE-Module-Class: Loader
Creating the Supporting Files
In this section, you update the Options window to allow the user to customize the way the tokens are presented in the IDE. Not only the colors, but also the font type and style, and the Manifest editor itself, can then be modified by the user in the Options window.
Creating the Options
Do the following:
package org.netbeans.modules.manifestsupport.options; import java.util.MissingResourceException; import org.netbeans.modules.editor.options.BaseOptions; import org.netbeans.modules.manifestsupport.ManifestEditorKit; import org.openide.util.HelpCtx; import org.openide.util.NbBundle; public class ManifestOptions extends BaseOptions { public static String MANIFEST = "Manifest"; // NOI18N /** Name of property. */ private static final String HELP_ID = "editing.editor.mf"; // NOI18N //no manifest specific options at this time static final String[] MF_PROP_NAMES = new String[] {}; public ManifestOptions() { super(ManifestEditorKit.class, MANIFEST); } /** * Gets the help ID */ public HelpCtx getHelpCtx() { return new HelpCtx(HELP_ID); } /** * Look up a resource bundle message, if it is not found locally defer to * the super implementation */ protected String getString(String key) { try { return NbBundle.getMessage(ManifestOptions.class, key); } catch (MissingResourceException e) { return super.getString(key); } } }
Displaying the Options in the Options Window
Do the following:
package org.netbeans.modules.manifestsupport.options; import java.util.MissingResourceException; import org.netbeans.modules.editor.options.BaseOptionsBeanInfo; import org.netbeans.modules.editor.options.OptionSupport; import org.openide.util.NbBundle; public class ManifestOptionsBeanInfo extends BaseOptionsBeanInfo { /** * Constructor. The parameter in the superclass constructor is the * icon prefix. */ public ManifestOptionsBeanInfo() { super("/org/netbeans/modules/manifestsupport/options/mfOptions"); // NOI18N } /* * Gets the property names after merging it with the set of properties * available from the BaseOptions from the editor module. */ protected String[] getPropNames() { return OptionSupport.mergeStringArrays( super.getPropNames(), ManifestOptions.MF_PROP_NAMES); } /** * Get the class described by this bean info */ protected Class getBeanClass() { return ManifestOptions.class; } /** * Look up a resource bundle message, if it is not found locally defer to * the super implementation */ protected String getString(String key) { try { return NbBundle.getMessage(ManifestOptionsBeanInfo.class, key); } catch (MissingResourceException e) { return super.getString(key); } } }

Add a Bundle.properties file to the org.netbeans.modules.manifestsupport.options package and add this line to it:
OPTIONS_Manifest=Manifest Editor
The string after the "OPTIONS_" above must match the String declaration in the ManifestOptions class. For example, in this tutorial, the following string is declared in ManifestOptions.java:
public static String MANIFEST = "Manifest";
However, if this line was as follows:
public static String MANIFEST = "MF";Then the Bundle.properties file would specify this string instead:
OPTIONS_MF=Manifest Editor
Note also that the string is case-sensitive.
Now this is the label that will be displayed in the Classic View of the Options window, to distinguish the Manifest Editor section from the other editors displayed in the Classic View of the Options window, as shown below:
Notice that there is an icon to the left of the label 'Manifest Editor' in the screenshot above. This is the mfOptions.gif file specified in the ManifestOptionsBeanInfo class.
Instantiating the Options
Do the following:
<?xml version="1.0"?> <!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN" "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd"> <settings version="1.0"> <module name="org.netbeans.modules.manifestsupport"/> <instanceof class="java.io.Externalizable"/> <instanceof class="org.openide.util.SharedClassObject"/> <instanceof class="java.beans.beancontext.BeanContextProxy"/> <instanceof class="java.io.Serializable"/> <instanceof class="org.openide.options.SystemOption"/> <instanceof class="org.netbeans.modules.editor.options.OptionSupport"/> <instanceof class="org.netbeans.modules.editor.options.BaseOptions"/> <instance class="org.netbeans.modules.manifestsupport.options.ManifestOptions"/> </settings>
Registering the Options in the NetBeans System Filesystem
To register the module in the Options window, you must do the following in the layer.xml file:
- Create default colors for each profile that the IDE supports. Currently, the IDE supports a profile called 'NetBeans' and a profile called 'City Lights'. They can be selected from the Profile drop-down list at the top of the Fonts & Colors section.
- Register the ManifestEditorKit.java class.
- Register the ManifestOptions.settings file.
- Create a file containing a text that will be shown in the Preview window, at the bottom of the Fonts & Colors section of the Options window.
- Localize the strings in the layer.xml file.
The screenshot on the left shows the NetBeans profile; on the right you see the City Lights profile:
Do the following:
<folder name="Editors"> <folder name="text"> <folder name="x-java-jar-manifest"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.manifestsupport.Bundle"/> <folder name="NetBeans"> <folder name="Defaults"> <file name="coloring.xml" url="resources/NetBeans-Manifest-fontsColors.xml"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.manifestsupport.Bundle"/> </file> </folder> </folder> <folder name="CityLights"> <folder name="Defaults"> <file name="coloring.xml" url="resources/CityLights-Properties-fontsColors.xml"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.manifestsupport.Bundle"/> </file> </folder> </folder> <file name="Settings.settings" url="options/ManifestOptions.settings"/> <file name="EditorKit.instance"> <attr name="instanceClass" stringvalue="org.netbeans.modules.manifestsupport.ManifestEditorKit"/> </file> </folder> </folder> </folder> <folder name="OptionsDialog"> <folder name="PreviewExamples"> <folder name="text"> <file name="x-java-jar-manifest" url="resources/ManifestExample"/> </folder> </folder> </folder>
text/x-java-jar-manifest=Manifest NetBeans=NetBeans mf-name=Name mf-value=Value mf-colon=Colon
<?xml version="1.0" encoding="UTF-8"?> <fontscolors> <fontcolor name="mf-name" foreColor="blue" default="default"/> <fontcolor name="mf-value" foreColor="magenta" default="default"/> <fontcolor name="mf-colon" default="default"/> </fontscolors>
<?xml version="1.0" encoding="UTF-8"?> <fontscolors> <fontcolor name="mf-name" default="default"/> <fontcolor name="mf-value" default="default"/> <fontcolor name="mf-colon" default="default"/> </fontscolors>
Manifest-Version: 1.0
Building and Installing the Module
The IDE uses an Ant build script to build and install your module. The build script is created for you when you create the module project.
Installing and Testing the NetBeans Module
- In the Projects window, right-click the ManifestSupport project and choose Install/Reload
in Target Platform.
The module is built and installed in the target IDE or Platform. The target IDE or Platform opens so that you can try out your new module. The default target IDE or Platform is the installation used by the current instance of the development IDE. Note that when you run your module, you will be using a temporary test user directory, not the development IDE's user directory.
- Choose File > New Project (Ctrl-Shift-N). Create a new Java application project
or a new Plug-in Module project. Open the project's Manifest file in the Source Editor
and notice the syntax highlighting.
For other aspects of this module, see the Introducing the Sample section.
Creating a Shareable Module Binary (NBM File)
An NBM file is a NetBeans module packaged for delivery via the web. The principal differences between NBM files and module JAR files are:
- An NBM file is compressed.
- An NBM file can contain more than one JAR file—modules can package any libraries they use into their NBM file.
- An NBM file contains metadata that NetBeans will use to display information about it in the Update Center, such as the manifest contents, the license, etc.
- An NBM file is typically signed for security purposes.
NBM files are just ZIP files with a special extension. They use the JDK's mechanism for signing JAR files. Unless you are doing something unusual, you will not need to worry about the contents of an NBM file—just let the standard Ant build script for NBM creation take care of it for you. The IDE generates the build script based on the options you enter in the project's Project Properties dialog box. You can set the module's dependencies, versioning, and packaging information in the Project Properties dialog box. You can further customize program execution by editing the Ant script and Ant properties for the project.
- In the Projects window, right-click the ManifestSupport project node and choose Create NBM.
The NBM file is created and you can view it in the Files window (Ctrl-2):
- Make it available to others via, for example, e-mail.
- Use the Update Center to install the NBM file.
Exploring Further
Below are some other avenues that you might want to explore.
Associating the EditorKit with a JEditorPane
Instead of using the EditorKit in the Source Editor, you can associate it with a JEditorPane.
Do the following:
In effect, the JEditorPane is now your editor. You can use the Options window to modify fonts and colors, just as if you were using the Source Editor.
Code Folding
To come.
Indentation Engine
To come.
Other Options
To come.
Applying What You Have Learned
Once you are comfortable with the principles outlined in this tutorial, you can begin applying them to your own scenarios. For example, here is NetBeans user Valentin Buergel's Wiki syntax highlighting:
Next Steps
For more information about creating and developing NetBeans Module, see the following resources:
Versioning
Version
|
Date
|
Changes
|
1 | 23 October 2005 |
|
2 | 30 November 2005 |
|
3 | 5 March 2006 |
|
4 | 13 June 2006 |
|
Many thanks to Andrei Badea, Tom Ball, and Martin Adamek for help and inspiration.