The API is widely used by all sorts of IDE modules which need to work with Java sources. They can obtain the classpath or boot classpath for a file (if there is one), find out where its source root is, find sources corresponding to bytecode class files, find all sources or classpaths corresponding to open projects, find Javadoc, etc. The SPI is intended mainly for Java platform and library providers, and project type providers, to declare all of this information.
There are 3 types of progress indication:
The default location of the progress indication is the status bar which aggregates all tasks running in the IDE that show progress. However it's possible to exclude the task from the default location and show the progress in one's custom dialog component. In such a case the same task should not appear in the status line component as well.
It's possible to request cancelling the task from status line progress aggregator if the task allows cancelling.
Progress tasks that get started as a result of explicit user action takes precedence in the status line docked component over tasks that are triggered by the system. (say filesystem refresh for example)
The most common usecase of the API looks like this:
ProgressHandle handle = ProgressHandleFactory.creatHandle("My custom task");
...
// we have 100 workunits
// at this point the task appears in status bar.
handle.start(100);
...
handle.progress(10);
...
handle.progress("half way through", 50);
...
handle.progress(99);
// at this point the task is finished and removed from status bar
// it's not realy necessary to count all the way to the limit, finish can be called earlier.
// however it has to be called at the end of the processing.
handle.finish();
In case your usage of the API
then you should consider using the aggregating version of APIs which is similar to the simple APIs but has distinctive differences and additions that allow for more complex scenarios.
It allows to compose the progress bar from 1+ independent sources, all sharing proportional piece of the progress bar. Additionally you can monitor the task's overall progress from one central place and possibly add more contributing sources of the progress during processing.
// let's have a factory for client code that performs some part of the job to be done..
Lookup.Result res = Lookup.getDefault().lookup(new LookupTemplate(MyWorkerFactory.class));
Iterator it = res.allInstances().iterator();
ProgressContributor[] contribs = new ProgressContributor[res.allInstances().size()];
int i = 0;
while (it.hasNext()) {
MyWorkerFactory prov = (MyWorkerFactory)it.next();
contribs[i] = AggregateProgressFactory.createProgressContributor("Module X contribution");
MyWorker worker = prov.createWorker(contribs[i]);
//... snip ... do something with the worker..
i = i + 1;
}
AggregateProgressHandle handle = AggregateProgressFactory.createHandle("My Task", contribs, null, null);
// non-cancellable and with out output link.
// calling start() at the time when the actual long running task starts processing
handle.start("here we go");
// ...snip...
// now the individual MyWorker instances log their progress.
// possibly in other threads too..
// ... snip...
//
if (myConditionThatSpawnsAnotherContributor()) {
ProgressContributor cont = AggregateProgressFactory.createProgressContributor("Additional exceptional contribution");
handle.addContributor(cont);
// ... snip ...
}
// the task is finished when all the ProgressContributors finish..
${...}.
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\""}.
editable having
true/false.
The API is small and it only allows to explicitly show or hide the completion window.
It's being used by code templates that need to explicitly show the code completion
window when tabbing to a particular parameter.
There may be certain actions that want to ensure that the code completion is hidden
at the time when they are invoked. For example the actions pasting the content
of the completion item into the document.
Completion infrastructure needs to obtain the results that are then displayed
in the completion window.
There are three types of displayed results related to the current caret offset:
For the purpose of obtaining these completion results
CompletionProvider
exists.
There may be an arbitrary number of independent completion providers for
a single completion popup window.
The completion providers are registered through the xml layer into
Editors/<mime-type>/CompletionProviders. Once the document
with the particular mime-type gets loaded the corresponding completion providers
will get instantiated and used.
Threading:
The code completion's infrastructure invokes the requests
for the completion results in the AWT thread.
Therefore all the methods of the completion providers are invoked
in AWT thread but they may reschedule their processing into other threads.
The completion provider creates a task that computes the resulting
data that will then be displayed by the code completion infrastructure.
The task creation and computation are called synchronously
from the AWT event dispatch thread.
However there can be potentially long-running tasks (e.g. working with MDR)
that are not desirable to be run in AWT thread.
Therefore the completion infrastructure provides a listener
to which the completion task notifies the results.
The support class
AsyncCompletionTask allows to post the task computation
into RequestProcessor.
The completion task computes a collection of completion items
which are then collected by the completion infrastructure and displayed.
Displaying. Each completion item must be able to display itself in a JList.
Sorting. The completion items may come from different completion providers
and they must be sorted before displaying. The sort order
should not only be alphabetical but it should also allow a prioritization
of the items according to their importance in the given context.
Actions. The interaction of the user with the completion item
is done by interacting with item's input map and action map.
Documentation. The item may want to display additional
detailed information in a documentation popup window.
A module in the IDE has information whether data shown in the Error Stripe is up-to-date or not. The Error Stripe may change the appearance according to this knowledge.
Implement the UpToDateStatusProvider that provides up-to-date status. Be sure that it fires PropertyChangeEvent when this status is changed.
Implement the UpToDateStatusProviderFactory that creates an instance of your UpToDateStatusProvider for a given JTextComponent and install it as described here.
The code folding structure (fold hierarchy) relates
to javax.swing.JTextComponent instance in one-to-one relationship.
To find the code folding hierarchy instance for the given non-null text component
the following code snippet can be used:
JTextComponent editorComponent = ...
FoldHierarchy hierarchy = FoldHierarchy.get(editorComponent);
The tree-based hierarchy has one non-removable and non-collapsable root fold that covers the whole document. It can be obtained by
FoldHierarchy foldHierarchy = ...
Fold rootFold = hierarchy.getRootFold();
The children folds of the root fold (or children folds) can be obtained by
// the hierarchy must be locked prior exploration or manipulation
hierarchy.lock();
try {
Fold rootFold = ...
int foldCount = rootFold.getFoldCount();
for (int i = 0; i < foldCount; i++) {
Fold childFold = rootFold.getFold(i);
}
} finally {
hierarchy.unlock();
}
Index of the child in its parent can be found by
hierarchy.lock();
try {
Fold rootFold = ...
int foldIndex = rootFold.getFoldIndex(childFold);
} finally {
hierarchy.unlock();
}
In the given fold hierarchy find the nearest fold right at or after the given offset and collapse it.
hierarchy.lock();
try {
Fold fold = FoldUtilities.findNearestFold(hierarchy, offset);
hierarchy.collapse(fold);
} finally {
hierarchy.unlock();
}
In the given fold hierarchy expand all folds that are currently collapsed.
FoldUtilities.expand(hierarchy, null);
In the given fold hierarchy collapse all e.g. javadoc folds that are currently collapsed.
The example can be generalized to any fold type.
FoldUtilities.collapse(hierarchy, JAVADOC_FOLD_TYPE);
In the given fold hierarchy expand the fold into which the caret
is going to be moved by Caret.setDot(offset).
The hierarchy must be locked and this example assumes that the underlying
document is already read-locked e.g. by Document.render().
FoldHierarchy hierarchy = FoldHierarchy.get(caretComponent);
hierarchy.lock();
try {
Fold collapsed = FoldUtilities.findCollapsedFold(hierarchy, offset, offset);
if (collapsed != null && collapsed.getStartOffset() < offset &&
collapsed.getEndOffset() > offset) {
hierarchy.expand(collapsed);
}
} finally {
hierarchy.unlock();
}
In the given fold hierarchy start to listen on all changes
done in the hierarchy.
This is actually used e.g. in the Editor's View Hierarchy that needs
to refresh views based on the fold changes.
hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
public void foldHierarchyChanged(FoldHierarchyEvent evt) {
// Hierarchy does not need to be locked here
//
// evt.getAffectedStartOffset() and getAffectedEndOffset()
// give text area affected by the fold changes in the event
}
});
Listen on the hierarchy changes
and refresh the views in the text area affected by the fold change.
Inspect the collapsed folds in the affected area
because special views need to be created for the collapsed folds.
The actual code in the View Hierarchy is somewhat different
but the one given here is more descriptive.
hierarchy.addFoldHierarchyListener(new FoldHierarchyListener() {
public void foldHierarchyChanged(FoldHierarchyEvent evt) {
for (Iterator collapsedFoldIterator
= FoldUtilities.collapsedFoldIterator(hierarchy,
evt.getAffectedStartOffset(),
evt.getAffectedEndOffset()
);
it.hasNext();
) {
Fold collapsedFold = (Fold)it.next();
// Create special view for the collapsedFold
}
}
});
Manipulation of the folds is designed to be done by fold managers.
Those classes implement FoldManager interface in the SPI.
At initialization time they are given instance of FoldOperation
through which they can create, add or remove the fold instances.
To create and use a new FoldManager instance
it's necessary to
public class MyFoldManager implements FoldManager { // or extends AbstractFoldManager
...
}
public class MyFoldManager ...
...
public static final class Factory implements FoldManagerFactory {
public FoldManager createFoldManager() {
return new MyFoldManager();
}
}
}
NbJavaSettingsInitializer)
public class MySettingsInitializer ...
public void updateSettingsMap(Class kitClass, Map settingsMap) {
...
settingsMap.put(SettingsNames.CODE_FOLDING_ENABLE, Boolean.TRUE);
}
}
Create a new fold and add it to the hierarchy. The operation
is performed by the fold manager either at initialization phase
(in the initFolds() which gets called automatically
by the infrastructure) or at any other time when the fold manager's
operation gets invoked (usually by a listener that the fold manager
attaches to be notified about changes that can cause the folds structure
to be changed - e.g. a parsing listener for java folds).
Operations that manipulate the hierarchy are done
in terms of a valid transaction over the fold hierarchy.
Transactions allow to fire the collected changes as a single
FoldHierarchyEvent at the time when they are committed.
// In the FoldManager's context
FoldOperation operation = getOperation();
FoldHierarchyTransaction transaction = operation.openTransaction();
try {
Fold fold = operation.createFold(...);
operation.addFoldToHierarchy(fold, transaction);
} finally {
transaction.commit();
}
Remove the existing fold from the hierarchy
// In the FoldManager's context
FoldOperation operation = getOperation();
FoldHierarchyTransaction transaction = operation.openTransaction();
try {
Fold fold = ...
operation.removeFoldFromHierarchy(fold, transaction);
} finally {
transaction.commit();
}
org.netbeans.spi.editor.fold.FoldManagerFactory classes)
in the Editors/<mime-type>/FoldManager layer folder.
org.netbeans.spi.editor.completion.CompletionProvider classes)
in the Editors/<mime-type>/CompletionProviders layer folder.
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).
MimeLookup childLookup(String mimeType) method in MimeLookup.
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));
MimeLookup lookup = MimeLookup.getMimeLookup("text/x-jsp").childLookup("text/x-java");
<filesystem>
<folder name="Editors">
<folder name="text">
<folder name="x-java">
<file name="org-netbeans-modules-editor-mimelookuptest-MimeSpecificObject.instance"/>
</folder>
</folder>
</folder>
</filesystem>
Lookup of this object will look like:
MimeLookup lookup = MimeLookup.getMimeLookup("text/x-java");
MimeSpecificObject mso = (MimeSpecificObject) lookup.lookup(MimeSpecificObject.class);
Class2LayerFolder.
Let's register FoldManagerFactory.class to FoldManager folder.
First we need to implement the interface:
public class FoldManagerClass2LayerFolder implements Class2LayerFolder{
public FoldManagerClass2LayerFolder {
}
/* declaring the class */
public Class getClazz(){
return FoldManagerFactory.class;
}
/* assigning the declared class to folder */
public String getLayerFolderName(){
return "FoldManager";
}
/* we will not support InstanceProvider */
public org.netbeans.spi.editor.mimelookup.InstanceProvider getInstanceProvider() {
return null;
}
}
Then we need to register it to default lookup via META-INF/services registration. We need to create
a folder structure META-INF/services and place there a file org.netbeans.spi.editor.mimelookup.Class2LayerFolder
with the content FoldManagerClass2LayerFolder
having this we can register appropriate object to specific folder:
<filesystem>
<folder name="Editors">
<folder name="text">
<folder name="x-java">
<folder name="FoldManager">
<file name="org-netbeans-modules-editor-MyFoldManagerFactory.instance"/>
</folder>
</folder>
</folder>
</folder>
</filesystem>
Lookup of this object will look like:
MimeLookup lookup = MimeLookup.getMimeLookup("text/x-java");
FoldManagerFactory foldManagerFactory = (FoldManagerFactory) lookup.lookup(FoldManagerFactory.class);
or, if there should be more instances of the FoldManagerFactory:
MimeLookup lookup = MimeLookup.getMimeLookup("text/x-java");
Collection foldManagerFactories = lookup.lookup(new Lookup.Template(FoldManagerFactory.class)).allInstances();
Notice, that the FoldManagerFactory object is found in "FoldManager" folder. It is not necessary for client
of the API to know about some folder structure.
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.
InstanceProvider and inheritance will be used together in one use case. Example of
editor context menu construction will be used. Each module can register its actions to
editor context menu via xml layer. As context menu is mime type sensitive (java editor has
different menu items than plain editor) the actions are registered into mime specific layer folders
to subfolder "Popup". When the context menu is constructing, the xml layer is scanned for the
action items located in the "Popup" subfolder of specific mime folder. In addition to
this there is inheritance mechanism used to share global actions (like Cut, Copy, Paste) over all mime
types context menus, thus not only the action items from actual mime type are considered. All
underlaying mime types are scanned also and the result is context menu with merged action items.
For example JSP scriplet context menu should be merged from action items gathered over:
MimeLookup will solve also this embeding and the gathering of the context menu items will
looks such simply as:
MimeLookup lookup = MimeLookup.getMimeLookup("text/x-jsp").childLookup("text/x-java");
PopupActions actions = (PopupActions) lookup.lookup(PopupActions.class);
List popupActions = actions.getPopupActions();
where PopupActions is implementation of InstanceProvider and PopupActions.class needs to be
registered to "Popup" subfolder using Class2LayerFolder implementation. Let's register
this step by step.
Because action items are instances of various objects like:
InstanceProvider needs to be created for this:
public class PopupActions implements InstanceProvider{
List ordered;
public PopupActions(){
}
public PopupActions(List ordered){
this.ordered = ordered;
}
public List getPopupActions(){
List retList = new ArrayList();
for (int i = 0; i<ordered.size(); i++){
DataObject dob = (DataObject) ordered.get(i);
InstanceCookie ic = (InstanceCookie)dob.getCookie(InstanceCookie.class);
if (ic!=null){
try{
if (String.class.isAssignableFrom(ic.instanceClass()) ||
Action.class.isAssignableFrom(ic.instanceClass()) ||
SystemAction.class.isAssignableFrom(ic.instanceClass()) ||
JSeparator.class.isAssignableFrom(ic.instanceClass())){
Object instance = ic.instanceCreate();
retList.add(instance);
}
}catch(IOException ioe){
ioe.printStackTrace();
}catch(ClassNotFoundException cnfe){
cnfe.printStackTrace();
}
} else{
retList.add(dob.getName());
}
}
return retList;
}
public Object createInstance(List ordered) {
return new PopupActions(ordered);
}
}
This InstanceProvider needs to be declared in Class2LayerFolder implementation:
public class PopupInitializer implements Class2LayerFolder{
public PopupInitializer() {
}
public Class getClazz(){
return PopupActions.class;
}
public String getLayerFolderName(){
return "Popup"; //NOI18N
}
public InstanceProvider getInstanceProvider() {
return new PopupActions();
}
}
Now, we just need to register PopupInitializer into default lookup via META-INF/services
registration and the initialization is done.
FontColorSettings fcs = (FontColorSettings) MimeLookup.getMimeLookup("text/x-java").lookup(FontColorSettings.class);
AttributeSet fontColors = fcs.getFontColors(FontColorNames.SELECTION_COLORING);
This will resolve Fonts ant Colors settings of the coloring used
for selection for the mime-type "text/x-java" in AttributeSet representation.
Lookup.Template by registering LookupListener
on the returned Lookup.Result.
LookupResult fontsColors = MimeLookup.getMimeLookup("text/x-java").lookup(
new Lookup.Template(FontColorSettings.class));
if (fontsColors !=null) {
fontsColors.addLookupListener(new LookupListener(){
public void resultChanged(LookupEvent ev) {
Lookup.Result result = ((Lookup.Result)ev.getSource());
//... settings client response on settings change
}
});
}
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.
Mostly an SPI for use by project type providers to create the project type. Also includes a small API/SPI for other projects to find what Ant build steps are necessary to create a certain build product, for use in inter-project dependencies.
Ant project support faq:You basicaly need to do two things. First create the representation of the project properties which can be used in the GUI. Second at some time convert the objects back to the ANT properties form and store them into the project.
Different technology support modules will supply definitions of different kinds of libraries, e.g. Java JARs, that may be reused in user projects. Modules may register library predefinitions to wrap libraries they bundle. Project type providers can refer to available libraries in customizer dialogs.
The SPI should be used by modules defining particular project types, e.g. the J2SE project type. The API is to be used primarily by GUI infrastructure and some queries, though other module code may on occasion need to refer to the API.
The main use case is for project type providers to supply logical views and customizers for the project. Also for template providers to create project-aware file templates. Can also get a list of open projects, create different kinds of project-related actions, and select projects on disk.
Particular use cases are enumerated in the Javadoc for each query API. Usage consists of simple static method calls. Potentially a wide variety of modules could use these queries; implementations are typically registered by project type providers, though also by Java library and platform implementations.
Implementing NavigatorPanel interface is easy, you can copy from template basic implementation BasicNavPanelImpl.java.
Advices on important part of panel implementation:
/** JavaDataObject used as example, replace with your own data source */
private static final Lookup.Template MY_DATA = new Lookup.Template(JavaDataObject.class);
public void panelActivated (Lookup context) {
// lookup context and listen to result to get notified about context changes
curResult = context.lookup(MY_DATA);
curResult.addLookupListener(/** your LookupListener impl here*/);
Collection data = curResult.allInstances();
// ... compute view from data and trigger repaint
}
Do *not* perform any long computation in panelActivated directly, see below.Declarative registration of your NavigatorPanel impl connects this implementation with specific content type, which is type of the document, expressed in mime-type syntax, for example 'text/x-java' for java sources. Infrastructure will automatically load and show your NavigatorPanel impl in UI, when currently activated Node is backed by primary FileObject whose FileObject.getMimeType() equals to content type specified in your layer.
Writing layer registration itself is easy, you can again copy from template layer Basic Navigator Registration Layer.
Additional important info:There may be situations where linking between your Navigator view and activated Node's primary FileObject is not enough or not possible at all. This simply happens when the data you want to represent in Navigator are not accessible through primary FileObject or DataObject. Usual example is Multiview environment, where more views of one document exists.
The solution is to bind content of your Navigator view directly to your TopComponent. Then, whenever your TopComponent gets activated in the system, Navigator UI will show th content you connected to it.
Steps to do:
class AmazingTypeLookupHint implements NavigatorLookupHint {
public String getContentType () {
return "text/my-amazing-type";
}
}
Node subclass
instead of directly altering lookup of your TopComponent.
See Node.getLookup() method.
Then Navigator will show your desired content whenever your Node
subclass will be active in the system.TopComponent.getLookup() includes also results from
lookup of asociated Node. So this approach will stop
working if you change default behaviour of TopComponent.getLookup() method.
The following steps must be taken if an editor module wants to display a palette of items that can be dropped to editor window:
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 (ProgressController.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 implementing PaletteFilter interface. Calling PaletteController.setPaletteFilter() updates palette content and repaints the palette window.
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().
The following steps must be taken when writing the item using the support provided by this module:
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 TopComponents 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.
XXX no answer for arch-usecases
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.
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);
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
DataObjects 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.
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.
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();
}
}
}