NetBeans Manifest File Syntax Highlighting Module Tutorial

Please note that this tutorial is obsolete. Go here instead:

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:

Highlighted manifest file

In addition, you will be able to use the Options window to modify the colors.

The following topics are covered in this tutorial:

  • Setting Up the Module Project
  • Creating the Main Files
  • Creating the Supporting Files
  • Building and Installing the Module
  • Exploring Further
  • 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:

    Installing the Sample

    Take the following steps to install the sample:

    1. Unzip the attached file.

    2. In the IDE, choose File > Open Project and browse to the folder that contains the unzipped file. Open the module project. It should look as follows:

      Final Projects window

    3. Right-click the project node and choose Install/Reload in Target Platform. The target platform opens and the module is installed.

      Introducing the Sample

    4. Use the New project wizard (Ctrl-Shift-N) to create a new Java application project or NetBeans plug-in module project and notice that the project's manifest file has syntax highlighting, as shown in the introduction of this tutorial.

    5. Choose Tools > Options. In the Options window, click Fonts & Colors in the left sidebar and notice that there is an entry called 'Manifest' in the Language drop-down list, as shown below:

      Fonts and Colors 1

    6. Select City Lights in the Profile drop-down list at the top of the Fonts & Colors page, and notice that the syntax changes, as shown below:

      Fonts and Colors 4

    7. Click Editor in the left sidebar and notice that there is an entry called 'Manifest' in the Language drop-down list, as shown below:

      Fonts and Colors 2

    8. Click Advanced Options in the lower left corner of the Options window. In the Editor Settings node, notice that there is a new entry for the Manifest Editor, as shown below:

      Fonts and Colors 3

      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:

        Main files.

        File
        Description
        Bundle.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:

          Data object files.

          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:

          Options window files.

          File
          Description
          Bundle.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:

          Resources files.

          File
          Description
          CityLights-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:

          Important files.

          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

  • Choose File > New Project. Under Categories, select NetBeans Plug-in Modules. Under projects, select Module Project and click Next.
  • In the Name and Location panel, type ManifestSupport in Project Name. Change the Project Location to any directory on your computer, such as c:\mymodules. Leave the Standalone Module radiobutton and the Set as Main Project checkbox selected. Click Next.

  • In the Basic Module Configuration panel, replace yourorghere in Code Name Base with netbeans.modules so that the whole name is org.netbeans.modules.manifestsupport. Leave ManifestSupport as the Module Display Name. Leave the location of the localizing bundle and XML layer, so that they will be stored in a package with the name org.netbeans.modules.manifestsupport. Click Finish.

    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.

    1. In the Projects window, right-click the ManifestSupport project node and choose Properties. In the Project Properties dialog box, click Libraries.

  • For each of the following APIs that is not already present in the Libraries panel, click "Add...", select the name from the Module list, and then click OK to confirm it:

    You should now see the following:

    Project Properties dialog box.

  • Click OK to exit the Project Properties dialog box.

  • In the Projects window, expand the Important Files node, double-click the Project Metadata node, and note that the APIs you selected have been declared as Module dependencies:
  • <?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:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, and type ManifestTokenContext in Class Name. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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);
            }
        }
    }
  • Understand the file. This Java class specifies a token for each item in the Manifest file with which we want to work. Each distinct item in a Manifest file is a token: 'name', 'colon', and 'value'. In addition, there is also a token for the end of the line, because we need to work with the end of the line—the end of the line determines where a value ends and the next name begins. The constructor above specifies that all tokens will be prefaced by 'mf-'. So, we've now created tokens called 'mf-name', 'mf-colon', 'mf-value', and 'mf-end-of-line'.

    Creating Syntax

    Do the following:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, and type ManifestSyntax in Class Name. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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;
        }
        
    }
  • Understand the file. This Java class tells the IDE which part of the text is which token. It does this by starting in an initial state and sequentially looking at each character in the text and deciding if it stays in that state, moves to another state, or announces that a token was found.

    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:

  • Create the file. Right-click the Unit Test Packages node, choose New > Java Class, type ManifestSyntaxTest in Class Name, and type org.netbeans.modules.manifestsupport in Package. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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);
        }
    }
  • Understand the file. This Java class checks that the IDE is able to correctly convert text to tokens. To run the test, right-click the project node and choose Run Unit Tests. The test should succeed, returning the following result in the JUnit Test Results window:

    error result from JUnit test.

    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:

    error result from JUnit test.

    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:

    error result from JUnit test.

    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:

    error result from JUnit test.

    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:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, and type ManifestEditorKit in Class Name. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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;
        }
    }
  • Understand the file. This Java class specifies an EditorKit specifically for a NetBeans editor. Therefore, it extends the NBEditorKit class. An editor kit establishes the set of things needed by a text component to be a reasonably functioning editor for some type of text content. In this case, the editor is for content that uses the text/x-java-jar-manifest MIME Type.

    Creating the Syntax Coloring

    Do the following:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, and type ManifestSettingsInitializer in Class Name. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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.
        
    }
  • Understand the file. This Java class sets the default colors for our tokens. Thanks to the supporting files described in the following sections, the user will be able to use the Options window to change the defaults.

    Creating the Color Restorer

    Do the following:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, and type RestoreColoring in Class Name. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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);
        }
        
    }
  • Understand the file. This Java class is used to install the module and maintain its state. In addition to creating it, you must specify this class in the module's manifest file, by adding the following line:

    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:

  • Create the file. Right-click the ManifestSupport project node, choose New > Java Class, type ManifestOptions in Class Name, and type org.netbeans.modules.manifestsupport.options in Package. Click Finish. The new Java class opens in the Source Editor. Replace the default code with 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);
            }
        }
        
    }
  • Understand the file. This Java class registers the ManifestEditorKit in the Options window. If there were properties specific to the Manifest Editor, they would be added in the static final String[] MF_PROP_NAMES = new String[] {}; declaration. However, in this implementation, only the default options will be supported. In addition, this file adds the string 'Manifest' to the drop-down list in the Code Templates page of the Editor section, as shown below:

    Fonts and Colors 2

    Displaying the Options in the Options Window

    Do the following:

  • Create the file. Create the ManifestOptionsBeanInfo file and add it to the org.netbeans.modules.manifestsupport.options package. Replace the default code with 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);
            }
        }
    }
  • Understand the file. This Java class displays the options specified in ManifestOptions in the Options window. The file referenced in the constructor can be any 16x16 pixel file. If you do not have one, you can get it from the downloadable ZIP file at the top of the page or you can right-click and save it here: mfOptions

    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:

    Fonts and Colors 3

    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:

  • Create the file. Create an XML file called ManifestOptions.settings and place it in the org.netbeans.modules.manifestsupport.options package. Replace the default code with 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>
  • Understand the file. This file lets the IDE quickly instantiate the ManifestOptions.java file, whenever called upon to do so. The next section shows you how to register the settings file in the NetBeans System Filesystem.

    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:

    Fonts and Colors 1 Fonts and Colors 4

    Do the following:

  • Update the layer.xml file. Add the following entries to the layer.xml file:
    <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>
  • Localize the Fonts & Colors page. In the package where the layer.xml file is found (i.e., in org.netbeans.modules.manifestsupport), add the following entries to the Bundle.properties file:

    text/x-java-jar-manifest=Manifest
    NetBeans=NetBeans
    mf-name=Name
    mf-value=Value
    mf-colon=Colon

  • Create a color file for each profile. Create a package called org.netbeans.modules.manifestsupport.resources and add two files, NetBeans-Manifest-fontsColors.xml and CityLights-Properties-fontsColors.xml:

    • NetBeans-Manifest-fontsColors.xml has the following content:

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

    • CityLights-Properties-fontsColors.xml has the following content:
      <?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>

    • Create the Preview file. At the bottom of the Fonts & Colors section in the Options window, the user can preview changes made to the way the Manifest file's entries are displayed in the editor. To specify the text that is displayed in the preview window, you need to create a file, as specified in the OptionsDialog folder of the layer.xml file above. In the org.netbeans.modules.manifestsupport.resources package, add a file called ManifestExample (note that this file has no extension), with the following content:

      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

      1. 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.

      2. 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.

      1. 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):

        Shareable NBM.

      2. Make it available to others via, for example, e-mail.

      3. 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:

    • Select the JEditorPane, click the editorKit property, and make the following selections:

      editorPane.

    • Select the JEditorPane, click the contentType property, and type the Manifest file's MIME Type (text/x-java-jar-manifest):

      editorPane2.

    • When you deploy the module, syntax highlighting will be enabled in the JEditorPane:

      editorPane3.

      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:

    wiki-screenshot




    Next Steps

    For more information about creating and developing NetBeans Module, see the following resources:

  • Module Developer's Resources
  • NetBeans API List (Current Development Version)
  • New API Support-Proposal

  • Versioning

    Version
    Date
    Changes
    1 23 October 2005
    • Initial version.
    2 30 November 2005
    • Added Valentin's screenshot at the end.
    • Fixed some smaller issues:
      • issue 69568
      • Wrong closing tag in one of the coloring XML files.
      • NbEditorKit was mistakenly NBEditorkit before
    • Need to fix several larger issues (from Andrei Badea and one or two people from openide mailing list) soon.
    3 5 March 2006
    • Several fixes:
      • Successfully worked through the whole tutorial, using NetBeans IDE 5.x (final version).
      • Added new test (from Andrei Badea) to assert new line parsing is working.
      • Fixed layer.xml error relating to new way of registering the preview file.
      • Tried to use Andrei Badea (and Vladimir) fixes for manifestSyntax,java, but that broke other things. Need to check that with him.
    4 13 June 2006
    • Took the ZIP attached to this tutorial, installed the NBM in 5.5 dev. No problems, worked as expected, except JUnit test had problems -- no junit.framework.test found in JUnit module.
    • Following suggestion by Tim B., shortened many lines of code to ensure that people don't need to scroll to the right too much, but some instances of this problem remain.
    • Need to go back to the code snippets and put back links to Javadoc, which were removed when I replaced the code, straight from the Source Editor, which I did to make sure that the shortened lines wouldn't cause errors.
    • Need to change the ZIP file to match the changes made in previous versions of the tutorial.
    • Need to integrate the Module Installer wizard, no need to manually create one (plus automatic manifest.mf and project.xml change).

    Many thanks to Andrei Badea, Tom Ball, and Martin Adamek for help and inspiration.

    Project Features

    Project Links

    About this Project

    Platform was started in November 2009, is owned by Antonin Nebuzelsky, and has 138 members.
    By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20140418.2d69abc). © 2013, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
     
     
    Close
    loading
    Please Confirm
    Close