The principal use cases for the API are covered in the overall API architecture.
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.
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();
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..
See documentation for complete set of use-cases.
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()
List<UpdateUnit> UpdateManager.getDefault().getUpdateUnits(UpdateStyle style)
List<UpdateUnit> UpdateManager.getDefault().getUpdateUnits(UpdateStyle style)
and filter units which haven't been installed yet.
UpdateUnits
which are applicable to active IDE. UpdateManager will
search all available UpdateUnit given attribute.
UpdateUnit by module's code name and finds UpdateElement what fits the required version.UpdateElement which wants to install.OperationContainer for install, e.g. OperationContainer.createForInstall
OperationContainer.add(UpdateElement) and gets OperationInfo for that operation.OperationInfo.getRequiredElements()
OperationInfo.getBrokenDependency() Note: if there are some broken dependencies then operation cannot continue.OperationContainer.doOperation()
UpdateElement which wants to uninstall.OperationContainer for uninstall, e.g. OperationContainer.createForUninstall
OperationContainer.add(UpdateElement) and gets OperationInfo for that operation.OperationInfo.getRequiredElements()
OperationContainer.doOperation()
UpdateElement which wants to uninstall.OperationContainer for disable, e.g. OperationContainer.createForDisable
OperationContainer.add(UpdateElement) and gets OperationInfo for that operation.OperationInfo.getRequiredElements()
OperationContainer.doOperation()
UpdateElement which wants to uninstall.OperationContainer for enable, e.g. OperationContainer.createForEnable
OperationContainer.add(UpdateElement) and gets OperationInfo for that operation.OperationInfo.getRequiredElements()
OperationContainer.doOperation()
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.
UpdateUnitProvider.
Proposed usage of API: Call UpdateUnitProviderFactory.getUpdateUnitProviders()
UpdateUnitProviderFactory.create() which creates and registered
new one subscription in the system and will be used from that time in the future.
UpdateUnitProviderFactory.setEnable(UpdateUnitProvider, boolean).
UpdateUnitProviderFactory.remove(Id).
UpdateUnitProvider.refresh().
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.
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().
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.
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.
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.
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.
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.
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.
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.
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.
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.
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();
}
});
}
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());
?? ?? }
}
${...}.
index
parameter which means that the infrastructure should fill in
a fresh index variable e.g. i.
java.util.Collection.
${i index}
or ${c instanceof=java.util.Collection}.
${x default="Hello world"}.
${x default="\"quoted string\""}.
JTextComponent pane = ...
String tempCodeTemplateText = ...
CodeTemplate ct = CodeTemplateManager.get(pane.getDocument()).createTemporary(tempCodeTemplateText);
ct.insert(pane);
editable having
true/false.
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.
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.
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.
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.
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.
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);
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();
}
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();
}
In the given fold hierarchy expand all folds that are currently collapsed.
FoldUtilities.expand(hierarchy, null);
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);
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();
}
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
}
});
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
}
}
});
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
public class MyFoldManager implements FoldManager { // or extends AbstractFoldManager
...
}
public class MyFoldManager ...
...
public static final class Factory implements FoldManagerFactory {
public FoldManager createFoldManager() {
return new MyFoldManager();
}
}
}
NbJavaSettingsInitializer)
public class MySettingsInitializer ...
public void updateSettingsMap(Class kitClass, Map settingsMap) {
...
settingsMap.put(SettingsNames.CODE_FOLDING_ENABLE, Boolean.TRUE);
}
}
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 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();
}
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).
class MimeLookup extends Lookup containing
static MimeLookup getMimeLookup(String mimeType).
static Lookup getLookup(MimePath mimePath) method in MimeLookup.
org.netbeans.spi.editor.fold.FoldManagerFactory classes).
org.netbeans.spi.editor.completion.CompletionProvider classes).
javax.swing.Action classes or names of actions
(i.e. value of Action.NAME attribute) present in editor kit e.g. "goto-source").
org.netbeans.editor.SideBarFactory classes).
org.netbeans.lib.editor.hyperlink.spi.HyperlinkProvider classes).
org.netbeans.lib.editor.codetemplates.spi.CodeTemplateProcessorFactory classes).
org.netbeans.modules.editor.hints.spi.HintsProvider classes).
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));
MimePath scriptletPath = MimePath.parse("text/x-jsp/text/x-java");
Lookup lookup = MimeLookup.getLookup(scriptletPath);
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
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);
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.
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.
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.
String text = "public void m() { }";
TokenHierarchy hi = TokenHierarchy.create(text, JavaLanguage.description());
document.readLock();
try {
TokenHierarchy hi = TokenHierarchy.get(document);
... // explore tokens etc.
} finally {
document.readUnlock();
}
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();
}
org.netbeans.modules.lexer.editorbridge.LexerLayer in lexer/editorbridge module.
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);
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);
...
Set<MyTokenId> skipIds = EnumSet.of(MyTokenId.COMMENT, MyTokenId.WHITESPACE);
TokenHierarchy tokenHierarchy = TokenHierarchy.create(inputText, false,
MyLanguage.description(), skipIds, null);
...
org.netbeans.lib.lexer.test.simple.SimpleTokenId can be copied
or the following example from
org.netbeans.modules.lexer.editorbridge.calc.lang.CalcTokenId.
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.
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.
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.
Client can install new panel to Options Dialog - see JavaDoc for OptionsCategory class.
Client can install new panel to Advanced Options Panel - see JavaDoc for AdvancedOption class.
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: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.
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.
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.
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.
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.
CommandLine.getDefault().process(args).
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.
--. 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.
META-INF/services/org.netbeans.spi.sendopts.OptionProcessor
in its
JAR file.
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.
--open X.java Y.java Z.txt
X.java Y.java --open Z.txt
if the option openhandles
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.
CommandLine.getDefault().parse
methods taking additional arguments like input and output streams.
This gets transfered to providers as an
Env
argument of their methods.
Option.defaultArguments
factory method.
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.mp3is being processed, the
PP's process method is going to get
called with values 91.9and
radio1.mp3.
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.
Implementing NavigatorPanel interface is easy, you can copy from template basic implementation BasicNavPanelImpl.java.
Advices on important part of panel implementation:
/** JavaDataObject used as example, replace with your own data source */
private static final Lookup.Template MY_DATA = new Lookup.Template(JavaDataObject.class);
public void panelActivated (Lookup context) {
// lookup context and listen to result to get notified about context changes
curResult = context.lookup(MY_DATA);
curResult.addLookupListener(/** your LookupListener impl here*/);
Collection data = curResult.allInstances();
// ... compute view from data and trigger repaint
}
Do *not* perform any long computation in panelActivated directly, see below.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: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:
class AmazingTypeLookupHint implements NavigatorLookupHint {
public String getContentType () {
return "text/my-amazing-type";
}
}
Node subclass
instead of directly altering lookup of your TopComponent.
See Node.getLookup() method.
Then Navigator will show your desired content whenever your Node
subclass will be active in the system.TopComponent.getLookup() includes also results from
lookup of asociated Node. So this approach will stop
working if you change default behaviour of TopComponent.getLookup() method.
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: SeveralTopComponents 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:
NavigatorPanel implementation that
you want to activate/show in navigator area.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