How to use certain NetBeans APIs

This page contains extracted usecases for some of the NetBeans modules that offer an API.

How to use Ant?

The Ant integration module recognizes Ant build scripts, facilitates editing them as text or structurally, lets the user run targets or create shortcuts to them, etc.

The principal use cases for the API are covered in the overall API architecture.


How to use Debugger Core API?

The debuggercore/api module (Debugger Core API) allows to install different debugger implementation to one IDE. It allows to share some common UI components.

UseCase I. - Install and use CPP debugger plug-in to NetBeans + Java Debugger.

CPP debugger plug-in installs support for debugging of some new language to the NetBeans IDE, and some new debugging engine. This implementation of debugger should share UI components (actions, Debugger Views, ...) with default NB Java Debugger. It should share basic debugger model too - notion of current context, current session, thread, call stack line, ...

CPP debugger plug-in installs:

UseCase II. - Install and use JSP debugger plug-in to NetBeans + Java Debugger.

JSP debugger plug-in installs support for debugging of some new language to the NetBeans Java Debugger. It does not contain a new debugger engine, but it delegates to standard NB Java debugger. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

JSP debugger plug-in installs:

UseCase III. - Install and use J2EE debugger plug-in to NetBeans + Java Debugger.

J2EE debugger plug-in installs some enhancements to the standard Java Debugger. It does not contain a new debugger engine or language support. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

J2EE debugger plug-in installs:

UseCase IV. - Install and use DBX debugger plug-in to NetBeans.

DBX debugger plug-in installs support for debugging of some new language (CPP) to the NetBeans IDE, and some new debugging engine. But it contains debugger engine for Java debugging too. DBX debugger engine has its own session management (or will have in the next versions). One debugger engine can manage more than one sessions. One engine supports debugging in more than one language.

UseCase V. - Implement Debugger Core UI module on top of Debugger Core API / SPI.

Debugger Core UI needs:


How to use JPDA Debugger API?

The debuggerjpda/api (Debugger JPDA API) defines API for NetBeans Java Debugger.

UseCase I. - Install and use CPP debugger plug-in to NetBeans + Java Debugger.

CPP debugger plug-in installs support for debugging of some new language to the NetBeans IDE, and some new debugging engine. This implementation of debugger should share UI components (actions, Debugger Views, ...) with default NB Java Debugger. It should share basic debugger model too - notion of current context, current session, thread, call stack line, ...

CPP debugger plug-in installs:

UseCase II. - Install and use JSP debugger plug-in to NetBeans + Java Debugger.

JSP debugger plug-in installs support for debugging of some new language to the NetBeans Java Debugger. It does not contain a new debugger engine, but it delegates to standard NB Java debugger. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

JSP debugger plug-in installs:

UseCase III. - Install and use J2EE debugger plug-in to NetBeans + Java Debugger.

J2EE debugger plug-in installs some enhancements to the standard Java Debugger. It does not contain a new debugger engine or language support. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

J2EE debugger plug-in installs:

UseCase IV. - Install and use DBX debugger plug-in to NetBeans.

DBX debugger plug-in installs support for debugging of some new language (CPP) to the NetBeans IDE, and some new debugging engine. But it contains debugger engine for Java debugging too. DBX debugger engine has its own session management (or will have in the next versions). One debugger engine can manage more than one sessions. One engine supports debugging in more than one language.


How to use Java Support APIs?

Models basic aspects of the metadata surrounding Java source files, such as the classpath. More information in the Javadoc.

The API is widely used by all sorts of IDE modules which need to work with Java sources. They can obtain the classpath or boot classpath for a file (if there is one), find out where its source root is, find sources corresponding to bytecode class files, find all sources or classpaths corresponding to open projects, find Javadoc, etc. The SPI is intended mainly for Java platform and library providers, and project type providers, to declare all of this information.


How to use Progress API?

The progress API is good for tracking progress of long lasting tasks in the IDE.

Basic usage

There are 3 types of progress indication:

The default location of the progress indication is the status bar which aggregates all tasks running in the IDE that show progress. However it's possible to exclude the task from the default location and show the progress in one's custom dialog component. In such a case the same task should not appear in the status line component as well.

It's possible to request cancelling the task from status line progress aggregator if the task allows cancelling.

Progress tasks that get started as a result of explicit user action takes precedence in the status line docked component over tasks that are triggered by the system. (say filesystem refresh for example)

The most common usecase of the API looks like this:

ProgressHandle handle = ProgressHandleFactory.creatHandle("My custom task");
...
// we have 100 workunits
// at this point the task appears in status bar.
handle.start(100);
...
handle.progress(10);
...
handle.progress("half way through", 50);
...
handle.progress(99);
// at this point the task is finished and removed from status bar
// it's not realy necessary to count all the way to the limit, finish can be called earlier.
// however it has to be called at the end of the processing.
handle.finish();

Advanced Usage

In case your usage of the API

then you should consider using the aggregating version of APIs which is similar to the simple APIs but has distinctive differences and additions that allow for more complex scenarios.

It allows to compose the progress bar from 1+ independent sources, all sharing proportional piece of the progress bar. Additionally you can monitor the task's overall progress from one central place and possibly add more contributing sources of the progress during processing.

        // let's have a factory for client code that performs some part of the job to be done..
        Lookup.Result res = Lookup.getDefault().lookup(new LookupTemplate(MyWorkerFactory.class));
        Iterator it = res.allInstances().iterator();
        ProgressContributor[] contribs = new ProgressContributor[res.allInstances().size()];
        int i = 0;
        while (it.hasNext()) {
            MyWorkerFactory prov = (MyWorkerFactory)it.next();
            contribs[i] = AggregateProgressFactory.createProgressContributor("Module X contribution");
            MyWorker worker = prov.createWorker(contribs[i]);
            //... snip ... do something with the worker..
            i = i + 1;
        }
        AggregateProgressHandle handle = AggregateProgressFactory.createHandle("My Task", contribs, null, null);
        // non-cancellable and with out output link.
        
        // calling start() at the time when the actual long running task starts processing
        handle.start("here we go");
        // ...snip...
        // now the individual MyWorker instances log their progress.
        // possibly in other threads too..
        // ... snip...
        // 
        if (myConditionThatSpawnsAnotherContributor()) {
            ProgressContributor cont = AggregateProgressFactory.createProgressContributor("Additional exceptional contribution");
            handle.addContributor(cont);
            // ... snip ...
        }
        
        // the task is finished when all the ProgressContributors finish..

How to use Visual Library API?

The Visual Library 2.0 is the next generation of the original Graph Library 1.0. It is designed for a general visualization with a support for graph-oriented modeling. Its focus is to become a part of the NetBeans platform and unify the visualization (UI and API) used in NetBeans-Platform-based applications. See http://graph.netbeans.org/ web-site for details. See documentation for complete set of use-cases and code-snippets.

See documentation for complete set of use-cases.


How to use Auto Update Services?

org.netbeans.api.autoupdate org.netbeans.spi.autoupdate

Browse all available units

Give overview of IDE installation to users, it involve overview of installed modules (grouped together as feature), overview of available updates, overview of available new features. The API can return list of UpdateUnit which describes all instances of unit, e.g. installation in IDE, all its available updates, optionlly its backup instance. UpdateUnit can represent either a feature (e.g. group of modules), a single module or a localization.

Proposed usage of API: Call List<UpdateUnit> UpdateManager.getDefault().getUpdateUnits()

Browse all units by chosen style (e.g. modules, features, localization)

Sometimes there can be a need to get overview of units by chosen style, e.g. feature, module or localization.

Proposed usage of API: Call List<UpdateUnit> UpdateManager.getDefault().getUpdateUnits(UpdateStyle style)

Browse installed modules

When an API client needs to get overview of installed modules.

Proposed usage of API: Call List<UpdateUnit> UpdateManager.getDefault().getUpdateUnits(UpdateStyle style) and filter units which haven't been installed yet.

Search for new functionality

Someone is searching for some functionality which can be installed into IDE. It needs a set of available UpdateUnits which are applicable to active IDE. UpdateManager will search all available UpdateUnit given attribute.

Install new functionality

An client needs to install new functionality into the IDE installation. She knows what unit and what version wants to install. Needs to identify if the functionality is ready to install, resolve its dependencies, identify possible problems and locate other unit what have to be installed together with asked functionality.

Proposed usage of API:

Update of installed unit

A client needs to update some unit of functionality which is already installed. She knows what unit and what update element (by version) wants to install. Needs to identify possible problems with update install, resolve its dependencies, identify possible problems and locate other unit what have to be installed together with asked functionality.

Proposed usage of API: See above Install new functionality

Uninstall functionality

An client needs to uninstall some functionality from IDE installation. She knows what unit wants to uninstall. Needs to identify if the functionality is ready to uninstall, resolve its dependencies, identify possible problems and locate other unit what will be disabled together.

Proposed usage of API:

Switch off functionality

An client needs to switch off (disable) some functionality in IDE installation. Needs to resolve its dependencies, identify possible problems and locate other unit what will be disabled together.

Proposed usage of API:

Switch on functionality

Like Switch off functionality An client needs to switch on (enable) some functionality in IDE installation.

Rollback of previous update

Sometimes an client needs to rollback of installed update of unit to previous version. Needs to resolve its dependencies, identify possible problems and locate other unit what are affected by rollback.

Proposed usage of API: Like above Switch off functionality

Resolve problems what accrued while processing operation

OperationContainer and OperationInfo identifies some problems, i.e. broken dependencies, needs to install more units, the operation causes disable some other modules and so on. The client can use this information to consult these with end-user.

Make IDE up-to-date

Sometimes need to make whole IDE installation up-to-date. Find all available updates of installed units and install the latest available version. It is covered by previous use-cases.

Get all subscriptions to Update Center

Show me all registered subscriptions to Update Center, e.g. get me list of UpdateUnitProvider. Proposed usage of API: Call UpdateUnitProviderFactory.getUpdateUnitProviders()

Subscribe new Update Center

If there is a new Update Center which is not already subscribed into IDE user wants to subscribe new one Update Center which will be connected from that time in periodically checks. There should be a factory where subscribe new one Update Center, known types of Update Center have own factory method. Proposed usage of API: Simply call UpdateUnitProviderFactory.create() which creates and registered new one subscription in the system and will be used from that time in the future.

Customization of Update Center subscription

An user wants to enable or disable of Update Center subscription. Proposed usage of API: Simply call UpdateUnitProviderFactory.setEnable(UpdateUnitProvider, boolean).

Unsubscribe of some Update Center

Simple unsubscribe a chosen Update Center from the system. Need to know of chosen Update Provider Id. This Update Center won't be checked anymore. Proposed usage of API: Simply call UpdateUnitProviderFactory.remove(Id).

Refresh content of subscribed Update Center

The content of Update Provider is cached and the system works across there caches. There is a cache per each Update Center subscription. The caches are refreshed periodically by the system. But, sometime an user wants to call refresh manually. Proposed usage of API: Simply call UpdateUnitProvider.refresh().

Specify the cluster where to install

TBD

Get all installed files of given unit

TBD


How to use Database Explorer?

This project provides access to objects defined in the Database Explorer. Documentation is available in the Javadoc.

Registering JDBC drivers

An external module can register JDBC drivers. A typical example is a module which provides integration with a database server. In this case the module contains the JDBC driver for that database server and uses the Database Explorer API to add it do the Database Explorer.

Another client of this API could be a module providing integration with a J2EE application server. Sometimes a J2EE application server bundles a database server for improving the out-of-the-box experience. When the server is registered in the IDE the JDBC drivers for the bundled database server are added to the Database Explorer.

The drivers are registered by making calls on JDBCDriverManager or by registering an XML file which describes the driver in the module layer. The XML file is described by the JDBC Driver DTD. An example of a registration file describing the JDBC driver for PostgreSQL follows:

    <?xml version='1.0'?>
    <!DOCTYPE driver PUBLIC '-//NetBeans//DTD JDBC Driver 1.0//EN' 'http://www.netbeans.org/dtds/jdbc-driver-1_0.dtd'>
    <driver>
      <name value='postgresql-7'/>
      <display-name value='PostgreSQL (v7.0 and later)'/>
      <class value='org.postgresql.Driver'/>
      <urls>
        <url value='file:/folder1/folder2/drivers/pg74.1jdbc3.jar'/>
      </urls>
    </driver>
   

This file should be registered in the Databases/JDBCDrivers folder of the module layer. To addres a bundled JAR inside the IDE the nbinst protocol can be used in the URLs: nbinst:/modules/ext/bundled-driver.jar.

Retrieving the list of JDBC drivers

When creating a new connection the JDBC driver which it should use can be specified. A list of all the registered JDBC drivers can be retrieved using JDBCDriverManager.getDrivers().

Registering database runtimes

An external module can register new database runtimes. A database runtime is an abstraction of a database server instance (usually bundled with the IDE, an integration module or with a J2EE server). It allows a database server instance to be started and stopped when a connection to this instance is made in the IDE. Database runtimes are represented by the DatabaseRuntime SPI interface and are registered in the Databases/Runtimes of the module layer.

Creating database connections

A module can create new database connections (for example to a bundled database). New connections can be added by calling DatabaseConnection.create() to create a new DatabaseConnection instance and then ConnectionManager.addConnection() to add the connection to the Database Explorer.

New connections can also be added by registering them in the module layer. The format of the registration file is described by the Database Connection DTD. An example of a registration file describing a connection to a PostgreSQL database follows:

    <?xml version='1.0'?>
    <!DOCTYPE connection PUBLIC '-//NetBeans//DTD Database Connection 1.1//EN' 'http://www.netbeans.org/dtds/connection-1_1.dtd'>
    <connection>
      <driver-class value='org.postgresql.Driver'/>
      <driver-name value='postgres-7'/>
      <database-url value='jdbc:postgresql:test'/>
      <schema value='public'/>
      <user value='test'/>
      <password value='cGFzc3dvcmQ='/>
    </connection>
   

This file should be registered in the Databases/Connections folder of the module layer.

The password element is optional, but if it is included, its value must be the Base64 encoding of the UTF-8 representation of the password. Note that the UTF-8 representation of passwords composed entirely of ASCII characters is the same as their ASCII representation, so for such passwords all that needs to be done is to convert them to Base64.

Base64 encoding serves as a simple scrambling to prevent accidental revelation of the password. It is not indended to offer any real security. You can protect the password by assigning appropriate file protections to the connection XML file.

Retrieving and displaying the list of database connections

Sometimes the list of connections needs to be displayed somewhere else in the IDE than the Runtime tab. A typical example is the SQL Editor, which allows the user to select the database connection which the SQL statement will be executed against in a combo box in the editor toolbar. The list of connections can be obtained by calling ConnectionManager.getConnections(), which returns an array of DatabaseConnection instances.

The client usually needs to show the display name of the connection. The display name can be retrieved using the DatabaseConnection.getDisplayName() method.

Retrieving the properties of database connections

Sometimes a client needs to retrieve the connection properties, such as the driver class. An example could be a module for a J2EE server creating a connection pool. The properties can be retrieved using the getDriverClass(), getDatabaseURL(), getSchema(), getUser() and getPassword() methods of the DatabaseConnection class.

Showing the New Database Connection dialog

Usually when displaying a list of connections (usually in a combo box), the last item is "New Connection", which displays the standard New Database Connection dialog of the Database Explorer. This can be achieved by calling one of the ConnectionManager.showAddConnectionDialog() methods.

Connecting to a database

A component which provides database functionality (such as the SQL Editor) will need to connect to a database. This can be achieved using the DatabaseConnection.showConnectionDialog() method and the java.sql.Connection instance can be retrieved using the getJDBCConnection() method.

Displaying the database connections in the UI

A component which provides database functionality (such as the SQL Editor or a module providing support for data sources) will need to let the user select the a database connection, usually through a combo box. This can be achieved using the DatabaseExplorerUIs.connect() method. The JComboBox passed to the method will be filled with the list of connections as returned by ConnectionManager.getConnections(), followed by a separator and a New Database Connection item which will display the dialog for adding a new database connection when selected.

Drag and drop support for database objects

A component might need to allow database tables from the Database Explorer to be dragged to a visual editor. An API is provided in DatabaseMetaDataTransfer containing DataFlavors for database objects and nested classes encapsulating those database objects during a drag and drop transfer.

Get support for working with SQL identifiers

A component might need support for working with SQL identifiers. In particular, it's important to know when to quote a SQL identifier. The SQLIdentifiers.Quoter class is provided for this.


How to use Diff?

The diff module provides the ability to visualize differencies between source files. It also has a graphical conflicts resolver tool and built-in patch algorithm. List of the main features: Simple APIs, that provide access to registered diff and merge engines and visualizers. Built in and external diff engines defined. Graphical diff vizualizer and conflicts resolver. Extensible with additional diff and merge engines and vizualizers. Patch algorithm implemented for UNIX-style, unidiff and contextual diff formats.

Show a diff from action

From an action or wherever you like you can call this:

public void diff(final StreamSource local, final StreamSource remote){
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            try {
                DiffView view = Diff.getDefault().createDiff(local, remote);
                showDiff(view);
            } catch (IOException ex) {
                Logger.getLogger(ThisClass.class.getName()).throwing(ex);
            }
        }
    });
}
public void showDiff(final DiffView view){
    SwingUtilities.invokeLater(new Runnable() {
        public void run() {
            //create our panel with our view
            //right now I am just going to use the diff component 
            // instead of a panel
            //create a topcomponent with our panel
            DiffTopComponent tc = new DiffTopComponent(view);
            tc.setName("MY_DIFF");
            tc.setDisplayName("Some display name");
            tc.open();
            tc.requestActive();
        }
    });
}

Embeded into TopComponent

Here is a top component to display it:

public class DiffTopComponent extends TopComponent {
?? ?? /** Creates a new instance of DiffTopComponent */
?? ?? public DiffTopComponent(Component diffPanel) {
?? ?? ?? ?? setLayout(new BorderLayout());
?? ?? ?? ?? add(diffPanel, BorderLayout.CENTER);
        getAccessibleContext().setAccessibleName(
            NbBundle.getMessage(DiffTopComponent.class, 
            "ACSN_Diff_Top_Component")); // NOI18N
?? ?? ?? ?? 
        getAccessibleContext().setAccessibleDescription(
            NbBundle.getMessage(DiffTopComponent.class, 
            "ACSD_Diff_Top_Component")); // NOI18N
?? ?? }
?? ??
?? ?? public DiffTopComponent(DiffView view) {
?? ?? ?? ?? this(view.getComponent());
?? ?? }
?? ?? public int getPersistenceType(){
?? ?? ?? ?? return TopComponent.PERSISTENCE_NEVER;
?? ?? }
?? ?? protected String preferredID(){
?? ?? ?? ?? return "DiffTopComponent"; ?? ??//NOI18N
?? ?? }
?? ?? public HelpCtx getHelpCtx() {
?? ?? ?? ?? return new HelpCtx(getClass());
?? ?? }
}

How to use Editor Code Templates?

Code Templates allow to paste various code snippets by using parametrized text. The parameters of the same name will share the same default value and if that value gets changed by user's typing the new value gets replicated into all the parameter's occurrences. Code Templates replace the original abbreviations functionality. Code template's example for (Iterator ${iterator} = ${collection instanceof="java.util.Collection"}.iterator(); ${iterator}.hasNext();) { ${cursor}${iterator}.next();" } Each parameter can have additional hints of what values can be assigned to it. The hint has a form ${param hint=value} or just ${param hint} which translates to ${param hint="true"} If necessary the value of the hint can be enclosed in quotes to allow to write whitespace or { or } into the value. The quote can be written by using \". Reserved parameter names ${cursor} defines position where the caret will be located after the editing of the code template default values will finish. Reserved hint names ${param editable=false} can be used to make the parameter to be skipped from user's editing. This may be useful e.g. with using java-specific type hint (described below). Java: ${ind index} defines that the default value of the parameter should be an unused variable in the given context named i. If i is already used then j is attempted or then k etc. until z. Then i0, i1 etc. are attempted. ${param type="java.util.Collection"} defines java type that the parameter must be instance of. Besides class names there can be array e.g. String[] or generics java.util.List<String> ${param array} defines parameter of array type (including arrays of primitive data types). ${param type="java.util.Iterator"} defines that the parameter has the given java type. The template processing infrastructure will use short name Iterator and import java.util.Iterator.

Code Template Parameters

One of the main benefits of the code templates is their parametrization which allows to substitute the default values for the parameters before the final insertion and it also allows the user to modify these default values explicitly after the code template gets inserted into the document.
The parameters are marked in the code template's text by ${...}.
Parameters of the same name benefit from automatic replication. Once the template gets pasted into the document all the parameter's occurrences get replaced by parameter's default value.
The first parameter's occurrence gets selected.
The user can now replace the default value. If the user does so the new value gets replicated to all the other occurrences of this parameter automatically.

Mime-type specific operation

Each code template needs to find the default values for its parameters before it gets inserted into the text.
Sometimes it's enough to just specify the default value in the template's text but usually the default value gets determined from the context of insertion.
There is an intent to create a mime-type specific code template processor that would be registered per mime-type. There could be even more than one such processors processing the template in a specific order.

Parameter hints

Besides parameter's name the template processors may need additional hints of how to find a default value for the parameter.
For example java code template's parameter may be an index parameter which means that the infrastructure should fill in a fresh index variable e.g. i.
Or the parameter can only be of a certain java type such as in the case of iterating through a collection the type must be subtype of java.util.Collection.
These requirements could be specified as additional hints to the parameters e.g. ${i index} or ${c instanceof=java.util.Collection}.
The hints allow string literals to support arbitrary explicit default values specifications e.g. ${x default="Hello world"}.
The '{' and '}' have no special meaning inside the string literal.
The '"' char is allowed to be used by escaping ${x default="\"quoted string\""}.

Temporary Code Templates

The Code Completion functionality allows to build temporary Code Templates functionality if it could build a temporary template for completing of the method parameters. The parameters could be completed one by one by tabbing and the Code Templates framework would fill in proper default values just like it does for regular templates.
    JTextComponent pane = ...
    String tempCodeTemplateText = ...
    CodeTemplate ct = CodeTemplateManager.get(pane.getDocument()).createTemporary(tempCodeTemplateText);
    ct.insert(pane);

Insert Text Building and Updating

The parametrized text of the code template first gets parsed and the parameters get their default values which by default are the names of the parameters. The code template processor are then called to update this default value.
The new java infrastructure being developed would benefit from the possibility to obtain the full string containing the skeleton of the code template (without parameters) with the present default values. It can take that string and locally parse it to find out types of local variables used in the particular template and fill in dependent variable types.

Parameter Editability

Certain part of the code template may change text but it should not be edited by the user. For example when iterating over collection given as a parameter the collection may be generics-ed by additional type. The iterator's variable type then also needs to generics-ed with the same type.
The iterator's type parameter should not be editable because this operation may be done automatically by the java code template processor.
There should be a hint editable having true/false.

How to use Editor Code Completion?

Code Completion provides users with a list of suggested completions for partially typed texts in the editor and various dialog input fields. The Code Completion module was created to replace the original legacy editor code completion which lacked several key requirements: Support for multiple independent code completion content providers. Implied requirement for ordering and prioritization of the completion items. Direct support for asynchronous completion result computation. Missing separation to the API and SPI and implementation parts.

API

Show or hide completion window

The API is small and it only allows to explicitly show or hide the completion window.
It's being used by code templates that need to explicitly show the code completion window when tabbing to a particular parameter.
There may be certain actions that want to ensure that the code completion is hidden at the time when they are invoked. For example the actions pasting the content of the completion item into the document.

SPI

Provide completion content by independent providers

Completion infrastructure needs to obtain the results that are then displayed in the completion window.
There are three types of displayed results related to the current caret offset:

For the purpose of obtaining these completion results CompletionProvider exists.
There may be an arbitrary number of independent completion providers for a single completion popup window.
The completion providers are registered through the xml layer into Editors/<mime-type>/CompletionProviders. Once the document with the particular mime-type gets loaded the corresponding completion providers will get instantiated and used.

Threading:
The code completion's infrastructure invokes the requests for the completion results in the AWT thread.
Therefore all the methods of the completion providers are invoked in AWT thread but they may reschedule their processing into other threads.

Provide completion results computed asynchronously

The completion provider creates a task that computes the resulting data that will then be displayed by the code completion infrastructure.
The task creation and computation are called synchronously from the AWT event dispatch thread.
However there can be potentially long-running tasks (e.g. working with MDR) that are not desirable to be run in AWT thread.
Therefore the completion infrastructure provides a listener to which the completion task notifies the results.
The support class AsyncCompletionTask allows to post the task computation into RequestProcessor.

Provide list of completion items fulfilling various requirements

The completion task computes a collection of completion items which are then collected by the completion infrastructure and displayed.
Displaying. Each completion item must be able to display itself in a JList.
Sorting. The completion items may come from different completion providers and they must be sorted before displaying. The sort order should not only be alphabetical but it should also allow a prioritization of the items according to their importance in the given context.
Actions. The interaction of the user with the completion item is done by interacting with item's input map and action map.
Documentation. The item may want to display additional detailed information in a documentation popup window.


How to use Editor Error Stripe?

The Error Stripe shows an overview of important information of an edited source code. It shows this information for the whole source code (regardless of its size).

Augment Annotations to be shown in the Error Stripe

Use the OpenIDE Text API.

Provide Up-to-date Status for the Error Stripe

A module in the IDE has information whether data shown in the Error Stripe is up-to-date or not. The Error Stripe may change the appearance according to this knowledge.

Implement the UpToDateStatusProvider that provides up-to-date status. Be sure that it fires PropertyChangeEvent when this status is changed.

Implement the UpToDateStatusProviderFactory that creates an instance of your UpToDateStatusProvider for a given JTextComponent and install it as described here.


How to use Editor Code Folding?

The Code Folding is part of the editor module functionality and it's responsible for hiding of the portions of the code that are less important for the user at the given time.

API Use Cases

Exploring of the Folds

The code folding structure (fold hierarchy) relates to javax.swing.JTextComponent instance in one-to-one relationship.
To find the code folding hierarchy instance for the given non-null text component the following code snippet can be used:

    JTextComponent editorComponent = ...
    FoldHierarchy hierarchy = FoldHierarchy.get(editorComponent);

Explore the Folds Hierarchy

The tree-based hierarchy has one non-removable and non-collapsable root fold that covers the whole document. It can be obtained by

    FoldHierarchy foldHierarchy = ...
    Fold rootFold = hierarchy.getRootFold();

The children folds of the root fold (or children folds) can be obtained by

    // the hierarchy must be locked prior exploration or manipulation
    hierarchy.lock();
    try {
        Fold rootFold = ...
        int foldCount = rootFold.getFoldCount();
        for (int i = 0; i < foldCount; i++) {
            Fold childFold = rootFold.getFold(i);
        }
    } finally {
        hierarchy.unlock();
    }

Index of the child in its parent can be found by

    hierarchy.lock();
    try {
        Fold rootFold = ...
        int foldIndex = rootFold.getFoldIndex(childFold);
    } finally {
        hierarchy.unlock();
    }

Collapse Nearest Fold

In the given fold hierarchy find the nearest fold right at or after the given offset and collapse it.

    hierarchy.lock();
    try {
        Fold fold = FoldUtilities.findNearestFold(hierarchy, offset);
        hierarchy.collapse(fold);
    } finally {
        hierarchy.unlock();
    }

Expand All Folds

In the given fold hierarchy expand all folds that are currently collapsed.

    FoldUtilities.expand(hierarchy, null);

Collapse All Folds of Certain Type

In the given fold hierarchy collapse all e.g. javadoc folds that are currently collapsed.
The example can be generalized to any fold type.

    FoldUtilities.collapse(hierarchy, JAVADOC_FOLD_TYPE);

Force Fold Expansion for Caret Moving Into Collapsed Fold

In the given fold hierarchy expand the fold into which the caret is going to be moved by Caret.setDot(offset).
The hierarchy must be locked and this example assumes that the underlying document is already read-locked e.g. by Document.render().

    FoldHierarchy hierarchy = FoldHierarchy.get(caretComponent);
    hierarchy.lock();
    try {
        Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset);
        if (collapsed != null && collapsed.getStartOffset() < offset &&
            collapsed.getEndOffset() > offset) {
            hierarchy.expand(collapsed);
        }
    } finally {
        hierarchy.unlock();
    }

Start Listening on Fold Hierarchy Changes

In the given fold hierarchy start to listen on all changes done in the hierarchy.
This is actually used e.g. in the Editor's View Hierarchy that needs to refresh views based on the fold changes.

    hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
            // Hierarchy does not need to be locked here
            //
            // evt.getAffectedStartOffset() and getAffectedEndOffset()
            // give text area affected by the fold changes in the event
        }
    });

Inspect Collapsed Folds in Affected Area

Listen on the hierarchy changes and refresh the views in the text area affected by the fold change.
Inspect the collapsed folds in the affected area because special views need to be created for the collapsed folds.
The actual code in the View Hierarchy is somewhat different but the one given here is more descriptive.

    hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
        public void foldHierarchyChanged(FoldHierarchyEvent evt) {
            for (Iterator collapsedFoldIterator
                = FoldUtilities.collapsedFoldIterator(hierarchy,
                    evt.getAffectedStartOffset(),
                    evt.getAffectedEndOffset()
                );
                it.hasNext();
            ) {
                Fold collapsedFold = (Fold)it.next();
                // Create special view for the collapsedFold
            }
        }
    });

SPI Use Cases

Create a New Fold Manager

Manipulation of the folds is designed to be done by fold managers.
Those classes implement FoldManager interface in the SPI.
At initialization time they are given instance of FoldOperation through which they can create, add or remove the fold instances.

To create and use a new FoldManager instance it's necessary to

Create a New Fold by Fold Manager

Create a new fold and add it to the hierarchy. The operation is performed by the fold manager either at initialization phase (in the initFolds() which gets called automatically by the infrastructure) or at any other time when the fold manager's operation gets invoked (usually by a listener that the fold manager attaches to be notified about changes that can cause the folds structure to be changed - e.g. a parsing listener for java folds).

Operations that manipulate the hierarchy are done in terms of a valid transaction over the fold hierarchy.
Transactions allow to fire the collected changes as a single FoldHierarchyEvent at the time when they are committed.

    // In the FoldManager's context
    FoldOperation operation = getOperation();
    FoldHierarchyTransaction transaction = operation.openTransaction();
    try {
        Fold fold = operation.createFold(...);
        operation.addFoldToHierarchy(fold, transaction);
    } finally {
        transaction.commit();
    }

Remove Fold from Hierarchy by Fold Manager

Remove the existing fold from the hierarchy

    // In the FoldManager's context
    FoldOperation operation = getOperation();
    FoldHierarchyTransaction transaction = operation.openTransaction();
    try {
        Fold fold = ...
        operation.removeFoldFromHierarchy(fold, transaction);
    } finally {
        transaction.commit();
    }

How to use MIME Lookup API?

Each editor provides an EditorKit which controls the policy of specific MIME content type. The policy of content type should be easily registered and found via some lookup mechanism, that will provide convenient way of using it either for kit provider or base editor infrastructure. In addition to this, the policy can be inherited, (e.g. in case of embeded kits like JSP) and the content types need to be merged in this case. MIME Lookup API should provide all mentioned requierements via easy lookup query, so content type policy user need not to solve this searching and merging on its own side.

Per mime-type operation

Operation of the editor module must be parametrized by the type of the file being edited. In the past the operation was parametrized by the class of the editor kit but that did not show up as being useful enough.
It is more practical to use a string-based parametrization concretely the mime-type. Anyone can then easily register an additional functionality for the editor because it's just enough to know the right mime-type and the type of the functionality class to be implemented and the xml layer folder where the class should be registered.

Provide list of instances as lookup result

On the modules' implementation side the registered functionality must be retrieved somehow. It's necessary to instantiate the registered objects and react to module enabling/disabling which can affect validity of the registered objects.
As the most convenient solution appears to use org.openide.util.Lookup allowing to provide the registered instances as a Lookup.Result allowing to listen for changes (e.g. caused by the module enabling/disabling).
This resulted into creation of class MimeLookup extends Lookup containing static MimeLookup getMimeLookup(String mimeType).

Nested mime-types

On the lexical level the document can contain nested languages.
For example JSP document can contain pieces of java code which can further contain javadoc comment tokens with nested javadoc language.
The nested languages should allow for special settings such as fonts and colors of nested syntax coloring but even things like actions that would be active in the nested document section.
This resulted into creation of static Lookup getLookup(MimePath mimePath) method in MimeLookup.

Known clients summary

Fold Manager Factories
The editor/fold module expects to find the registered fold manager factories (org.netbeans.spi.editor.fold.FoldManagerFactory classes).

Completion Providers
The editor/completion module expects to find the registered completion providers (org.netbeans.spi.editor.completion.CompletionProvider classes).

Editor Context Menu Actions
The editor module expects to find the registered popup menu actions (javax.swing.Action classes or names of actions (i.e. value of Action.NAME attribute) present in editor kit e.g. "goto-source").

Side Bars
The editor/lib module expects to find factories for components to be placed on the sides of the editor component (org.netbeans.editor.SideBarFactory classes).

Hyperlink Providers
The editor/lib module expects to find hyperlink providers that allow connecting an open document with some other documents (org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider classes).

Code Template Processors
The editor/codetemplates module expects to find factories for code template processors (org.netbeans.lib.editor.codetemplates.spi.CodeTemplateProcessorFactory classes).

Hints Providers
The editor/hints module expects to find editor hints providers (org.netbeans.modules.editor.hints.spi.HintsProvider classes).


API Use Cases

Find class instances for the given mime-type

An API method

MimeLookup lookup = MimeLookup.getMimeLookup("text/x-java");

can be used for getting the mime specific lookup. Having this we can lookup class or template:

Object obj = lookup.lookup(LookedUpClass.class);

or

Lookup.Result result = lookup.lookup(new Lookup.Template(LookedUpClass.class));

Getting embeded mime-type specific Lookup

As an example a jsp scriptlet is used. Scriptlet in fact consists of parent "text/x-jsp" mime-type and embeded "text/x-java" mime-type. To obtain a scriptlet lookup firstly we need to get a MimePath and then get appropriate lookup:

    MimePath scriptletPath = MimePath.parse("text/x-jsp/text/x-java");
    Lookup lookup = MimeLookup.getLookup(scriptletPath);

SPI Use Cases

Providing implemented MimeLookupInitializer

It is the general way of adding mime specific object into the MimeLookup. Implementation of MimeLookupInitializer should be created and registered to default lookup via META-INF/services registration. For details, please look at the simplified TestMimeLookupInitializer in mimelookup/test/unit or LayerMimeLookupInitializer. Usage of MimeLookupInitializer is deprecated, please use MimeDataProvider instead in similar way

How to use Editor Settings?

The legacy settings system in the editor module is complicated, error prone and hard to use. It'd been created spontaneously over the years to support immediate needs at that time without paying enough attention to extensibility and interoperability. Historically any module providing editor settings needed to depend on the whole editor module. The main purpose of this project is to define API for editor settings, that is lightweight and easily extensible. The API relies on MimeLookup to provide a way of registering and looking up settings. The aim is NOT to provide an implementation of a storage for editor settings, but to define an interface between this storage and clients like <mime-type> editors, externaleditor, etc.

Accessing settings

All editor settings are mime type specific and therefore should be retrieved using MimeLookup. The following example shows how to retrieve the FontColorSettings for java files and how to get AttributeSet with coloring attributes for a particular coloring (i.e. in this case the colors used for highlighting selected text)

    MimePath mimePath = MimePath.parse("text/x-java");
    FontColorSettings fcs  = (FontColorSettings) MimeLookup.getLookup(mimePath).lookup(FontColorSettings.class);
    AttributeSet coloring = fcs.getFontColors(FontColorNames.SELECTION_COLORING);

Listening on changes

If clients need to react on changes in editor settings they can attach LookupListener to the LookupResult they got for their particular settings class from MimeLookup. The following example shows how to do it.

    MimePath mimePath = MimePath.parse("text/x-java");
    Lookup lookup = MimeLookup.getLookup(mimePath);
    LookupResult result = lookup.lookup(new Lookup.Template(FontColorSettings.class));
    
    result.addLookupListener(new LookupListener() {
        public void resultChanged(LookupEvent ev) {
            //... the client's response to the settings change
        }
    });

The FontColorSettings class implementor is responsible and will create a new instance of FontColorSettings whenever some coloring will change. This new instance will be placed in MimeLookup replacing the old one.


How to use Java Platform?

Many Java-based project types need to be able to configure the version and location of Java to be used when building and running the project. This API/SPI permits these platforms to be registered and queried, and any customizations made in an appropriate GUI and persisted to disk.

The API can be used by any code wishing to know the list of installed platforms and information about each one; typically this would be used by project type providers to implement a customizer dialog. The SPI is intended to be implemented by a few modules supply support for locating and introspecting installed platforms, for example a JDK setup wizard.


How to use Java Project Support?

Provides support infrastructure for projects working with the Java language.

Project type providers wishing to show Java packages in their logical views can use this SPI. Templates which are Java-centric can use it. Projects which wish to implement queries from the Java Support APIs can place implementations in their lookup and these will be delegated to automatically.


How to use Lexer?

Lexer module provides token lists for various text inputs. Token lists can either be flat or they can form tree token hierarchies if any language embedding is present. Tokens

API Usecases

Obtaining of token hierarchy for various inputs.

The TokenHierarchy is an entry point into Lexer API and it represents the given input in terms of tokens.
    String text = "public void m() { }";
    TokenHierarchy hi = TokenHierarchy.create(text, JavaLanguage.description());

Token hierarchy for swing documents must be operated under read/write document's lock.
    document.readLock();
    try {
        TokenHierarchy hi = TokenHierarchy.get(document);
        ... // explore tokens etc.
    } finally {
        document.readUnlock();
    }

Obtaining and iterating token sequence over particular swing document from the given offset.

The tokens cover the whole document and it's possible to iterate either forward or backward.
Each token can contain language embedding that can also be explored by the token sequence. The language embedding covers the whole text of the token (there can be few characters skipped at the begining an end of the branch token).
    document.readLock();
    try {
        TokenHierarchy hi = TokenHierarchy.get(document);
        TokenSequence ts = hi.tokenSequence();
        // If necessary move ts to the requested offset
        ts.move(offset);
        while (ts.moveNext()) {
            Token t = ts.token();
            if (t.id() == ...) { ... }
            if (TokenUtilities.equals(t.text(), "mytext")) { ... }
            if (ts.offset() == ...) { ... }

            // Possibly retrieve embedded token sequence
            TokenSequence embedded = ts.embedded();
            if (embedded != null) { // Token has a valid language embedding
                ...
            }
        }
    } finally {
        document.readUnlock();
    }

Typical clients:

Using language path of the token sequence

For the given token sequence the client may check whether it's a top level token sequence in the token hierarchy or whether it's embedded at which level it's embedded and what are the parent languages.
Each token can contain language embedding that can also be explored by the token sequence. The language embedding covers the whole text of the token (there can be few characters skipped at the begining an end of the branch token).
    TokenSequence ts = ...
    LanguagePath lp = ts.languagePath();
    if (lp.size() > 1) { ... } // This is embedded token sequence
    if (lp.topLanguage() == JavaLanguage.description()) { ... } // top-level language of the token hierarchy
    String mimePath = lp.mimePath();
    Object setting-value = some-settings.getSetting(mimePath, setting-name);

Extra information about the input

The InputAttributes class may carry extra information about the text input on which the token hierarchy is being created. For example there can be information about the version of the language that the input represents and the lexer may be written to recognize multiple versions of the language. It should suffice to do the versioning through a simple integer:
public class MyLexer implements Lexer<MyTokenId> {
    
    private final int version;
    
    ...
    
    public MyLexer(LexerInput input, TokenFactory<MyTokenId> tokenFactory, Object state,
    LanguagePath languagePath, InputAttributes inputAttributes) {
        ...
        
        Integer ver = (inputAttributes != null)
                ? (Integer)inputAttributes.getValue(languagePath, "version")
                : null;
        this.version = (ver != null) ? ver.intValue() : 1; // Use version 1 if not specified explicitly
    }
    
    public Token<MyTokenId> nextToken() {
        ...
        if (recognized-assert-keyword) {
            return (version >= 4) { // "assert" recognized as keyword since version 4
                ? keyword(MyTokenId.ASSERT)
                : identifier();
        }
        ...
    }
    ...
}
The client will then use the following code:
    InputAttributes attrs = new InputAttributes();
    // The "true" means global value i.e. for any occurrence of the MyLanguage including embeddings
    attrs.setValue(MyLanguage.description(), "version", Integer.valueOf(3), true);
    TokenHierarchy hi = TokenHierarchy.create(text, false, SimpleLanguage.description(), null, attrs);
    ...

Filtering out unnecessary tokens

Filtering is only possible for immutable inputs (e.g. String or Reader).
    Set<MyTokenId> skipIds = EnumSet.of(MyTokenId.COMMENT, MyTokenId.WHITESPACE);
    TokenHierarchy tokenHierarchy = TokenHierarchy.create(inputText, false,
        MyLanguage.description(), skipIds, null);
    ...

Typical clients:

SPI Usecases

Providing language description and lexer.

Token ids should be defined as enums. For example org.netbeans.lib.lexer.test.simple.SimpleTokenId can be copied or the following example from org.netbeans.modules.lexer.editorbridge.calc.lang.CalcTokenId.
The static language() method returns the language describing the token ids.
public enum CalcTokenId implements TokenId {

    WHITESPACE(null, "whitespace"),
    SL_COMMENT(null, "comment"),
    ML_COMMENT(null, "comment"),
    E("e", "keyword"),
    PI("pi", "keyword"),
    IDENTIFIER(null, null),
    INT_LITERAL(null, "number"),
    FLOAT_LITERAL(null, "number"),
    PLUS("+", "operator"),
    MINUS("-", "operator"),
    STAR("*", "operator"),
    SLASH("/", "operator"),
    LPAREN("(", "separator"),
    RPAREN(")", "separator"),
    ERROR(null, "error"),
    ML_COMMENT_INCOMPLETE(null, "comment");


    private final String fixedText;

    private final String primaryCategory;

    private CalcTokenId(String fixedText, String primaryCategory) {
        this.fixedText = fixedText;
        this.primaryCategory = primaryCategory;
    }
    
    public String fixedText() {
        return fixedText;
    }

    public String primaryCategory() {
        return primaryCategory;
    }

    private static final Language<CalcTokenId> language = new LanguageHierarchy<CalcTokenId>() {
        @Override
        protected Collection<CalcTokenId> createTokenIds() {
            return EnumSet.allOf(CalcTokenId.class);
        }
        
        @Override
        protected Map<String,Collection<CalcTokenId>> createTokenCategories() {
            Map<String,Collection<CalcTokenId>> cats = new HashMap<String,Collection<CalcTokenId>>();

            // Incomplete literals 
            cats.put("incomplete", EnumSet.of(CalcTokenId.ML_COMMENT_INCOMPLETE));
            // Additional literals being a lexical error
            cats.put("error", EnumSet.of(CalcTokenId.ML_COMMENT_INCOMPLETE));
            
            return cats;
        }

        @Override
        protected Lexer<CalcTokenId> createLexer(LexerRestartInfo<CalcTokenId> info) {
            return new CalcLexer(info);
        }

        @Override
        protected String mimeType() {
            return "text/x-calc";
        }
        
    }.language();

    public static final Language<CalcTokenId> language() {
        return language;
    }

}
Note that it is not needed to publish the underlying LanguageHierarchy extension.
Lexer example:
public final class CalcLexer implements Lexer<CalcTokenId> {

    private static final int EOF = LexerInput.EOF;

    private static final Map<String,CalcTokenId> keywords = new HashMap<String,CalcTokenId>();
    static {
        keywords.put(CalcTokenId.E.fixedText(), CalcTokenId.E);
        keywords.put(CalcTokenId.PI.fixedText(), CalcTokenId.PI);
    }
    
    private LexerInput input;
    
    private TokenFactory<CalcTokenId> tokenFactory;

    CalcLexer(LexerRestartInfo<CalcTokenId> info) {
        this.input = info.input();
        this.tokenFactory = info.tokenFactory();
        assert (info.state() == null); // passed argument always null
    }
    
    public Token<CalcTokenId> nextToken() {
        while (true) {
            int ch = input.read();
            switch (ch) {
                case '+':
                    return token(CalcTokenId.PLUS);

                case '-':
                    return token(CalcTokenId.MINUS);

                case '*':
                    return token(CalcTokenId.STAR);

                case '/':
                    switch (input.read()) {
                        case '/': // in single-line comment
                            while (true)
                                switch (input.read()) {
                                    case '\r': input.consumeNewline();
                                    case '\n':
                                    case EOF:
                                        return token(CalcTokenId.SL_COMMENT);
                                }
                        case '*': // in multi-line comment
                            while (true) {
                                ch = input.read();
                                while (ch == '*') {
                                    ch = input.read();
                                    if (ch == '/')
                                        return token(CalcTokenId.ML_COMMENT);
                                    else if (ch == EOF)
                                        return token(CalcTokenId.ML_COMMENT_INCOMPLETE);
                                }
                                if (ch == EOF)
                                    return token(CalcTokenId.ML_COMMENT_INCOMPLETE);
                            }
                    }
                    input.backup(1);
                    return token(CalcTokenId.SLASH);

                case '(':
                    return token(CalcTokenId.LPAREN);

                case ')':
                    return token(CalcTokenId.RPAREN);

                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                case '.':
                    return finishIntOrFloatLiteral(ch);

                case EOF:
                    return null;

                default:
                    if (Character.isWhitespace((char)ch)) {
                        ch = input.read();
                        while (ch != EOF && Character.isWhitespace((char)ch)) {
                            ch = input.read();
                        }
                        input.backup(1);
                        return token(CalcTokenId.WHITESPACE);
                    }

                    if (Character.isLetter((char)ch)) { // identifier or keyword
                        while (true) {
                            if (ch == EOF || !Character.isLetter((char)ch)) {
                                input.backup(1); // backup the extra char (or EOF)
                                // Check for keywords
                                CalcTokenId id = keywords.get(input.readText());
                                if (id == null) {
                                    id = CalcTokenId.IDENTIFIER;
                                }
                                return token(id);
                            }
                            ch = input.read(); // read next char
                        }
                    }

                    return token(CalcTokenId.ERROR);
            }
        }
    }

    public Object state() {
        return null;
    }

    private Token<CalcTokenId> finishIntOrFloatLiteral(int ch) {
        boolean floatLiteral = false;
        boolean inExponent = false;
        while (true) {
            switch (ch) {
                case '.':
                    if (floatLiteral) {
                        return token(CalcTokenId.FLOAT_LITERAL);
                    } else {
                        floatLiteral = true;
                    }
                    break;
                case '0': case '1': case '2': case '3': case '4':
                case '5': case '6': case '7': case '8': case '9':
                    break;
                case 'e': case 'E': // exponent part
                    if (inExponent) {
                        return token(CalcTokenId.FLOAT_LITERAL);
                    } else {
                        floatLiteral = true;
                        inExponent = true;
                    }
                    break;
                default:
                    input.backup(1);
                    return token(floatLiteral ? CalcTokenId.FLOAT_LITERAL
                            : CalcTokenId.INT_LITERAL);
            }
            ch = input.read();
        }
    }
    
    private Token<CalcTokenId> token(CalcTokenId id) {
        return (id.fixedText() != null)
            ? tokenFactory.getFlyweightToken(id, id.fixedText())
            : tokenFactory.createToken(id);
    }

}

The classes containing token ids and the language description should be part of an API. The lexer should only be part of the implementation.

Providing language embedding.

The embedding may be provided statically in the LanguageHierarchy.embedding() see e.g. org.netbeans.lib.lexer.test.simple.SimpleLanguage.

Or it may be provided dynamically through the xml layer by using file named with the token-id's name with ".instance" suffix located in "Editors/language-mime-type/embed" folder. The file should instantiate the language description for the embedded language.


How to use Options Dialog and SPI?

This module contains implementation of Options Panel and simple SPI.

Register top level Options Panel

Client can install new panel to Options Dialog - see JavaDoc for OptionsCategory class.

Register panel to Advanced Options Panel

Client can install new panel to Advanced Options Panel - see JavaDoc for AdvancedOption class.


How to use Ant-Based Project Support?

Provides the basic infrastructure by which Ant-based projects can be created, read and write configuration parameters and properties from/to disk, satisfy common queries and interfaces, etc. See Javadoc and build system design document.

Mostly an SPI for use by project type providers to create the project type. Also includes a small API/SPI for other projects to find what Ant build steps are necessary to create a certain build product, for use in inter-project dependencies.

Ant project support faq:

How to use support for storing project properties?

Q: I'm creating a customizer (properties dialog) for my project type. I wan't to use the support for simple data types. What do I need to do?

You basicaly need to do two things. First create the representation of the project properties which can be used in the GUI. Second at some time convert the objects back to the ANT properties form and store them into the project.


How to use External Libraries?

Permits libraries to be defined, customized, and stored by the user for reuse in multiple projects. For example, a Java JAR library has a classpath (usually one JAR), and an optional source path and Javadoc path that may be used for development-time features.

Different technology support modules will supply definitions of different kinds of libraries, e.g. Java JARs, that may be reused in user projects. Modules may register library predefinitions to wrap libraries they bundle. Project type providers can refer to available libraries in customizer dialogs.


How to use Project API?

Provides a generic infrastructure for modelling projects. Documentation available in the Javadoc. The build system design overview describes the basic purpose of modelling projects.

The SPI should be used by modules defining particular project types, e.g. the J2SE project type. The API is to be used primarily by GUI infrastructure and some queries, though other module code may on occasion need to refer to the API.


How to use Project UI API?

The module supplies the APIs for the basic, generic UI infrastructure for projects: list of opened projects, main project, basic project-sensitive actions, template wizards, etc.

The main use case is for project type providers to supply logical views and customizers for the project. Also for template providers to create project-aware file templates. Can also get a list of open projects, create different kinds of project-related actions, and select projects on disk.


How to use General Queries API?

General kinds of queries between modules. Queries are one way of solving the intermodule communication problem when it is necessary for some modules to obtain basic information about the system (e.g. whether a particular file is intended for version control) without needing direct dependencies on the module providing the answer (e.g. the project type which controls the file). Details are covered in the Javadoc.

Particular use cases are enumerated in the Javadoc for each query API. Usage consists of simple static method calls. Potentially a wide variety of modules could use these queries; implementations are typically registered by project type providers, though also by Java library and platform implementations.


How to use Command Line Parsing API?

GetOpts like infrastructure to parse command line arguments with the cooperative participation of various modules.

Just Parse the Command Line

There needs to be a simple API for someone who has an array of strings and wants to parse them. One does not need to search for providers, just ask the infrastructure to do the parse and get the result.

The correct way to achieve this is to call CommandLine.getDefault().process(args).

Short and Long options with or without an argument

The standard getopts supports short form of options - e.g. a dash followed with one letter - or long form using two dashes followed with a word. Moreover the long form is optimized for abbrevations. If there are no conflicts between multiple options, then one can only use double dash followed with a prefix of a long option.

One can create an Option by calling any of its factory methods (like withoutArgument) and provider char for the one letter option and/or string for the long getopts option.

Options with or without an argument

There are three types of options. Those without an argument, those with a required one and those with optional one. Each one can be created by appropriate factory method in the Option class.

Support for --

The getopts compliant command line parsers support --. If these characters do appear on the command line, the rest of it is treated as extra arguments and not processed. The sendopts infrastructure supports this as well.

Multiple Independent CLI Handlers

The handlers for the options need not know about each other and still have to be able to process the command line successfully. Any module which wishes to provide its own options can register its OptionProcessor into a file META-INF/services/org.netbeans.spi.sendopts.OptionProcessor in its JAR file.

Extensible Options Set

Q: How shall one write an OptionProcessor that recognizes set of basic options, however contains one open slot? The processor wants other modules to provide recognizers for that slot and wants to communicate with them. For example, by default the processor recognizes option --channel <name_of_the_channel> which describes a source of data, and stores such data into a sink. There can be multiple sinks - discard the output, save it to file, show it on stdout, stream it to network. The processor itself can handle the copying of data, but does not itself know all the possible sink types.

To implement OptionProcessor like this one shall define an additional interface to communicate with the sink providers:

   package my.module;
   public interface SinkProvider {
     /** gets the option (even composite) that this sink needs on command line */
     public Option getOption();

     /** processes the options and creates a "sink" */
     public OutputStream createSink(Env env, Map<Option,String[]> values) throws CommandException;
   }
       

Other modules would then registered implementations of this interface in the META-INF/services/my.module.SinkProvider files. The OptionProcessor itself would just look all the implementations up, queried for the sinks, and then did the copying:

   class CopyingProvider extends OptionProvider {
     public Option getOption() {
        List<Option> l = ...;
        for (SinkProvider sp : Lookup.getDefault().lookupAll(SinkProvider.class)) {
          l.add(sp.getOption());
        }

        // we need only one provider to be present
        Option oneOfSinks = OptionGroups.oneOf(l.toArray(new Option[0]));

        // our channel option
        Option channel = ...;

        // the channel option needs to be present as well as a sink
        return OptionGroups.allOf(channel, oneOfSinks);
     }

     public void process(Env env, Map<Option,String[]> values) throws CommandException {
        OutputStream os = null;
        for (SinkProvider sp : Lookup.getDefault().lookupAll(SinkProvider.class)) {
          if (values.containsKey(sp.getOption())) {
            os = sp.createSink(env, values);
            break;
          }
        }
        if (os == null) {
          throw CommandException.exitCode(2);
        }

        // process the channel option and
        // handle the copying to the sink os
     }
   }
       

Another possible approach how to allow sharing of one option between multiple modules is to expose the option definition and its handling code as an interface to other modules, and then let the modules to write their own OptionProcessors. Necessary condition is that each of the processor is uniquely identified by some additional option, so when the shared option appears the infrastructure knows which processor to delegate to. This is demonstrated in the SharedOptionTest which basically does the following:

   /** the shared option, part of an interface of some module */
   public static final Option SHARED = ...;
   /** finds value(s) associated with the SHARED option and 
   * creates a JPanel based on them */
   public static JPanel getSharedPanel(Map<Option,String[]> args) { ... }
       

Then each module who wishes to reuse the SHARED option and the factory method that knows how to process their values for their own processing can just:

  public static final class ShowDialog extends OptionProcessor {
    private static final Option DIALOG = Option.withoutArgument('d', "dialog");

    protected Set<Option> getOptions() {
        // the following says that this processor should be invoked
        // everytime --dialog appears on command line, if the SHARED
        // option is there, then this processor wants to consume it 
        // as well...
        return Collections.singleton(Option.allOf(DIALOG, Option.anyOf(SHARED)));
    }

    protected void process(Env env, Map<Option, String[]> optionValues) throws CommandException {
        JPanel p = getSharedPanel(optionvalues);
        if (p == null) {
           // show empty dialog
        } else {
           // show some dialog containing the panel p
        }
    }
  }
       

The other modules are then free to write other processors refering to SHARED, for example one can write ShowFrame that does the same, just shows the panel in a frame, etc. The infrastructure guarantees that the exactly one provider which matches the command line options is called.

Printing Full Help Text

Althrough the handlers are provided by independent parties, it must be possible to generate resonable and consistent help description from all of them, so for the end user it appears as well formated and easily understandable. That is why every option can be associated with a short description providing info about what it is useful for using Option.shortDescription method. To get the description for all available options one can use CommandLine.getDefault().usage(java.io.PrintWriter).

Finding and Reporting when Options Are Not Correct

In case the command line cannot be processed a clean error for programmatic consumation and also one that can be shown to the end user of the command line must be given. This is handled by throwing CommandException with appropriate message description and exit code.

Processing Extra Command Line Arguments

There can be non-option arguments in the command line and they can freely mix with the option ones. For example the getopts would treat the following command line arguments as the same:
       --open X.java Y.java Z.txt
       X.java Y.java --open Z.txt
       
if the option open handles extra arguments. The sendopts infrastructure must distinquish between them and pass the non-option ones to the only one handler (active because it processed an option) that knowns how to parse them. It is an error if more than one or no handler expresses an interest in extra arguments and those are given. One can register such option by using the Option.additionalArgument factory method.

Handling Input and Output

Handler's shall not use the input and output streams directly for their execution, they should rely on the framework provided ones. This allows NetBeans based application to transfer the I/O from second started instance to the master one which is already running. From the client side there is the CommandLine.getDefault().parse methods taking additional arguments like input and output streams. This gets transfered to providers as an Env argument of their methods.

Returning Exit Code

When Handler's get execute (in the order defined by the order of options on the command line), each of them can either execute successfully, or fail. If a handler succeeds, next one is executed, if it fails, the execution is terminated and its return code is returned to the caller. The error can be notified by creating and throwing CommandException.exitCode(int errorCode).

Processing Only Extra Command Line Arguments

Sometimes it is desirable to process non-option arguments like file names without providing any option. Handlers can declare interest in such arguments. It is an error if such non-options are provided and no or more than one handler is around to handle them. One can create such option by using Option.defaultArguments factory method.

Only those processor need to process the options are created

For purposes of usage in NetBeans, it is needed to non initialize those handlers that are not really needed to process certain command line. The infrastracture decides which of them are going to be needed and instantiates only those. Given the amount of providers in current NetBeans IDE, this is not supported yet.

Complex Option Relations

Certain CLI processors may need more than one option before they can process the input. For example it is necesary to tune the radio and then also tell what to do with the output. It is unconvenient to process that as one option with argument(s), that is why one can use the OptionGroups.allOf, OptionGroups.someOf, for example like:
class PP extends OptionProcessor {
    private static Option tune = Option.requiredArgument(Option.NO_SHORT_NAME, "tune");
    private static Option stream = Option.requiredArgument(Option.NO_SHORT_NAME, "stream");
    
    public Set<Option> getOptions() {
      return Collections.singleton(
        OptionGroups.allOf(tune, stream)
      );
    }
    
    public void process(Env env, Map>Option,String[]> values) throws CommandException {
        String freq = values.get(tune)[0];
        String output = values.get(stream)[0];

        // XXX handle what is needed here
    }
}
When the two options are registered and command line like --tune 91.9 --stream radio1.mp3 is being processed, the PP's process method is going to get called with values 91.9 and radio1.mp3.

Alternative Options

Sometimes there may different ways to specify the same option and just one of them or none of them can be provided at given time. For example is there is a way to tune the radio with direct frequency or with name of the station. Just one can be provided and one is needed. This can be specified by using OptionGroups.oneOf factory methods:
Option freq = Option.requiredArgument(Option.NO_SHORT_NAME, "tune");
Option station = Option.requiredArgument(Option.NO_SHORT_NAME, "station");
Option tune = OptionGroups.oneOf(freq, station);    
The option tune then signals that just one of the station or freq options can appear and that they both are replaceable.


How to use Debugger Core - UI?

The debuggercore module (Debugger Core UI) contains shared UI components for all debugger implementations, and defines some SPI for sharing of them.

UseCase I. - Install and use CPP debugger plug-in to NetBeans + Java Debugger.

CPP debugger plug-in installs support for debugging of some new language to the NetBeans IDE, and some new debugging engine. This implementation of debugger should share UI components (actions, Debugger Views, ...) with default NB Java Debugger. It should share basic debugger model too - notion of current context, current session, thread, call stack line, ...

CPP debugger plug-in installs:

UseCase II. - Install and use JSP debugger plug-in to NetBeans + Java Debugger.

JSP debugger plug-in installs support for debugging of some new language to the NetBeans Java Debugger. It does not contain a new debugger engine, but it delegates to standard NB Java debugger. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

JSP debugger plug-in installs:

UseCase III. - Install and use J2EE debugger plug-in to NetBeans + Java Debugger.

J2EE debugger plug-in installs some enhancements to the standard Java Debugger. It does not contain a new debugger engine or language support. So it does not depends on Debugger Core API only, but it depends on JavaDebugger API too.

J2EE debugger plug-in installs:

UseCase IV. - Install and use DBX debugger plug-in to NetBeans.

DBX debugger plug-in installs support for debugging of some new language (CPP) to the NetBeans IDE, and some new debugging engine. But it contains debugger engine for Java debugging too. DBX debugger engine has its own session management (or will have in the next versions). One debugger engine can manage more than one sessions. One engine supports debugging in more than one language.

UseCase V. - Implement Debugger Core UI module on top of Debugger Core API / SPI.

Debugger Core UI needs:


How to use Navigator API?

Navigator module is a base API module which provides: A place for modules to show structure/outline of their documents Ability for modules to show their view only when special document(node) is active in the system UI for switching between multiple views available for currently active document(node) Coalescing of fast coming selected node changes to show content for

Basic Usage Steps

In order to plug in a view into Navigator UI for certain document (data) type, module writers need to complete following steps:

Writing NavigatorPanel implementation

Implementing NavigatorPanel interface is easy, you can copy from template basic implementation BasicNavPanelImpl.java.

Advices on important part of panel implementation:

Registering NavigatorPanel impl in a layer

Declarative registration of your NavigatorPanel impl connects this implementation with specific content type, which is type of the document, expressed in mime-type syntax, for example 'text/x-java' for java sources. Infrastructure will automatically load and show your NavigatorPanel impl in UI, when currently activated Node is backed by primary FileObject whose FileObject.getMimeType() equals to content type specified in your layer.

Writing layer registration itself is easy, you can again copy from template layer Basic Navigator Registration Layer.

Additional important info:

Advanced Content Registration - Linking to Node's Lookup

There may be situations where linking between your Navigator view and activated Node's primary FileObject is not enough or not possible at all. This simply happens when the data you want to represent in Navigator are not accessible through primary FileObject or DataObject. Usual example is Multiview environment, where more views of one document exists.

The solution is to bind content of your Navigator view directly to your TopComponent. Then, whenever your TopComponent gets activated in the system, Navigator UI will show th content you connected to it.

Steps to do:

Programmatic activation of NavigatorPanel

Programmatic activation of specific navigator panel activates and shows navigator panel in navigation area, as if user had selected the panel manually. API clients are expected to use programmatic activation to activate/select preferred panel from a set of available panels.

Example: Several TopComponents in multiview arrangement, TopComponentA and TopComponentB. Both components provide the same NavigatorLookupHint impl, which is recognized by two providers NavigatorPanelA and NavigatorPanelB. Now when TopComponentA becomes activated (has a focus), it's desirable to select/show NavigatorPanelA from navigator panels. On the other side, when TopComponentB is activated, NavigatorPanelB should be activated automatically.

Steps to do to activate panel programmatically:

Setting activated node of Navigator window

Sometimes clients need to alter activated Nodes of Navigator window, to better represent Navigator area content within the whole application. See TopComponent.getActivatedNodes() and TopComponent.Registry.html#getActivatedNodes() to find out what activated nodes of TopComponent and whole system mean.

Use Case Example: NavigatorPanel implementation