The principal use cases for the API are covered in the overall API architecture.
When the method return value is important value to check (or the only effect the method has) the method can be annotated with CheckReturnValue. Annotation servers as documentation as well as a hint for static code analysis.
Method annotated with
CheckForNull
may return null
value. Annotation servers as documentation
as well as a hint for static code analysis.
When the field, parameter, local variable or return value of the method
must not be null
the
NonNull
annotation can be used to clearly express this. It servers as documentation
as well as a hint for static code analysis.
Field, parameter or local variable annotated with
NullAllowed
can contain null
value so null
check should
occur before any dereference. Annotation servers as documentation
as well as a hint for static code analysis.
Annotation
NullUnknown
complementing other nullness annotations servers for cases where
the element may or may not be null
under certain
defined circumstances (usage).
When the analysis tool report false warning it is possible to use SuppressWarning annotation to suppress the warning.
Primary purpose of this API is to allow smooth use of HTML based UI in NetBeans Platform. To achieve that it provides specific annotatations like @OpenHTMLRegistration, but otherwise it builds on the same usecases as the HTML for Java API.
Future<Object> result = new Intent(Intent.ACTION_VIEW, new URI("scheme://path/")).execute(); Object value = result.get();
new Intent(Intent.ACTION_VIEW, new URI("scheme://path/")).execute(new Callback() { void success(Object result) { // use the result somehow } void failure(Exception exception) { // report the failure somehow } });
@IntentHandlerRegistration( displayName = "Show my item in MyEditor", position = 800, uriPattern = "myscheme://.*", actions = {Intent.ACTION_VIEW, Intent.ACTION_EDIT} ) public static Object handleIntent(Intent intent) { SomeType result = parseAndPerformIntentSomehow(intent); return result; }
@IntentHandlerRegistration( displayName = "Show my item in MyEditor", position = 800, uriPattern = "myscheme://.*", actions = "*" ) public static void handleIntent(final Intent intent, final Result result) { // Move the execution to another thread. Do not wait for the result // here, just pass the result object. EventQueue.invokeLater(new Runnable() { public void run() { try { Object result = doSomethingInEDT(intent); result.setResult(e); } catch (Exception e) { result.setException(e); } } }); }
The basic use-case is printing a simple text, e.g. text output of an application, into a dedicated pane in the UI, e.g. a tab in Output Window in the IDE.
InputOutput io = InputOutput.get("UseCase1", true); io.getOut().println("This is a simple output"); io.getOut().close();
Hyperlinks can be also used to invoke some code when clicked.
InputOutput io = InputOutput.get("UseCase3", true); io.getOut().print("A line containing a "); io.getOut().print("hyperlink", Hyperlink.from(new Runnable() { public void run() { System.gc(); } })); io.getOut().println(" for invocation of custom code."); io.getOut().close();
Print a color text. Users can select a predefined color for common cases (debug, warning, failure, success), or custom color specified as RGB value.
InputOutput io = InputOutput.get("UseCase4", true); io.getOut().println("Let's print some info", OutputColor.debug()); io.getOut().println("or warning with appropriate color", OutputColor.warning()); io.getOut().println("Maybe also text with custom reddish color", OutputColor.rgb(255, 16, 16)); io.getOut().close();
It is possible to reuse already created output pane and clear all the previously printed text if it is not needed any more.
InputOutput io = InputOutput.get("UseCase5", true); io.getOut().println("Let's print some text"); io.getErr().println("and reset the pane immediately."); io.reset(); io.getOut().println("The pane is now empty and we can reuse it simply"); io.getOut().close();
The API is widely used by all sorts of IDE modules which need to work with Java sources. They can find Javadoc, unit tests, source level, etc. The SPI is intended mainly for Java platform and library providers, and project type providers, to declare all of this information.
The API is widely used by all sorts of IDE modules which need to work with sources. The SPI is intended mainly for (java) platforms and library providers, and project type providers, to declare all of this information.
This is the code to register a sample provider:
{@code @}ServiceProvider(service = BindingsProvider.class) public class SampleBindingsProvider implements {@link org.netbeans.spi.knockout.BindingsProvider} { {@code @Override} public void findBindings(FileObject htmlFile, Response r) { Bindings tweet = Bindings.create("Tweet"). stringProperty("from_user", false). intProperty("from_user_id", false); Bindings tweeters = Bindings.create("Tweeters"). stringProperty("name", false). stringProperty("userNames", true); Bindings twitterClient = Bindings.create("TwitterClient"); twitterClient. stringProperty("activeTweetersName", false). stringProperty("activeTweeters", true). stringProperty("userNameToAdd", false). booleanProperty("loading", false). modelProperty("currentTweets", tweet, true). modelProperty("savedLists", tweeters, true); r.applyBindings(twitterClient); } }
This sample has been used when testing the module.
Start exploring use-cases related to Maven archetypes at the ArchetypeWizards class.
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..
XXX no answer for arch-usecases
createManager()
method that
should be used whenever one needs an instance of
ScriptEngineManager
inside of NetBeans based application.
To register additional scripting engine, just include its JAR file in
the application. Its factory registration in META-INF/services/javax.script.ScriptEngineFactory
will be found and used by
Scripting
factory methods. Alternatively you can implement and register
EngineProvider
as in the dynamic registration use-case.
In any case don't forget to advertise your engine to the
runtime container
via OpenIDE-Module-Providers: javax.script.ScriptEngine."engineName"
tag as specified by manifest registration
API.
SearchHistory
is synchronising history content through netbeans modules and it saves history to preferences.
There are two separate histories. One for search and another for replace.
When you add your propertyListener to
SearchHistory
- you can listen for changes in histories.
SearchHistory
has methods for adding new entries and getting whole history list.
This use-case was formerly covered by module org.openidex.search.
The SearchInfo API+SPI allows other modules to specify whether and how should nodes they define be searched.
The definition is represented by objects extending class
SearchInfoDefinition
. To customize searching on a custom node, a
SearchInfoDefinition
object must be added to the node's lookup.
In most cases, there is no need to define own class extending the
class - one can use factory methods of class
SearchInfoDefinitionFactory
.
In some cases implementators may need to apply the same set
of SearchFilterDefinitions in the whole subtree of a node.
If so, it is not needed to put SearchInfoDefinition
to all
nodes' lookups, but only one instance of
SubTreeSearchOptions
have to be put into the lookup of the root node.
SearchInfoDefinition
,
SearchFilterDefinition
,
SubTreeSearchOptions
and a factory class
SearchInfoDefinitionFactory
People that want to enhance IDE searching features (with custom search criteria or specialized algorithms) can add a new tab to the "Search in Projects" dialog.
They need to implement several classes:
SearchProvider
to register the new search feature to the IDE or platform application.
SearchProvider.Presenter
that creates visual component for adding to the search dialog and that can interact with dialog buttons.
SearchResultsDisplayer
to show search results to the user.
SearchComposition
that encapsulates setting and state of searches, provide access to result displayer, and is able to start
and terminate the search.
SearchProvider
,
SearchProvider.Presenter
,
SearchResultsDisplayer
,
SearchComposition
and relative classes.
SearchInfo
,
that defines which files should be searched (it can be retrieved from methods in
SearchInfoUtils
,
or UI component controller ScopeController
),
SearchListener
that you should inform about events that happen during searching, and helper classes
SearchInfoUtils
(getting SearchInfo objects for nodes) and
FileNameMatcher
(filtering files by file name).
This API enables other modules to open Find in Projects dialog with some pre-defined criteria and to start searching programatically.
An existing file can be used as a boilerplate for creation of a new file. The boiler plate can contain necessary skeleton, comments, content. As the boilerplate resides on config filesystem, it is also customizable by the user and the user can eventually develop custom templates.
In previous NetBeans versions, templating system was built into
Often many people require ability to create a "clever" template - e.g. write piece of simple text and at the time of its processing do some advanced changes to it using either scripting or templating languages.
This traditionally used to be a bit complicated task (hacking into DataObject implementation), however since
version 6.1 there are interface in
Runtime or project-related values may be supplied by
The CreateFromTemplateAttribute implementation knows which template is being used, where the outcome should be placed, so it can derive appropriate values for both the template and the target location.
There is a built in support for scripting languages
in the NetBeans Platform. If a template is annotated with
ScriptEngine
interface or
a String
name of the engine that is then used to
search for it in the javax.script.ScriptEngineManager
.
Usually the freemarker engine is the one that is
supported by the NetBeans IDE - if your module wants to use it
then include a token dependency OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker
in your manifest file (also accessible through project customizer GUI)
to indicate to the system that you need it.
String
representing the current day like 23. 3. 2007
String
the current time like 17:18:30
java.util.Date
representing current data and time likeString
the file encoding of the template instance
Other properties can indeed be provided by
CreateFromTemplateAttributess.
After processing, the output is also sent to appropriate
org.openide.text.IndentEngine
associated
with the mime type of the template, for formating.
Smart Templating Quick How-To
First of all create a file in your module layer located somewhere
under the Templates/
folder. Make it a template by
adding <attr name="template" boolvalue="true"/>. Associate
this template with a scripting language, for example by
<attr name="javax.script.ScriptEngine" stringvalue="freemarker"/>.
Now make sure that the scripting language integration is also available
by requesting a token in standard format, for freemarker just put
OpenIDE-Module-Needs: javax.script.ScriptEngine.freemarker
in your manifest. This tells the NetBeans module system that a
module providing integration with such scripting engine has to be
enabled. Now you can use regular script language tags inside of
your template file. When you write your instantiate
method in your wizard, you can create a Map<String,Object> and
fill it with parameters collected from your wizard and then pass it
to
createFromTemplate(targetFolder, targetName, mapWithParameters)
. This will invoke the scripting language and make the
mapWithParameters
values available to it. Beyond this
there is few standard parameters predefined including name
, user
, date
, time
, etc.
and also additional parameters are collected from all registered
CreateFromTemplateAttributesProviders.
A CreateFromTemplateHandler should be able to create multiple files, one of them important so it will open after user initiates the creation action. The template of set of related files may be represented by a folder with a handler attached, and the operation deploys multiple files in the target directory.
There is a way to create a portable wizard (e.g. one that can
be executed inside of NetBeans as well as in a browser). The
most portable UI these days is written in HTML. To
register such HTML based wizard with your file template,
use @TemplateRegistration
annotation and include page()
attribute referencing
your own HTML page:
public class X { {@code @TemplateRegistration}( page = "x.html", scriptEngine = "freemarker", displayName = "JS Wizard", folder = "Other", content = "x.fmk" ) public static String jsWizard() { return "yourInitializationCode();"; } }
the return value of the annotated method (named jsWizard
)
should be of type String and its content should be snippet of
JavaScript code to execute inside of your specified HTML page
(e.g. x.html
) to create an instance of
KnockoutJS model to
drive the wizard. Here is a sample code for the model:
function yourInitializationCode() { var ok = ko.observable(false); var msg = ko.observable(''); var current = ko.observable('Init'); var data = { 'errorCode': ko.computed(function() { if ('Init' == current()) return 0; if (!ok()) return 1; if (msg()) return 0; return 2; }), 'current': current, 'ok': ok, 'msg' : msg } ko.applyBindings(data); return data; }
The model defines wizard composed of few panels (defined in following
HTML file) and a verification function (registered as errorCode
)
to check if everything is OK. In addition to that it defines
proprietary text value msg
which is
going to be filled by the wizard and cannot be empty. Each
page of the wizard is registered using a custom
Knockout.js binding called
step
. Here is an HTML page defining three steps:
<section data-bind="step: { 'id' : 'init', text : 'Initial Page'}" > <p> Write your UI in portable HTML and display it in NetBeans or on web! Read more at <a href="http://wiki.netbeans.org/HtmlUIForTemplates">our wiki</a>... </p> </section> <section data-bind="step: 'info'" > <p> Use <a href="http://knockoutjs.com">knockout.js</a> bindings to isolate your view model from the actual look of your HTML page. Bind your view to model written in Java or JavaScript. </p> <h3>Is everything OK?</h3> <input type="checkbox" data-bind="checked: ok"/> <h3>How do you feel?</h3> <input type='text' data-bind="textInput: msg"/> </section> <section data-bind="step: { 'id' : 'summary' }" > <p> You are feeling <span data-bind="text: msg"></span>! Let's proceed to create a file which will express your feeling by using <a href="http://freemarker.org/">Freemarker</a> templating engine and values filled in this wizard. </p> </section>
The Next/Finish buttons are controlled by the errorCode
property.
If it is non-zero, there is an error and these buttons are disabled.
Also once can use that inside of the HTML page to display user related errors:
<div data-bind="visible: errorCode() == 1"> <span style="color: red">Please check you are OK!</span> </div> <div data-bind="visible: errorCode() == 2"> <span style="color: red">Tell us how do you feel!</span> </div>
The L10N of the wizard is done on the level of HTML pages.
The whole page gets translated into different language with appropriate
suffix like x_cs.html
and it is then
selected instead of the default one, when user runs in such locale.
When the wizard is successfully finished, all the values
specified in the model (except system ones like current
,
errorCode
, etc.) are transfered to the templating engine,
so they can influence the content of created files.
Here is a sample x.fmt
content which reuses the msg
value provided by the wizard:
Hi, I am Freemarker. I feel ${wizard.msg}.
When such file is instantiated, the ${wizard.msg}
is
replaced by the actual value taken from the wizard.
Some people would rather use Java instead of Java script while getting the portability of the HTML. There is a simple way to rewrite the HTML and JavaScript sample to Java (and possibly run it in a plugin-less browser via bck2brwsr VM). Keep the same HTML, Freemarker, etc. files - just instead of encoding the logic in JavaScript use Java:
{@link net.java.html.json.Model @Model}(className = "JavaWizard", properties = { {@link net.java.html.json.Property @Property}(name = "current", type = String.class), {@link net.java.html.json.Property @Property}(name = "ok", type = boolean.class), {@link net.java.html.json.Property @Property}(name = "msg", type = String.class) }) public class JavaWizardCntrl { {@link net.java.html.json.ComputedProperty @ComputedProperty} static int errorCode( String current, boolean ok, String msg ) { if ("Init".equals(current)) return 0; if (!ok) return 1; if (msg == null || msg.isEmpty()) return 2; return 0; } {@code @TemplateRegistration}( page = "x.html", scriptEngine = "freemarker", displayName = "HTML/Java Wizard", folder = "Java", content = "x.fmk" ) public static JavaWizard javaWizardFactory() { return new JavaWizard("Init", false, ""); } }
The return value of the annotated method is now an HTML/Java model class which can naturally represent the essential Knockout.js objects in Java.
It is very common that the HTML file creation wizards (either controled by JavaScript or by Java) need to allow user to specify target location of their file. To simplify such common task and to ensure its UI is consistent with the rest of the environment, one can just include following code snippet in the HTML file and leave its actual rendering on the system:
<section data-bind="step: 'targetChooser'" > </section>
Such section will then be replaced by a panel which provides appropriate UI for choosing target directory as well as name for the newly created file.
In case one prefers more Java-like chooser, it is possible to use
'targetChooser:java'
as name of the step. Then all
Java source groups in target project will be listed and presented
in a typical Java package view selection mode. Once can use different
suffix than java
to list other types of source groups.
This feature requires presence of org.netbeans.modules.java.project.ui
module, otherwise the target chooser falls back to classical one.
There is a way to create a portable wizard (with logic either in JavaScript or in Java) to instantiate a Maven archetype. This way one merges the project templating functionality of Maven with flexible and tailored UI provided by HTML and JS/Java.
The definition of the UI is the same as in
previous
cases, just as a target chooser
one can also use dedicated Maven one - just use
'targetChooser:archetype'
to get a panel
with options to select directory, archetypeId
,
groupId
and version
of the project to
create.
This feature requires presence of org.netbeans.api.maven
module, otherwise the target chooser falls back to classical one.
The archetype registration is then a classical one. Here is an
example of the UI being in x.html
and the archetype
described in x.archetype
one:
{@code @TemplateRegistration}( page = "x.html", displayName = "HTML/Java Wizard", folder = "Java", content = "x.archetype" ) public static MavenWizard mavenArchetype() { return new MavenWizard(/*...*/); }
The x.archetype
file describes the archetype to use
and has following properties-like syntax:
archetypeGroupId=org.codehaus.mojo.archetypes archetypeArtifactId=javafx archetypeVersion=0.6 archetypeBuild=false # or true to build the project once created archetypeOpen=src/main/java/.*/Main.java,src/main/resources/default.config # regexp to # find files that should be opened once the project is created
The values archetypeArtifactId
,
archetypeGroupId
and archetypeVersion
are by default taken from the archetype definition file, but the
wizard model can define these properties as well and in such case
they would take precedence. These values define what Maven
archetype will be used to initialized the project structure.
Any properties defined by the model (in the above example
the MavenWizard
) are going to be passed into
Maven archetype execution and can thus influence the
the behavior of the archetype - this is the way to write an
HTML UI for Maven archetype.
See documentation for complete set of use-cases.
First of all, you don't need to worry about serialization if all your MultiViewDescription
instances
contained in the multiview state to be non serializable.
Meaning they all return TopComponent.PERSISTENCE_NEVER
in MultiViewDescription.getPersistenceType()
.
If at least one of the views requires serialization, you have no choice but to make all
MultiViewDescription
implementors serializable.
You also can persist the MultiViewElement instances that the multiview contains. The algorithm here is a bit complicated for
performance reasons. Only those Elements are stored that were created during the session and which are Serializable.
So if the user never switches to the 4rd tab, and it's corresponding element and visual component never get created, then
it won't be stored. (We would have to create it just for the sake of persistance).
So if your visual component needs some inital context for creation, you should store it in the description instance, and if the visual component
wants to store it's state (location of cursor, selected field, something else that makes sense for the opened component) you should store it in the MultiViewElement.
So basically if you are always able create the Element from Description without any persisted data, you don't need to persist anything.
If you define your own CloseOperationHandler
implementation for the multiview component, then you also ought to define it
Serializable. Otherwise it will be silently replaced by the default handler on restoration of the multiview component.
Each MultiViewDescription
defines display name and icon. While the icon
is meant for the whole document/view tab, the display name is just for the inner switching button.
So how does one set the name for the whole MultiView component? It can be done when creating the component.
TopComponent mvtc = MultiViewFactory.createMultiView(myDescriptions); mvtc.setDisplayName("My static mvtc displayName");
Later in the lifecycle of the component, it can be also influenced from within the individual
multiview visual elements using the MultiViewElementCallback.updateTitle()
method.
XXX no answer for arch-usecases
createManager()
method that
should be used whenever one needs an instance of
ScriptEngineManager
inside of NetBeans based application.
GraalVM:lang
name and makes them accessible via
NetBeans Scripting
API.
org.graalvm.polyglot
APIs.
Use them to obtain directly, if you trust the provider of those APIs.
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.
UpdateUnit
s
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
.
You can use the JDBCDriver.getDriver() method to obtain a reference to the underlying JDBC Driver instance. This is useful if you want to use the registered drivers but create your own JDBC connections independent of the Database Explorer.
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 user of this API may want to remove a connection from the list of connections registered by the Database Explorer. This is done using ConnectionManager.removeConnection()
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.
If you want to connect to the database without showing a dialog or any kind of UI, you can use the DatabaseConnection.connect() method.
You may want to test to make sure the underlying physical JDBC connection obtained from a DatabaseConnection is either valid or null. This is done using the DatabaseConnection.getJDBCConnection(boolean test) method, which validates the underlying connection before returning it. If the connection is invalid, it marks the DatabaseConnection as disconnected and returns null.
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 DataFlavor
s 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 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(); }
// create new fold positional information for all folds. Collection<FoldInfo> newInfos = ...; // create FoldInfo for each of the fold newInfos.add( FoldInfo.range(start, end, type). withTemplate(customTemplate). withDescription(veryCustomDescription). collapse(true) ); // the hierarchy must be locked prior to update doc.readLock(); hierarchy.lock(); try { operation.update(newInfos, null, null); } finally { }The
update()
operation performs a diff, creates new folds, discards old ones, and updates the folds, which
prevailed.
operation.foldIterator
. The iterator
will enumerate all folds, including (recursively) blocked ones.
String sectionName = ...; StyledDocument doc = ...; GuardedSectionManager guards = GuardedSectionManager.getInstance(doc); GuardedSection g = guards.findSimpleSection(sectionName); guards.createSimpleSection("new_name", doc.createPosition(g.getEndPosition().getOffset() + 1));
StyledDocument doc = ...; GuardedSectionManager guards = GuardedSectionManager.getInstance(doc); GuardedSection g = guards.findSimpleSection("sectionName"); g.deleteSection();
CloneableEditorSupport
to provide
guarded sections you should implement the GuardedEditorSupport
interface.
private final class MyGuardedEditor implements GuardedEditorSupport { ... }Further implement reading and writing of existing sections.
protected void loadFromStreamToKit(StyledDocument doc, InputStream stream, EditorKit kit) throws IOException, BadLocationException { if (guardedEditor == null) { guardedEditor = new MyGuardedEditor(); // remember the provider String mimeType = ((CloneableEditorSupport.Env) this.env).getMimeType(); guardedProvider = GuardedSectionsFactory.find(mimeType).create(guardedEditor); } // load content to kit if (guardedProvider != null) { guardedEditor.setDocument(doc); Charset cs = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile()); Reader reader = guardedProvider.createGuardedReader(stream, cs); try { kit.read(reader, doc, 0); } finally { reader.close(); } } else { kit.read(stream, doc, 0); } } protected void saveFromKitToStream(StyledDocument doc, EditorKit kit, OutputStream stream) throws IOException, BadLocationException { if (guardedProvider != null) { Charset cs = FileEncodingQuery.getEncoding(this.getDataObject().getPrimaryFile()); Writer writer = guardedProvider.createGuardedWriter(stream, cs); try { kit.write(writer, doc, 0, doc.getLength()); } finally { writer.close(); } } else { kit.write(stream, doc, 0, doc.getLength()); } }Your module should also require a proper implementation. In case of java content add to your module manifest file:
OpenIDE-Module-Requires: org.netbeans.api.editor.guards.Java
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.
Client needs to execute an external process and handle process streams and display the output in the output tab.
In order to achieve this client creates the ExecutionDescriptor. Via this object client configures all the UI behaviour of the subsequent execution. As a next step client creates the ExecutionService itself and calls run to execute the job. Run can be called multiple times. The output and input streams are presented in output tab. Additional processing and printing conversion can be configured in descriptor through interfaces described in following usecases.
The creation of the external process is supported by ExternalProcessBuilder to make things easier.
Client needs to process character data coming from stream, file or other source. This usecase should be solved by External Execution Base API.
To abstract the source of the data client must implement InputReader. To abstract the data processing client must implement InputProcessor or LineProcessor. For all three interfaces there are prepared common implementations (and bridge from character based to line based processing) at these three factory classes:
To configure additional functionality specific to org.openide.windows.OutputWriter
see the next usecase.
Once the data source and processing objects are prepared client creates InputReaderTask. Factory methods of the InputReaderTask can create either common task exiting on interruption or cancellation or draining task which is trying to drain out all available data before exiting.
Client intends to process input lines and print them to org.openide.windows.OutputWriter
.
In addition printed lines should be transformed (converted) somehow
and enriched by line listeners.
The both default printing processors provide factory method accepting LineConvertor. Namely InputProcessors.printing(org.openide.windows.OutputWriter out, LineConvertor convertor, boolean resetEnabled) and LineProcessors.printing(org.openide.windows.OutputWriter out, LineConvertor convertor, boolean resetEnabled). Convertor is then used to convert received lines to printed ones. Common convertors (file, http) are provided in factory class LineConvertors.
Third party wants to implement custom process builder to provide additional functionality, such as remote execution. This usecase should be solved by External Execution Base API.
In order to do so it will implement ProcessBuilderImplementation and pass ProcessBuilder to its clients. The API instances are created with help of ProcessBuilderFactory.
Client wants to destroy the process, trying to kill whole process tree. This usecase should be solved by External Execution Base API. Method ExternalProcessSupport.destroy(java.lang.Process process, Map<String,String> env) is designed for that. It will use a ProcessDestroyPerformer registered in default lookup to do so.
The third party plugin may want to be able provide additional arguments for the process startup in a standardized way. In order to do so it will register a implementation of StartupExtenderImplementation to the layer folder StartupExtender. The annotation StartupExtenderImplementation.Registration can be used for that.
The clients (for exmaple project or server) may query the extenders via the API class StartupExtender and use the additional arguments for the process.
Some default LineConvertors returned by LineConvertor needs to open file or URL. A bit special case is also options dialog opening required by ExecutionService to open options dialog specified by ExecutionDescriptor. To cover this three usecases in a pluggable way while keeping dependencies minimal there are three corresponding SPI classes one may implement. So there is FileOpenHandler to handle file opening, HttpOpenHandler to deal with HTTP URLs and OptionOpenHandler to open proper options dialog.
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.
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.
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.
XXX no answer for arch-usecases
XXX no answer for arch-usecases
XXX no answer for arch-usecases
XXX no answer for arch-usecases
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>() {Note that it is not needed to publish the underlying@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; } }
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 a file in "Editors/language-mime-type/languagesEmbeddingMap" folder named by the token-id's name containing target mime-type and initial and ending skip lengths:
<folder name="Editors"> <folder name="text"> <folder name="x-outer-language"> <folder name="languagesEmbeddingMap"> <file name="WORD"><![CDATA[text/x-inner-language,1,2]]> </file> </folder> </folder> </folder> </folder>
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.
The typical client of Print module can be any tool to print custom data.
The simple way to enable printing for a custom data is:
If the data is a Swing component which extends javax.swing.JComponent
and shown in a org.openide.windows.TopComponent
, the key "print.printable"
(PrintManager.PRINT_PRINTABLE) with value "Boolean.TRUE"
in the component must be set as a client property. See example:
public class MyComponent extends javax.swing.JComponent { public MyComponent() { ... putClientProperty("print.printable", Boolean.TRUE); // NOI18N } ... }
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.
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.
XXX no answer for arch-usecases
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.
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)
.
public final class MyOption implements Runnable { @Arg(longName="hello") public String name; public void run() { System.out.println("Hello " + name + "!"); } public static void main(String... args) { CommandLine line = CommandLine.create(MyOption.class); line.process(args); } }
If the above main class is called with parameters --hello World
it
will print out Hello World!
.
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.
When using the declarative annotation style
one can always specify
@Arg(longName="text", shortName='t')
.
The longName
attribute is required, but if there is supposed to be
no long version of the argument, it can be set to empty string.
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.
boolean
to create
an option without an argument.
--. 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.
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.txtif 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.
When using the
declarative annotation style
one may annotate a field of type String[]
which then means
this field should be filled with all additional arguments.
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. With the
declarative annotation style
one can annotate a field of type String[]
and specify that
it is supposed to be implicit.
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. This kind of grouping is not currently supported with the declarative annotation style registration.
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.
Primary purpose of this module is collect information about the user actions that take place in the running NetBeans based application.
Important part of the behaviour of this module is the ability to cooperate with information analyzing tools and present their results. This is done thru special HTTP contracts, where the module reads and understands various server responses and is able to open browser after submitting data to analysis.
org.netbeans.modules.uihandler.Bundle
that specifies the location of the page on a server one shall
query and display to the user when the module is about the
submit usage data for analysis.
org.netbeans.modules.uihandler.Bundle
that specifies the location of the page on a server one shall
query and display to the user when the module is about the
submit metrics data for analysis.
org.netbeans.modules.uihandler.Bundle
that specifies the location of the page on a server one shall
query and display to the user when the module is about the
submit an error report.
WELCOME_URL
can contain
any XHTML text, but it also should contain a <form/> tag
that defines <input type="hidden" name="submit" value="localizedName"/>.
The localizedName
is then going to be used for a button
for the dialog displaying the summary. When this button is invoked,
the "action" URL is then feed with data from the UI logs.
The server is then supposed to process the data, create
some analytics pages and return them back to the client.
If the returned page contains tag like
<meta http-equiv='Refresh' content='3; URL=somepage'>
an (external) browser is opened with the specified URL and
the user can then interact directly with the server, thru
pages it serves.
org.netbeans.modules.uihandler.Submit
property when invoking NetBeans.
The base module is in fact just an infrastructure which collects data about UI gestures, but the actual gestures need to be delivered to it somehow. Here is the description of the ways how one can extend own modules to cooperate with this UI gestures infrastructure.
To feed own data about special UI gestures one can just create own
Logger.getLogger(UI_LOGGER_NAME_VALUE+".ownname")
and send own log records to it.
UI_LOGGER_NAME_VALUE is a value of UI_LOGGER_NAME
resource bundle key, defining the name of the logger.
The format of the log messages shall follow the one described by the
structured logging
document, e.g. the
LogRecord
shall have associated
ResourceBundle
and the record's getMessage
shall point to a key
in that bundle.
MSG_KEY_ICON_BASE
in the bundle associated
with the
LogRecord
(where the MSG_KEY is the string returned by record.getMessage()
)
and the value is then going to be used for the
Node
representing the UI gesture.
Sometimes direct logging may not be possible. For example for
performance data it might be meaningful to collect the information
over a longer time period and only at the end output some statistics.
This is supported as well. Just implement and register one
of the
These usecases are realized as described in here in provided UI specification.
@Registration
.
Implementing NavigatorPanel interface is easy, you can copy from template basic implementation BasicNavPanelImpl.java.
Advices on important part of panel implementation:@Registration
./** 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 registering annotation (see more options below).
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 shows list or tree of someNode
s
in Navigator area. When user selects a Node in the list or tree,
it is desirable to show selected Node's properties in Properties
window and enable proper actions in main menu. Exactly this can be done
by presenting Node selected in the list/tree as activated Node of
Navigator window.
Steps to specify activated Nodes of Navigator window:
NavigatorPanel
, implement
method getLookup()
to return Lookup instance filled
with Node(s) that you want to set as activated Nodes of Navigator window.
class MyNavigatorPanel implements NavigatorPanel { /** Dynamic Lookup content */ private final InstanceContent ic; /** Lookup instance */ private final Lookup lookup; public MyNavigatorPanel () { this.ic = new InstanceContent(); this.lookup = new AbstractLookup(ic); } public Lookup getLookup () { return lookup; } /** Call this method when activated Nodes change is needed, * for example when selection changes in your NavigatorPanel's Component */ private void selectionChanged (Node oldSel, Node newSel) { ic.remove(oldSel); ic.add(newSel); } ... impl of rest of your NavigatorPanel }
Some complex navigation views need support for undoing and redoing edit changes done either directly in the view or in document which the view is representing.
Steps to support undo and redo in navigation view:NavigatorPanel
interface with extra method
getUndoRedo().
NavigatorPanel
usage.
UndoRedo
support returned from NavigatorPanelWithUndo.getUndoRedo()
is propagated to the Navigator TopComponent and returned as its
UndoRedo
support. For details see
TopComponent.getUndoRedo()
and UndoRedo interface.
NavigatorPanelWithUndo
implementation:
class MyNavigatorPanelWithUndo implements NavigatorPanelWithUndo { /** UndoRedo support, substitute with your impl */ private final UndoRedo undo = new UndoRedo.Manager(); public UndoRedo getUndoRedo () { return undo; } ... rest of the NavigatorPanelWithUndo impl ... }
In certain situations it's not desired to show NavigatorPanel implementations related to DataObject of active Node in Navigator window. Typically you need to have active Node of some type, so that actions in the system works properly. But you don't want to show NavigatorPanels that "come" with such active Node.
Steps to remove such NavigatorPanels:getPanelsPolicy()
method.
Explorer views comes handy when showing Nodes in varienty of situations and it is just natural to be able to integrate them into Navigator window. Working with explorer views is described at ExplorerUtils javadoc. Integration with Navigator is easy and there are only subtle differencies from integration into TopComponent.
Steps to integrate some kind of Explorer View into Navigator:NavigatorPanel
interface and return created explorer
view from getComponent()
method. Creating explorer view
is described in ExplorerUtils.
getLookup()
method of NavigatorPanel
.
panelActivated
and panelDeactivated
.
public class ListViewNavigatorPanel extends JPanel implements NavigatorPanel, ExplorerManager.Provider { private ExplorerManager manager; private ListView listView; private Lookup lookup; private Action copyAction; public ListViewNavigatorPanel () { manager = new ExplorerManager(); ActionMap map = getActionMap(); copyAction = ExplorerUtils.actionCopy(manager); map.put(DefaultEditorKit.copyAction, copyAction); map.put(DefaultEditorKit.cutAction, ExplorerUtils.actionCut(manager)); map.put(DefaultEditorKit.pasteAction, ExplorerUtils.actionPaste(manager)); map.put("delete", ExplorerUtils.actionDelete(manager, true)); // or false lookup = ExplorerUtils.createLookup(manager, map); listView = new ListView(); fillListView(listView); add(listView); } public String getDisplayName() { return "List view panel"; } public String getDisplayHint() { return "List view based navigator panel"; } public JComponent getComponent() { return this; } public void panelActivated(Lookup context) { ExplorerUtils.activateActions(manager, true); } public void panelDeactivated() { ExplorerUtils.activateActions(manager, false); } public Lookup getLookup() { return lookup; } public ExplorerManager getExplorerManager() { return manager; } private void fillListView(ListView listView) { try { Node testNode = new AbstractNode(Children.LEAF); manager.setRootContext(testNode); manager.setSelectedNodes(new Node[]{testNode}); } catch (PropertyVetoException ex) { Exceptions.printStackTrace(ex); } } }
The Common Palette content is a two-level hierarchy. The top-most level are Categories, the Category children are Items. It's possible to select (highlight) items in the palette panel using a mouse or keyboard and then inserted/dropped into an editor that supports the palette.
The palette content can come from two different sources:
The following steps must be taken if a module wants to define its own palette content as a hierarchy of folders and files in its XML layer:
<filesystem> <folder name="MyModulePalette"> <folder name="Category1"> <file name="PaletteItem_1.myitem" url="palette/PaletteItem_1.xml" /> <file name="PaletteItem_2.myitem" url="palette/PaletteItem_2.xml" /> <file name="PaletteItem_3.myitem" url="palette/PaletteItem_3.xml" /> </folder> <folder name="Category2"> <file name="PaletteItem_4.myitem" url="palette/PaletteItem_4.xml" /> <file name="PaletteItem_5.myitem" url="palette/PaletteItem_5.xml" /> <file name="PaletteItem_6.myitem" url="palette/PaletteItem_6.xml" /> </folder> </folder> </filesystem>
class MyPaletteActions extends PaletteActions { public Action getPreferredAction(Lookup lookup) { Node itemNode = (Node)lookup.lookup( Node.class ); if( null != itemNode ) { return new InsertItemAtDefaultLocationAction( itemNode ); } return null; } public Action[] getCustomItemActions(Lookup lookup) { Node itemNode = (Node)lookup.lookup( Node.class ); if( null != itemNode ) { return new Action[] { new CustomizeItemAction( itemNode ) }; } return null; } public Action[] getCustomCategoryActions(Lookup lookup) { Node categoryNode = (Node)lookup.lookup( Node.class ); if( null != categoryNode ) { return new Action[] { new CustomizeCategoryAction( categoryNode ) }; } return null; } public Action[] getImportActions() { return new Action[] { new ImportItemsFromWebAction() }; } public Action[] getCustomPaletteActions() { return null; //no custom actions for palette's root } }
class MyClass { PaletteController controller; PaletteController initializePalette() { try { controller = PaletteFactory.createPalette( "MyPalette", new MyPaletteActions() ); } catch (IOException ex) { ex.printStackTrace(); return; } controller.addPropertyChangeListener( new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { if( PaletteController.PROP_SELECTED_ITEM.equals( evt.getPropertyName() ) ) { Lookup selItem = controller.getSelectedItem(); if( null == selItem ) { //nothing is selected in palette } else { Node selNode = (Node)selItem.lookup( Node.class ); if( null != selNode ) { //change mouse cursor for editor window to indicate //the type of palette item that can be dropped changeCursorInEditorWindow( selNode ); } } } } }); return controller; } }
class MyEditorTopComponent extends TopComponent { private MyEditorTopComponent() { this( new InstanceContent() ); } private MyEditorTopComponent( InstanceContent content ) { super( new AbstractLookup( content ) ); content.add( initializePalette() ); initEditorComponents(); } PaletteController controller; private PaletteController initializePalette() { if( null == controller ) { controller = PaletteFactory.createPalette( "MyPalette", new MyPaletteActions() ); } return controller; } }
When an item is selected in the palette and user clicks into the editor window then the module can ask for selected item by calling PaletteController.getSelectedItem(). This method returns a Lookup that holds object(s) representing the selected item. After the item is inserted into the editor window the module may clear palette's selection (PaletteController.clearSelection()) or leave the item selected to implement 'multi drop' insertion scenario.
It is possible to filter palette content and hide some categories and/or items from the user by extending PaletteFilter class.
class MyPaletteFilter extends PaletteFilter { public boolean isValidItem(Lookup lookup) { Node itemNode = (Node)lookup.lookup( Node.class ); return isItemVisibleInCurrentEditorContext( itemNode ); } public boolean isValidCategory(Lookup lookup) { Node categoryNode = (Node)lookup.lookup( Node.class ); return isCategoryVisibleInCurrentEditorContext( categoryNode ); } private boolean isItemVisibleInCurrentEditorContext( Node item ) { boolean res = true; //check current cursor positions and/or item type and decide whether //the item is valid, i.e. can be selected and dropped into editor return res; } private boolean isCategoryVisibleInCurrentEditorContext( Node item ) { boolean res = true; //check current cursor positions and/or category type and decide whether //the category is valid, i.e. its items can be selected and dropped into editor return res; }
Then initialize the palette using the following method:
MyPaletteFilter filter = new MyPaletteFilter(); PaletteController controller = PaletteFactory.createPalette( "MyPalette", new MyPaletteActions(), filter, null );
It is necessary to call PaletteController.refresh() to refresh and repaint the palette window whenever the filtering condition has changed:
myPaletteFilter.setShowSomeSpecialCategories( false ); paletteController.refresh();
The initial state of the palette can be overridden by setting appropriate attributes to palette model. The list of supported attributes is defined in PaletteController class. If the palette model is create from Nodes then the attributes are extracted by calling Node.getValue() method on the root Node and category and item nodes. If the palette model is defined as folders and files in the layer then the attributes are extracted by calling FileObject.getAttribute().
In the example below the palette will not show item names initially
(only icons are visible), the user can change this in palette's context menu.
Category1 is read-only therefore the user cannot remove it.
Category2 is not initially visible, the user can change this in
palette's customizer.
<filesystem> <folder name="MyModulePalette"> <attr name="showItemNames" stringvalue="false"/> <folder name="Category1"> <attr name="isReadonly" stringvalue="true"/> <file name="PaletteItem_1.myitem" url="palette/PaletteItem_1.myitem" /> <file name="PaletteItem_2.myitem" url="palette/PaletteItem_2.myitem" /> <file name="PaletteItem_3.myitem" url="palette/PaletteItem_3.myitem" /> </folder> <folder name="Category2"> <attr name="isVisible" stringvalue="false"/> <file name="PaletteItem_4.myitem" url="palette/PaletteItem_4.myitem" /> <file name="PaletteItem_5.myitem" url="palette/PaletteItem_5.myitem" /> <file name="PaletteItem_6.myitem" url="palette/PaletteItem_6.myitem" /> </folder> </folder> </filesystem>
It is possible to add new palette categories and/or palette item at runtime when the palette window is already visible.
Adding a new category is very straight-forward, it basically means creating a new folder under palette's root folder in XML layer:
FileObject paletteRoot = FileUtil.getConfigFile( "MyModulePalette" ); paletteRoot.createFolder( "NewCategory" );
Adding a new item is a similar task:
FileObject paletteRoot = FileUtil.getConfigFile( "MyPalette" ); FileObject targetCategoryFO = paletteRoot.getFileObject( "CategoryName" ); DataFolder targetCategoryDF = DataFolder.findFolder( targetCategoryFO ); DataObject dobj = (DataObject)itemNode.getLookup().lookup( DataObject.class ); dobj.copy( targetCategoryFolder );
Please refer to Nodes API in case the palette content is defined as a hierarchy of arbitrary Nodes.
The following steps must be taken when writing the item using the support provided by this module:
SearchProvider.evaluate
method
like suggested in its javadoc.
<folder name="QuickSearch"> <folder name="Category1_ID"> <attr name="position" intvalue="300"/> <file name="org-netbeans-module1-package1-Provider1Impl.instance"/> </folder> <folder name="Category2_ID"> <!--Attribute for localization - provide localized display name of category!--> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.yourmodule.YourBundle"/> <!--Attribute for command prefix - used to narrow search to this category only!--> <attr name="command" stringvalue="p"/> <!--Attribute for category ordering!--> <attr name="position" intvalue="200"/> <!--Note that multiple providers can contribute to one category!--> <file name="org-netbeans-module2-package2-Provider2Impl.instance"/> <file name="org-netbeans-module2-package3-Provider3Impl.instance"/> </folder> </folder>Syntax explanation:
Quick Search UI shows search results divided into visually separeted sections,
called categories. Several SearchProvider
implementations may decide to display
their results in one shared category of results in Quick Search UI.
In order to share category, module writers have to agree on shared category and its properties, especially its name. It means that all providers (possibly in different NetBeans modules) need to be registered under the same folder, as shown below:
SharedCategory
such as
display name, position and command prefix.
<folder name="QuickSearch"> <folder name="SharedCategory"> <attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.yourmodule.YourBundle"/> <attr name="command" stringvalue="p"/> <attr name="position" intvalue="200"/> <file name="org-netbeans-module1-package1-Provider1Impl.instance"> <attr name="position" intvalue="300"/> </file> </folder> </folder>
SharedCategory
,
as they were already defined by Provider 1.
Note that module dependency on the module of Provider 1 is needed
to ensure that SharedCategory
is fully defined.
<folder name="QuickSearch"> <folder name="SharedCategory"> <file name="org-netbeans-module2-package2-Provider2Impl.instance"/> <attr name="position" intvalue="200"/> </file> </folder> </folder>
<folder name="QuickSearch"> <folder name="SharedCategory"> <file name="org-netbeans-module2-package3-Provider3Impl.instance"/> <attr name="position" intvalue="100"/> </file> </folder> </folder>
FirstCategory
to be first, and SecongCategory
to be second :),
which means that FirstCategory
and its results will be
displayed above Secondcategory
in QuickSearch results window.
<folder name="QuickSearch"> <folder name="SecondCategory"> <attr name="position" intvalue="300"/> ... </folder> <folder name="FirstCategory"> <attr name="position" intvalue="200"/> ... </folder> </folder>
spi.quicksearch
module, so its functionality is automatically always available by default.
However, if your module wants to disable "Recent Searches" or any other
category, follow the steps below:
spi,quicksearch
,
on which you probably already depend.
<folder name="QuickSearch"> <folder name="Recent_hidden"> </folder> </folder>"Recent" is a name of category for "Recent Searches" provider and by appending "_hidden" suffix you are telling system to "hide" it. This technique can be used also to disable invidual search providers.
<folder name="Toolbars"> <folder name="QuickSearch"> <attr name="SystemFileSystem.localizingBundle" stringvalue="com.myapp.mymodule.MyBundle"/> <file name="org-netbeans-modules-quicksearch-QuickSearchAction.shadow"> <attr name="originalFile" stringvalue="Actions/Edit/org-netbeans-modules-quicksearch-QuickSearchAction.instance"/> </file> </folder> </folder>
com.myapp.mymodule.MyBundle
in the xml registration
above with path to your properties file, in which you'll define
localized name of Quick Search toolbar:
Toolbars/QuickSearch=Quick Search
By default, providers for searching in actions and recent searches will be enabled. Web search provider is disabled by default, see use case below for info how to turn it on.
<folder name="QuickSearch"> <folder name="WebSearch"> <!--Attribute for localization - provide localized display name of category!--> <attr name="SystemFileSystem.localizingBundle" stringvalue="com.myapp.mymodule.MyBundle"/> <!--Attribute for command prefix - used to narrow search to this category only!--> <attr name="command" stringvalue="g"/> <!--Attribute for category ordering!--> <attr name="position" intvalue="200"/> <!--Note that multiple providers can contribute to one category!--> <file name="org-netbeans-modules-quicksearch-web-WebQuickSearchProviderImpl.instance"/> </folder> </folder>You can also add branding for
org.netbeans.modules.quicksearch.web
Bundle to restrict
the search to a particular site only:
quicksearch.web.site=mywebsite.comAnd you can also restrict the search to some parts your website only:
quicksearch.web.url_patterns=mywebsite.com/docs,mywebsite.com/files/tutorials
Border
instance into
UIManager
under key nb.quicksearch.border
.
The main feature of the Task List SPI is the ability to 'plug-in' additional task providing modules that generate tasks for the task list window.
The plugable task scanners can either push new Tasks to the Task List window whenever they want - PushTaskScanner - or they can inherit from FileTaskScanner and the Task List framework will actively poll them for new tasks for each file under the current scanning scope (see below).
Scanner instances are registered in XML layer in folder "/TaskList/Scanners". The framework keeps track of modified files and notifies the scanners whenever a file under the scanning scope needs to be rescanned.
Scanning scope defines which files/folders will be scanned for tasks. The default implementation includes scopes for currently edited file, scope for files and folders in the main project and projects that depend on it and scope for all opened projects.
Additional scopes may be provided by extending TaskScanningScope class and registering instances in folder "/TaskList/ScanningScopes" in XML layer.
Tasks are organized into Groups according to their importance. Each task can be in one group only. The default implementation includes "Error", "Warning" and "TODO" groups. Additional groups can be registered in XML layer in folder "/TaskList/Groups". Each task group is identified by its unique name.
In order for the action to show up in Keyboards Shortcut dialog you need the action defined in the layer file under "Actions" folder and have the shortcut defined there under "Keymaps/<Profile Name>" linking to your action.
<folder name="Actions" > <folder name="Window"> <file name="org-netbeans-core-actions-PreviousViewCallbackAction.instance"/> </folder> </folder> <folder name="Keymaps"> <folder name="NetBeans"> <file name="S-A-Left.shadow"> <attr name="originalFile" stringvalue="Actions/Window/org-netbeans-core-actions-PreviousViewCallbackAction.instance"/> </file> </folder> </folder>
The mentioned Action has to be a subclass of org.openide.util.actions.CallbackSystemAction
. It does not necessarily has to
perform the action, it's just a placeholder for linking the shortcut. You might want to override it's getActionMapKey()
and give it a
reasonable key.
The actual action that does the work in your component (preferably a simple Swing javax.swing.Action
)
is to be put into your TopComponent
's ActionMap
. The key for the ActionMap
has to match the key defined in the global action's getActionMapKey()
method.
getActionMap().put("PreviousViewAction", new MyPreviousTabAction());
This way even actions from multiple TopComponent
s with the same gesture (eg. "switch to next tab") can share the same configurable shortcut.
Note: Don't define your action's shortcut and don't put it into any of the TopComponent
's
javax.swing.InputMap
. Otherwise the component would not pick up the changed shortcut from the
global context.
For general overview of the concepts of NetBeans action system and related UI elements, together with code samples, see chapter 5, of NetBeans Platform for Beginners by Jason Wexbridge and Walter Nyland.
A: You can change the format of your wizard's title by WizardDescriptor.setTitleFormat(MessageFormat format) and rid of 'wizard' word in the default wizard's title.
For general overview of the concepts related to nodes and explorers, together with code samples, see chapter 7, of NetBeans Platform for Beginners by Jason Wexbridge and Walter Nyland.
For general overview of the filesystem concepts, related topics, together with code samples, see chapter 3, of NetBeans Platform for Beginners by Jason Wexbridge and Walter Nyland. Many of the usecases are described at the overall documentation, in a way how to register a mime type. Some of the additional usecases are covered here.
See documentation about dynamically changing the system filesystem.
There is an SPI but additional implementations are not expected. The API is most important.
Simple usage example:
InputOutput io = IOProvider.getDefault().getIO("My Window", true); io.select(); OutputWriter w = io.getOut(); w.println("Line of plain text."); OutputListener listener = new OutputListener() { public void outputLineAction(OutputEvent ev) { StatusDisplayer.getDefault().setStatusText("Hyperlink clicked!"); } public void outputLineSelected(OutputEvent ev) { // Let's not do anything special. } public void outputLineCleared(OutputEvent ev) { // Leave it blank, no state to remove. } }; w.println("Line of hyperlinked text.", listener, true);
Often many people require ability to create a "clever" template - e.g. write piece of simple text and at the time of its processing do some advanced changes to it using either scripting or templating languages.
This traditionally used to be a bit complicated task, however since
version 6.1 there are new interfaces
The support was moved to a new module; please see api.templates module for more information.
Loaders/folder/any/Actions
so if any module wishes
to extend, hide or reorder some of them it can just register its actions there.<folder name="Loaders" > <folder name="folder" > <folder name="any" > <folder name="Actions" > <file name="org-mymodule-MyAction.instance" > <attr name="instanceCreate" stringvalue="org.mymodule.MyAction" /> </file> </folder> </folder> </folder> </folder>As described in general actions registration tutorial. This functionality is available since version 5.0 of the loaders module. Please use
OpenIDE-Module-Module-Dependencies: org.openide.loaders > 5.0
in your
module dependencies.
In version 5.8 all the standard loaders were changed to read actions from layer:
Loaders/text/xml/Actions
Loaders/content/unknown/Actions
Loaders/application/x-nbsettings/Actions
DataObject
s produced by your DataLoader
and you
are either using DataNode
or its subclass, you can just override
protected String actionsContext()
method to return non-null
location of context in layers from where to read the actions.
The usual value should match Loaders/mime/type/Actions
scheme,
for example java is using Loaders/text/x-java/Actions
, but
the name can be arbitrary.
This functionality is available since version 5.0 of the loaders module. Please use
OpenIDE-Module-Module-Dependencies: org.openide.loaders > 5.0
in your
module dependencies.
To maintain binary compatibility, method implementations may be injected at runtime, in a form of a superclass in the class' inheritance hierarchy. Modules compiled against older version of APIs which contains MethodReferences to methods removed from the oficial APIs will be then linked according to JVM Resolution algorithm to a matching method present in the superclass of the referenced type.
Annotations are used to instruct the ClassLoader to make transformations to the API classes. PatchFor causes the annotated class to be injected as a superclass of the API class identified by the annotation's value. ConstructorDelegate marks a method, which is called as constructor implementation in the case that it is necessary to preserve a constructor for binary compatibility.
For general overview of the concepts related to nodes and explorers, together with code samples, see chapter 7, of NetBeans Platform for Beginners by Jason Wexbridge and Walter Nyland.
XXX no answer for arch-usecases
Use-cases can be found in org.openide.util.ui module arch summary.
How can I specify (in the xml, or programmatically) that this service should only be added to the Lookup if the platform is Windows? >
In general there are three ways to achieve this.It is possible to write a specific module and enable it only on windows. See os specific modules documentation. Then you can put a registration of your instance into your module's META-INF/services directory and it will be available only on Windows.
Another possibility that does not require new module, but which executes
a code on startup (which may have performance implications) is to use methodvalue
attribute. Register your instance in layer using your-Object.instance
file
as described at
services
documentation and in your factory method either return the instance
your want or null
depending on result of
Utilities.isWindows() call.
In some cases, the interface for which you will register an implementation permits a
no-operation semantics. For example, InstalledFileLocator.locate(...)
can
return a valid File
, or null. You could always register an
InstalledFileLocator
instance yet disable it on non-Windows platforms
(always returning null).
Q: I have more modules one of them providing the core functionality and few more that wish to extend it. What is the right way to do it? How does the Netbeans platform declare such extension point?
Start with declaring an extension interface in your
core module and put it into the module's public packages. Imagine
for example that the core module is in JAR file org-my-netbeans-coremodule.jar
and already contains in manifests line like
OpenIDE-Module: org.my.netbeans.coremodule/1
and wants
to display various tips of the day provided by other modules and thus defines:
package org.my.netbeans.coremodule; public interface TipsOfTheDayProvider { public String provideTipOfTheDay (); }
And in its manifest adds line
OpenIDE-Module-Public-Packages: org.my.netbeans.coremodule.*
to specify that this package contains exported API and shall be
accessible to other modules.
When the core module is about to display the tip of the day it can ask
the system for all registered instances of the TipsOfTheDayProvider
,
randomly select one of them:
import java.util.Collection; import java.util.Collections; import org.openide.util.Lookup; Lookup.Result result = Lookup.getDefault ().lookup (new Lookup.Template (TipsOfTheDayProvider.class)); Collection c = result.allInstances (); Collections.shuffle (c); TipsOfTheDayProvider selected = (TipsOfTheDayProvider)c.iterator ().next ();
and then display the tip. Simple, trivial, just by the usage of
Lookup interface once
creates a registry that other modules can enhance. But such enhancing
of course requires work on the other side. Each module that would like
to register its TipsOfTheDayProvider
needs to depend on the
core module - add
OpenIDE-Module-Module-Dependencies: org.my.netbeans.coremodule/1
into its manifest and write a class with its own implementation of the
provider:
package org.my.netbeans.extramodule; class ExtraTip implements TipsOfTheDayProvider { public String provideTipOfTheDay () { return "Do you know that in order to write extension point you should use Lookup?"; } }
Then, the only necessary thing is to register such class by using the
J2SE standard META-INF/services/org.my.netbeans.coremodule.TipsOfTheDayProvider
in the module JAR containing just one line:
org.my.netbeans.extramodule.ExtraTip
and your modules are now ready to communicate using your own extension point.
If you are interested in logging from inside your module, or in writing your own log handler or in configuring the whole system, then best place to start is the NetBeans logging guide.
For general overview of the concepts, together with code samples, see chapter 6, of NetBeans Platform for Beginners by Jason Wexbridge and Walter Nyland.
protected void componentDeactivated () { // close window group containing propsheet, but only if we're // selecting a different kind of TC in the same mode boolean closeGroup = true; Mode curMode = WindowManager.getDefault().findMode(this); TopComponent selected = curMode.getSelectedTopComponent(); if (selected != null && selected instanceof FooTopComponent) closeGroup = false; if (closeGroup) { TopComponentGroup group = WindowManager.getDefault().findTopComponentGroup(TC_GROUP); if (group != null) { group.close(); } } }