ServiceType
,
and the collection of them is the
ServiceType.Registry
.
For lookup, this centers around
Lookup
and helper implementations in
org.openide.util.lookup
,
as well as interactions with the Datasystems API in
FolderLookup
.
Presently there are several varieties of services in the IDE - script types, and perhaps search types. (It is possible for a module to add more to this list.) Each variety serves quite a different function in the IDE - the only reason they are grouped together as services is that they share characteristics of needing to be installed, configured, stored, and associated with files. Within each variety, there can be any number of service types (Java classes implementing the abstract class for one of these varieties), each of which is installed with a manifest tag. Finally, each service type has at least one default instance but may have additional instances as configured by the user (or, occasionally, also installed by the module).
The user interface to services is fairly neutral. First of all, the implementation is responsible for providing a way of configuring all registered services somehow. This is currently done by providing nodes under Options (because the list of configured services is part of a project) which use node substructure to display the service varieties, service types, and instances. All of this display is performed according to JavaBeans introspection, which means that implementors of all levels have control over the appearance of these nodes and their behavior towards the user - and this also keeps the API rather thin.
Service instances may be configured by the user as Beans and these changes are saved with the project. Then, services can be associated with files that they should be applied to; the standard interface to this uses API support classes which display the current service selection for each variety on the Property Sheet, for every file that can use services.
Newer idioms in the APIs emphasize lookup and instances rather than just service types. As of NetBeans 3.2, there is a full system for registering arbitrary objects into the system (normally using XML layers) and independently finding all registered objects - this is known as lookup, and the registered objects are normally represented as files called instances. In NetBeans 3.3 there is additionally generic API support for declaring templates for services that can be used to form new instances based on some model, and a property editor providing the generic UI for selecting, modifying, and creating instances of some constrained type.
Creating a service type means creating a new class which subclasses the correct abstract superclass according to the service variety.
ScriptType
permits scripting languages to be plugged in.
IndentEngine
can control the automatic formatting of text files.
ServiceType
, and may be used from subclasses via
ServiceType.firePropertyChange(...)
.
For example:
private String someOption; public String getSomeOption () { return someOption; } public synchronized void setSomeOption (String nue) { if (! isValid (nue)) throw new IllegalArgumentException ("Invalid: " + nue); String old = someOption; someOption = nue; firePropertyChange ("someOption", old, nue); }
BeanDescriptor
with a display name and possibly a short description for the service type. This
descriptor will be used for both the category node (i.e. display of the class itself, rather than any particular instance)
as well as providing information for the instance nodes.
Providing the display name in the descriptor is especially useful as it will also serve as the default for the display name of each instance, though this may be overridden.
ServiceType
, and generally also need to include some properties from a
superclass. For example:
public BeanInfo[] getAdditionalBeanInfo () { try { return new BeanInfo[] { Introspector.getBeanInfo (DirectSuperclass.class) }; } catch (IntrospectionException ie) { ie.printStackTrace (); return null; } }
BeanContext
(in which case the displayed node can contain children according to
the context).
One method which can be overridden in service types is
ServiceType.displayName()
,
which provides a default display name for the service type instance. Generally you do not need to do anything beyond providing a
suitable display name in this method; the standard bean info for ServiceType
exposes the display name (property
name
) as a writable property, modifiable either through the Property Sheet or by directly renaming the
representation node; and when new instances are created under Project Settings, they will automatically be given unique
names if needed. Note that just specifying a display name for the bean descriptor suffices, if you do not
need there to be any difference.
You may override
ServiceType.getHelpCtx()
to provide a context-help association for your service type.
The association of service types with files is not done using serialization of the instance itself, however. Rather,
ServiceType.Handle
is used to actually store the reference. This handle currently specifies both the class name of the service type, as well as the
display name of the service instance. When the handle is resolved to a real instance, the lookup is done first by display name,
and if that fails, by retrieving the default instance of the indicated class in the registry. The handle accomplishes two things:
first, it saves space in extended file attributes by not storing the complete instance configuration (which could be somewhat
large); second, it permits users to associate service instances to files by name, so that the instance can be customized in the
registry and the customizations will then apply to all files referring to it.
Like all JavaBeans, service types should have a public default constructor to enable them to be instantiated afresh.
Normally the manifest section specifies the service type class, and the default constructor is called to make the default instance. However, you may instead specify a serialized instance in the manifest section, according to the JavaBeans specification, and this preconfigured instance will be installed instead (possibly adding multiple predefined entries to a category node). This option is especially appropriate for those who only need a single Java class to represent different configurations used by their module, but wish to install some convenient preconfigured variations.
ServiceType.Registry.find(String)
(to find an instance by display name), or
directly looking up an instance in Lookup by its class.
Available services can also be listed using lookup.
You may also create handles using
new ServiceType.Handle(ServiceType)
,
which creates a small, serializable handle storing the display name and class name, and resolve these handles later (if that is
still possible) using
ServiceType.Handle.getServiceType()
.
Whenever service types are to be stored, it is desirable to use a handle instead of the actual instance.
Finally, the IDE implementation includes standard property editors for ServiceType
as well as some standard
varieties, which displays a dialog listing all registered services of that variety, permitting the user to select one, as well as
configure the details of the service instance with a property sheet. Although these property editors are a convenient way to
select service instances, remember that the declared property type they use is ServiceType
or a subclass, while
typically code wishing to store service references will wish to retain only the handle.
ServiceType
that will have concrete service type subclasses. Nothing special is required to install such a new variety - simply begin
installing specific service types into the IDE via module manifest as usual, and appropriate variety and category nodes will
automatically appear under Project Settings. Of course, most of the work is up to you:
ServiceType
, then set the custom attribute (on the
Node.Property
or PropertyDescriptor
)
superClass
to the java.lang.Class
of your
superclass. See the
Explorer API
for more details on this.
ServiceType
, and there is no particular reason to make
this restriction; similarly, ServiceType
registration is
oriented towards registering service classes, whereas in many cases it
is necessary to use one implementing class with many instances.
Finally, a more generic registration system was desired in order to
simplify the APIs and make it easier to implement some APIs
independently of the others.
These considerations gave rise to the lookup system first introduced in NetBeans 3.2 and expanded upon for NetBeans 3.3. The center of this API from the client perspective is very simple - you can look up a class, and get an instance of that class (or a collection of them). The service provider side of it is more complex but useful lookup implementations are already provided in the IDE core; for common cases you can register an object into lookup just by adding one simple file to an XML layer.
This section of the Services API will first discuss what instances are and how to create them from files, as this is the core concept for service providers. It will discuss how you can manually retrieve sets of instances as a client, which is not used very frequently in new code but helps to understand what lookup is doing behind the scenes. Then lookup itself is discussed, and how the standard instance lookup works and how it relates to JDK's standard for service provider registration. Lookup templates, which separate the provision of instances from the provision of categories, will be explained. Finally, a note is made of the compatibility system which ensures that older manifest-installed services (such as service types) are available via lookup transparently.
InstanceCookie
.
As an example, menu items may be added by inserting data objects
that provide this cookie into the proper folder, and having the
instance be a system action (or other things). Or an XML DTD may be
registered by placing an object with an instance of
org.xml.sax.EntityResolver
in the proper folder.
Where do these instances come from? Technically, it is up to you
to decide how to provide InstanceCookie
; you could if
really necessary create your own data loader that provides it
according to some unusual scheme, and add files recognized by that
loader to the right folder. Practically, the APIs provide
implementations of this cookie sufficient for normal purposes.
The most common way to provide an instance is using
InstanceDataObject
.
This is a type of data object whose sole purpose is to provide the
instance cookie. It typically does so based on a class name you
supply. There are several styles of instance file; all are empty (i.e.
the file contents are of zero length, and just the file name and
attributes matter). There are then other ways of providing instances
which rely on the file contents. Here are the methods of providing
instances defined in the APIs:
InstanceDataObject
If there is a file with the extension *.instance, then its
name (minus extension) will be converted to a class name by replacing dashes
with dots; and a fresh instance of that class will be created and used as the
instance. For example, com-mycom-mymodule-MyAction.instance
produces an instance of the class com.mycom.mymodule.MyAction
.
Since reflection is used to create the new instance, just as in the
realm of JavaBeans, the class must be loadable from your module or
otherwise from within the IDE (technically, via the classloader found
by querying Lookup for ClassLoader
);
public; and have a public no-argument constructor.
(Or be a
SharedClassObject
.)
This form is often
used for singleton classes such as system actions: there is
no need to specify any parameters to the constructor, any instance
will suffice.
InstanceDataObject
Rather than shoving the class name into
the file name, you can name the file more normally and specify the
class with a file attribute. Then the class name is specified as
a string-valued attribute on the instance named
instanceClass
. For example, a keyboard shortcut
could be registered as follows in an XML layer:
<file name="C-F6.instance"> <attr name="instanceClass" stringvalue="com.mycom.mymodule.MyAction"/> </file>
In addition to instanceClass
you may specify an
additional attribute instanceOf
giving the name of a
superclass (or implemented interface) of the instance class. In fact
it may be a comma-separated list of superclasses and interfaces. While
its purpose is explained more fully
below,
essentially it lets you give the system a hint as to what this
instance is for before your instance class is even loaded into the VM.
For example:
<file name="com-me-some-service.instance"> <attr name="instanceClass" stringvalue="com.me.FactoryForEverything"/> <attr name="instanceOf" stringvalue="org.xml.sax.EntityResolver,org.openide.cookies.ExecCookie"/> </file>
InstanceDataObject
A powerful way of providing instances is to use the expressiveness
of the XML layer syntax to handle the instance creation. In this case
the file attribute instanceCreate
can be defined and the
attribute value becomes the instance. Typically the attribute value
would be specified using the methodvalue
syntax of
layers. For example:
<file name="com-me-some-service.instance"> <attr name="instanceClass" stringvalue="com.me.FactoryForEverything"/> <attr name="instanceCreate" methodvalue="com.me.FactoryForEverything.configure"/> <attr name="myParam" urlvalue="nbres:/com/me/config-1.properties"/> <attr name="instanceOf" stringvalue="org.xml.sax.EntityResolver,org.openide.cookies.ExecCookie"/> </file>
According to the general system for
XMLFileSystem
,
you now need a method configure
in
FactoryForEverything
which must be static; the method
need not be public (if you do not want other Java code to see it). It
may take a file object as argument if you wish - this will be the
instance file; typically you use this to pass extra configuration from
the layer, useful if you want to create multiple instances with the
same creation method. So for example you might implement such a method
like this:
public class FactoryForEverything extends SomeBaseFactory implements EntityResolver, ExecCookie { public FactoryForEverything(Map props) { // ... } // ... // Called directly from XML layer. Pass URL to // properties file from attr 'myParam'. private static Object configure(FileObject inst) throws IOException { URL u = (URL)inst.getAttribute("myParam"); Properties p = new Properties(); p.load(u.openStream()); return new FactoryForEverything(p); } }
A simple way to provide an instance is to serialize it as a JavaBean, into a file with the extension *.ser. This is not very useful from a layer, because you should avoid putting binary data into a layer, but may be useful in some circumstances.
This is probably the most powerful option. You can define an XML DTD which provides a rich configuration structure for your instances.
First, you should implement
Environment.Provider
which will be used to serve cookies and perhaps other things from the
XML file. It will be passed a data object; you can cast this to
XMLDataObject
and use
getDocument()
to retrieve the DOM tree, typically. It should
create a lookup
(such as AbstractLookup
with
InstanceContent
); you might want to service requests for
InstanceCookie
, ExecCookie
, or whatever you
want the XML file to support. You can change cookies on the fly by
firing changes in the lookup result (done automatically if you use the
proper lookup implementation).
To register the lookup provider, you should create an instance of
it (in your layer, typically) beneath xml/lookups/, named
according to the public ID of the DTD you wish to recognize.
EntityCatalog
describes the general public ID escaping algorithm, but to that
filename should be appended either .instance or
.xml. For example, a handler for the public ID
-//NetBeans//My Format 1.0//EN could be
registered as follows:
<filesystem> <folder name="xml"> <folder name="lookups"> <folder name="NetBeans"> <file name="My_Format_1_0.instance"> <attr name="instanceClass" stringvalue="my.module.MyFormatHandler"/> </file> </folder> </folder> </folder> </filesystem>
Here my.module.MyFormatHandler
implements
Environment.Provider
.
You may also in this way register mappings from public IDs to
instances of either XMLDataObject.Processor
or
XMLDataObject.Info
, but these are both deprecated in
favor of the more powerful Environment.Provider
.
Again, modules may also have additional ways of providing instances
from files. For example, currently the utilities
module
enables any URL file (*.url) to be used directly in a
menu, as the URL file provides an instance of
Presenter.Menu
.
As an interactive demonstration of these things, first go into
Filesystem Settings and make the system filesystem (first in
the list) visible; then explore its contents in Filesystems,
going into some subdirectory of Menu. Note the various
actions and menu separators; these are all by default instance data
objects.
You may find some of these on disk in your installation directory
under system/ if you have customized them, but by default
they live in memory only.
You may copy-and-paste these instances from one place to another;
create new ones on disk and watch them be recognized and inserted
into the menus after a few seconds; and you may also choose
Customize Bean (which really customizes the provided instance) on
(say) a toolbar separator (under Toolbars
) to change
the separator size, and serialize the result to a new
*.ser
file which should then create a toolbar
separator.
For example, if one instance file represents a menu item (or a system action which can be represented by one), then a folder full of them represents a menu. A folder full of menu folders represents a menu bar. A subfolder underneath a menu folder with some instances in it, represents a submenu. Specifically, the menu bar for the IDE's Main Window is represented by the folder Menu/ in the system filesystem, so you can add a menu item very easily from a layer:
<filesystem> <folder name="Menu"> <folder name="Tools"> <file name="com-me-MyAction.instance"/> </folder> </folder> </filesystem>In a more general way, the lookup system (more on this later) looks for services of all sorts in the folder Services/ in the system filesystem; they may be concealed from the UI by placing them in a subfolder Hidden/. So a typical way to add a new service is as follows:
<filesystem> <folder name="Services"> <folder name="Hidden"> <file name="com-me-SomeService.instance"/> </folder> </folder> </filesystem>
FolderInstance
is typically used to supply instances from entire folders, by
recursively scanning the contents and collecting them together in a
meaningful fashion. (You do not need to know this to install instances
into existing systems, but this class will be useful if you wish to
create your own instance-based configuration system.)
FolderInstance
is fairly flexible and allows you to
control the superclass (or interface) of instances that you wish to
pay attention to; accept certain instances but not others; transform
instances; control how to assemble them into compound instances; and
so on.
There are a few things you should know about dealing with folders containing instances that are used by the system:
However, occasionally you may need more precise control over folder
ordering; if so you may retrieve the folder in question and use
DataFolder.setOrder(DataObject[])
to fix its ordering. To use this method, it is
recommended that you first create all the files you need to in the
folder (or preferably have them be created for you by a layer); use
getChildren()
to retrieve the default ordering; and then move your own data
objects to more appropriate positions based on whatever heuristics,
leaving the order of existing
objects otherwise intact. Now you can set the new order, which will
also fix the order in menus and so on. Such code should typically
be run during
module installation.
InstanceCookie.Of
and lazy class loadingInstanceCookie.Of
. Suppose that there are two generic
interfaces under consideration: e.g. javax.swing.Action
and org.xml.sax.EntityResolver
. For each interface, there
is code to find registered instances, all under the
Services/ folder. Furthermore, using either of these
interfaces is relatively rare, and might not happen at all during an
IDE session; and the implementations of the instances are complicated
and involve a lot of code, so it is undesirable (for performance
reasons) to load these implementations unless and until they are
really needed. If you write the layers simply like this:
<filesystem> <folder name="Services"> <folder name="Hidden"> <file name="com-me-MyAction.instance"/> <file name="com-me-MyResolver.instance"/> </folder> </folder> </filesystem>everything will work, but this is inefficient. Consider some piece of code asking for all actions. The search through the services folder for actions would ask each of these files if it provides an instance cookie assignable to
javax.swing.Action
. For
com-me-MyAction.instance, this will load the class
com.me.MyAction
, determine that it implements
Action
, and thus create a MyAction
instance
and return it; so far so good. But when
com-me-MyResolver.instance is encountered, it will again
load com.me.MyResolver
, only to find that this does not
implement Action
and skip the instance. The behavior is
correct, but now the MyResolver
class has been loaded
into the VM even though no one will ever use it (unless a resolver
search is made). This will degrade startup time and memory usage (and
thus performance).
So the better solution is to mark each file in advance, saying what interfaces it is intended to provide in its instance:
<filesystem> <folder name="Services"> <folder name="Hidden"> <file name="com-me-MyAction.instance"> <attr name="instanceOf" stringvalue="javax.swing.Action"/> </file> <file name="com-me-MyResolver.instance"> <attr name="instanceOf" stringvalue="org.xml.sax.EntityResolver"/> </file> </folder> </folder> </filesystem>
Now the folder instance processor for Action
will pass
over com-me-MyResolver.instance without needing to load
com.me.MyResolver
, since it sees that its interfaces are
declared, and Action
is not among them. Of course, the
interface classes - Action
and
EntityResolver
- need to be loaded right away, but they
were probably already loaded anyway, so this is acceptable.
Caution: if you do supply an
instanceOf
attribute, but it does not list all
of the implemented interfaces and superclasses of the actual
implementation class (including that implementation class itself,
which is not implied), a lookup query on one of the missing
superclasses may or may not succeed. So you should include in
instanceOf
any superclasses and interfaces that you think
someone might use in a lookup query, possibly including the actual
implementation class.
Lookup
.
In the simplest usage, all that is needed is to get some single
instance of a given class (or subclass). For example, if some kind of
service has been defined as an interface or abstract class, and you
wish to find the implementation of it, you
may simply use:
MyService impl = (MyService)Lookup.getDefault().lookup(MyService.class); if (impl == null) /* nothing registered */ ... impl.useIt();Such implementation has to be registered by some module to the system. Either via layer as described above or as a JDK's service provider. If some module wants to register for example org.me.MyService it shall provide file name META-INF/services/org.me.MyService in its own JAR with single line containing name of the implementation class (for example org.you.MyServiceImpl). The lookup infrastructure will then load the implementation class and call its default constructor to answer the query in the above example.
# remove the other implementation (by prefixing the line with #-) #-org.you.MyServiceImpl # provide my own org.alien.MyServiceAlienImplThe reason why the removal line starts with #- is to keep compatibility with JDK's implementation. The # means comment and thus JDK will not interpret the line and will not get confused by the - before class name.
Second extension allows ordering of items. The class implementing the interface can be followed by advisory position attribute. If multiple implementations are defined in one file then each can be followed by its own positioning attribute. When querying on an interface, items with a smaller position are guaranteed to be returned before items with a larger position. Items with no defined position are returned last. Example of content of META-INF/services/org.me.MyService file could be:
org.you.MyServiceImpl #position=20 org.you.MyMoreImportantServiceImpl #position=10The MyMoreImportantServiceImpl will be returned in lookup before the MyServiceImpl. It is recommended to pick up larger numbers so that there is gap for other modules if they need to get in front of your item. And, again, to keep compatibility the position attribute must starts with comment delimiter.
If more than one implementation has been registered, the "first" will be returned. For example, if the implementations were present in the Services folder as *.instance files, then folder order would control this.
As mentioned above, the IDE's default lookup searches in the
Services folder and its subfolders for instances whose
class matches the requested class. Technically, it looks for data
objects with InstanceCookie.Of
claiming to match the
requested superclass, or plain InstanceCookie
whose
instanceClass
is assignable to it.
Note that you may use this method to find singleton instances of
subclasses of
SharedClassObject
that have been registered in lookup (as is normally the case for
system options).
However for this purpose it is simpler to use the static finder method
SharedClassObject.findObject(Class, true)
which is guaranteed to find the singleton whether it was registered in
lookup or not (if necessary it will first
initialize
the object according to saved state).
In many situations it is normal for there to be more than one registered implementation of a service. In such a case you use a more general method:
Lookup.Template templ = new Lookup.Template(MyService.class); final Lookup.Result result = Lookup.getDefault().lookup(templ); Collection impls = result.allInstances(); // Collection<MyService> // use Java Collections API to get iterator, ... // Pay attention to subsequent changes in the result. result.addLookupListener(new LookupListener() { public void resultChanged(LookupEvent ev) { // Now it is different. Collection impls2 = result.allInstances(); // use the new list of instances... } });Here you receive a collection of all instances matching your query, again in the order found if this matters. You can also listen to changes in the list (additions, deletions, and reorderings). It is fine to keep a
Lookup.Result
for a long period of time as
it may implement its own caching scheme and only really compute the
instances when allInstances
is called; in fact it may be
more efficient to keep a result, and listen for changes in it, than to
repeatedly call lookup
and create fresh result objects.
When a lookup query is finished - for example when
Lookup.Result.allInstances()
returns some
Collection
of instances - it is guaranteed that all
objects registered in the same thread prior to the call to
lookup()
or prior to some change notification on the
Lookup.Result
, will be returned. Specifically, lookup
instances registered via module layer will be available by the time
ModuleInstall.restored()
(or .installed()
)
is called. There are two situations in which lookup results may be
incomplete: when you are currently inside the dynamic scope of some
method providing a lookup instance itself; and when you are
dynamically inside a DataLoader
method involved in
recognizing data objects.
In such cases it is possible to use code like this:
Lookup.Template templ = new Lookup.Template(MyService.class); Lookup.Result result = Lookup.getDefault().lookup(templ); Iterator it = result.allItems().iterator(); while (it.hasNext()) { Lookup.Item item = (Lookup.Item)it.next(); String displayName = item.getDisplayName(); if (/* user accepts displayName as the right one */) { MyService instance = (MyService)item.getInstance(); // use instance for now, and ... String id = item.getId(); someSettings.setChosenService(id); break; } } // later... String storedID = someSettings.getChosenService(); Lookup.Template templ = new Lookup.Template(MyService.class, storedID, null); Iterator it = Lookup.getDefault().lookup(templ).allInstances().iterator(); if (! it.hasNext()) /* failed to find it... */ MyService instance = (MyService)it.next(); // use instance againThe ID permits you to track which instance from all those available in the lookup result was last selected by the user, and find the "same" instance later, perhaps after a restart of the IDE. The exact form of the ID is the private knowledge of the implementor of the lookup, but typically if the instance has been provided via layer the ID will mention the name of the file from which it was derived.
Lookup
specially by proxying to them. This
can be very powerful because you may register just one layer file
which points to your custom lookup, which in turn may provide an
unlimited number of actual instances/items for queries (and compute
them in a manner other than registration by files). Another reason is
to associate a context with a data object using
Environment.Provider
;
for example the data object might be an XML file and this provider
might be registered with the file by means of the public ID of the
doctype (informally, the DTD). Here the provider has an associated
lookup which can serve requests for cookies and such things.
See more information about associating lookups
with XML files.
The simplest way to create a fresh lookup is to base it on other
existing ones.
ProxyLookup
accepts a list of other lookup implementations (in the constructor and
also changeable later). The results it provides are constructed by
merging together the results of the delegate lookups.
If you want to use the common mechanism of finding instances in
folders (or subfolders) and serving these as the results,
FolderLookup
makes this possible: you need only provide a folder to look in, and
use
getLookup
to retrieve a lookup implementation which will scan this folder and
its subfolders for data objects with InstanceCookie
matching the lookup template. Furthermore, any instance cookies whose
instance class is assignable to Lookup
will be treated
specially: they will be proxied to, so these sub-lookups may provide
additional instances if they match the lookup template.
The most powerful way to provide a lookup is to directly define
what instances and items it should provide, by subclassing. For this,
AbstractLookup
is recommended as it is easiest to use.
The simplest way to use AbstractLookup
is to use its
public constructor (in which case you need not subclass it). Here you
provide an
AbstractLookup.Content
object which you have created and hold on to privately, and which
keeps track of instances and permits them to be registered and
deregistered. Often
InstanceContent
is used as the content implementation. To add something to the lookup,
simply use
add(Object)
(and remove(Object)
for the reverse). These may be called
at any time and will update the set of registered instances (firing
result changes as needed).
In case it is expensive to actually compute the object in the
lookup, but there is some cheap "key" which can easily generate it,
you may instead register the key by passing in an
InstanceContent.Convertor
.
This convertor translates the key to the real instance that the lookup
client sees, if and when needed. For example, if you have a long list
of class names and wish to register default instances of each class,
you might actually register the class name as the key, and supply a
convertor which really loads the class and instantiates it. This makes
it easy to set up the lookup, but nothing is really loaded until
someone asks for it.
BeanInfo
.
In order to save such settings, an XML file is normally used, and the APIs provide a convenient DTD for settings which can be represented as a single bean. In typical usage, the module layer declares an initial settings file which instructs lookup to create a default instance of the settings class. This might look like the following:
<?xml version="1.0"?> <!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN" "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd"> <settings version="1.0"> <module name="com.foo/1" spec="1.0"/> <instanceof class="org.openide.options.SystemOption"/> <instanceof class="com.foo.MyOption"/> <instance class="com.foo.MyOption"/> </settings>Such a file might be placed in Services/com-foo-my-settings.xml (the exact name inside the Services folder is not important but ought to be globally unique to avoid conflicts with other modules). It provides an
InstanceCookie
with the settings object as instance.
The interesting parts of this file are:
InstanceCookie.Of
).
It is only necessary to indicate superclasses and interfaces that you
actually expect someone to look up when searching for this setting,
but be careful that you know what these might be. The actual class
of the instance itself should be listed as well.
ClassNotFoundException
. If the module is
subsequently reinstalled, the setting will automatically become active
again (regain its InstanceCookie
). Similarly, settings will
not be loaded if they were written by a newer version of the module than
the one currently installed - module-supplied settings should be readable
by newer versions of the module, but generally not older ones.
There are actually three ways that the instance may be declared:
Using <instance/> as above to generate a default instance. This is most common.
You may pass an additional attribute method
indicating a static method to call to produce
the instance, rather than using a default constructor.
The method may either be a simple name, in which case it is assumed
to be a method in the class given by class
, or you
may give a full class name followed by a dot and method name to invoke
a static method from some other class.
The method may
optionally take a FileObject
argument which will be the
settings file itself. This is analogous to the mechanism used for
creating complex *.instance files. For example:
<file name="some-difficult-to-make-instance.settings"> <![CDATA[<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN" "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd"> <settings version="1.0"> <module name="my.module/1" spec="1.0"/> <instanceof class="javax.swing.Action"/> <instanceof class="my.module.MyAction"/> <instance class="my.module.MyAction" method="createAction"/> </settings> ]]> <attr name="param" stringvalue="someval"/> </file>
package my.module; public class MyAction extends javax.swing.AbstractAction { public MyAction(String name) {/* ... */} // ... public static MyAction createAction(FileObject fo) { return new MyAction((String)fo.getAttribute("param")); } }
This will result in an instance of MyAction
being
created with name someval.
You may use the <serialdata> element. Its textual contents are a hexadecimal dump (whitespace ignored) of the raw serialized bytes of an object to serve as the instance. Naturally this is the least preferred mechanism as it is not human-readable.
In the future it is planned for a fourth mechanism to be supported, taking advantage of the JDK 1.4 XML persistence ("archiver") system.
A client can find the current setting in a couple of ways:
SystemOption
, you may simply
call
SharedClassObject.findObject(Class, true)
which will either provide a previously initialized singleton, or find
the setting in lookup if possible and read any customized state before
returning it. You may then use property change listeners as needed to
listen for changes.
How does the customization of setting instances work? After finding a
setting instance via this DTD, the system will automatically look for
a beans-style event set of type PropertyChangeListener
,
and add its own listener. If the bean changes state (either
programmatically or as a result of user manipulation), the property
change will cause the new state to be written out to the original XML
file, keeping the same name. (Normally this would mean the XML would
be written to disk in the user directory.)
(Currently the state is simply serialized into
the XML file using <serialdata>, meaning the class
must be Serializable
, but in the future if an alternate
persistence form is available, such as the JDK 1.4 archiver, this will
be used instead.)
Conversely, changes to the setting file on disk should trigger a reload of the state and modification of the in-memory bean (or creation of a new instance cookie with a new bean).
There are several things you can do to not only have services and lookup function programmatically but also look good and behave nicely within the user's view of configuration options and settings.
For many kinds of services, especially ServiceType
s
but also others, it is necessary to permit the user to create new
instances of the service. Generally two criteria should be met for
such services:
Creation of new service instances may be supported simply by producing a template residing beneath Templates/Services/. If the service normally resides in a subfolder of services, for example Services/Executor/, then the template should correspondingly be placed in Templates/Services/Executor/.
The template should work like regular source code templates do: a
file giving the initial structure of the service (typically a
*.settings file), with the file attribute
template
set to true, and optionally a
templateWizardDescription
and
templateWizardURL
. For example:
<folder name="Templates"> <folder name="Services"> <folder name="Executor"> <file name="my-module-executor.settings" url="executor.settings"> <attr name="template" boolvalue="true"/> <!-- SystemFileSystem.localizedName and SystemFileSystem.icon as usual --> </file> </folder> </folder> </folder>
If the user selects New From Template on the corresponding options folder, the template will be available.
In addition to providing services, it is desirable to display them to the user as well. This is done, as is customary in other aspects of the IDE's configuration, by displaying customized variants of the data nodes coming from the system filesystem. The root folder for displaying options is called UI/Services/. Its subfolders govern the display of the options available in the system.
As a rule, it is undesirable to place any actual settings in this folder (nor would they be recognized by the default lookup anyway). That is because the organization of this folder is driven by UI needs, without regards to API maintenance or compatibility of persisted user settings. So this folder solely mirrors configuration available elsewhere. You may freely reorganize the mirror according to current UI needs: existing modules plugging into services areas will continue to work unmodified, and existing user customizations stored to disk will continue to apply, since both of these act on the original files (which should not be moved frivolously).
While technically you could place anything you wish in the UI folder, in practice a few types of things are used:
Symbolic links to settings files displayed elsewhere. In the IDE's options, these will appear as the real setting. For example:
<folder name="UI"> <folder name="Services"> <folder name="Editing"> <file name="my-module-config.shadow"> <attr name="originalFile" stringvalue="Services/my-module-Config.settings"/> <attr name="originalFileSystem" stringvalue="SystemFileSystem"/> </file> </folder> </folder> </folder>
The attribute "originalFileSystem" can be omitted. In this case the search for the
linked file will be done on the Filesystem on which the link resides, which
is wanted for the usages on SystemFileSystem. The link file should have zero length.
Note that any localized display name
and icon should be set on the original settings file; they will be
picked up automatically by the shadow.
<folder name="UI"> <folder name="Services"> <folder name="Editing"> <file name="my-module-config.shadow"> <![CDATA[Services/my-module-Config.settings SystemFileSystem ]]> </file> </folder> </folder> </folder>
Here the shadow file consists of two lines, the first being the path to the real settings, the second always being SystemFileSystem.
Links to other kinds of files, such as folders, whether part of the services lookup area or not. For example:
<folder name="UI"> <folder name="Services"> <folder name="IDEConfiguration"> <folder name="LookAndFeel"> <file name="my-module-stuff.shadow"> <attr name="originalFile" stringvalue="Stuff"/> </file> </folder> </folder> </folder> </folder> <folder name="Stuff"> <attr name="SystemFileSystem.localizingBundle" stringvalue="my.module.Bundle"/> <attr name="SystemFileSystem.icon" urlvalue="nbresloc:/my/module/stuff.gif"/> <!-- perhaps some files to display here, perhaps not --> </folder>
This defines a folder Stuff in the system filesystem which may be used for some kind of special configuration, and displays it in options.
Specialized nodes. In some cases you do not wish to display a particular folder but want to have complete control over the literal display of the option. In such a case you need only include a *.instance file with an instance of the node (or its handle, if you prefer), as the node delegate of this object will be (a clone of) the node you provide. For example:
<folder name="UI"> <folder name="Services"> <folder name="Building"> <!-- Some subclass of org.openide.nodes.Node: --> <file name="my-module-ConfigurationNode.instance"/> </folder> </folder> </folder>
No particular substructure of UI/Services/ is defined by the APIs. For optimal UI integration you may wish to examine the categories used by other modules and try to reuse an existing category appropriate to your needs.
In some cases it is necessary to hide a service file, or a
whole folder of services. While you can place files into
Services/ and simply not make any corresponding mirror in
UI/Services/, you may wish to create services or
subfolders inside existing displayable folders (for purposes of
lookup, generally) yet not have them be visible in the UI. In this
case you should mark the file or subfolder with the file attribute
hidden
(set to boolean true).
If you wish to permit the user to select a service as part of the
Property Sheet (from a node property, or as a
PropertyPanel
, providing general GUI embeddability), this
is supported. You should use the property editor assigned to
java.lang.Object
(not your desired service
interface). Various hints defined in the
Explorer API
permit you to control the result.
As an example, here is a node property permitting you to ask the
user to select a value of type my.module.Thing
, being
some interface or abstract superclass, where some instances are
registered to lookup, conventionally in the
Services/Things/ folder which the module has provided:
public abstract class ThingProperty extends PropertySupport.ReadWrite { protected ThingProperty(String name, String displayName, String shortDescription) throws IOException { super(name, Object.class, displayName, shortDescription); setValue("superClass", Thing.class); // NOI18N setValue("nullValue", NbBundle.getMessage(ThingProperty.class, "LBL_no_thing")); // NOI18N DataFolder thingsFolder = DataFolder.create( DataFolder.findFolder(Repository.getDefault().getDefaultFileSystem().getRoot()), "Services/Things" // NOI18N ); setValue("node", thingsFolder.getNodeDelegate()); // NOI18N } public final Object getValue() { return getThing(); } public final void setValue(Object o) { if (o != null) { Lookup.Template templ = new Lookup.Template(Thing.class, o, null); Iterator it = Lookup.getDefault().lookup(templ).allItems().iterator(); if (it.hasNext()) { setThingID(((Lookup.Item)it.next()).getId()); } else { // Thing was registered but is not persistable. setThingID(null); } } else { setThingID(null); } } public final boolean supportsDefaultValue() { return true; } public final void restoreDefaultValue() { setValue(null); } // May be used by code wishing to get the actual Thing (or null): public final Thing getThing() { String id = getThingID(); if (id != null) { Lookup.Template templ = new Lookup.Template(Thing.class, null, id); Iterator it = Lookup.getDefault().lookup(templ).allInstances().iterator(); if (it.hasNext()) { return (Thing)it.next(); } else { // Invalid ID. return null; } } else { return null; } } // Subclasses implement to actually read/write Thing persistent IDs (or null): protected abstract String getThingID(); protected abstract void setThingID(String id); }
A property extending this class would in the current UI display a
pull-down list of all Thing
implementations available in
lookup; the custom property editor dialog would display the
Things folder with anything contained inside it for the
user to select from, provided it in fact had an instance assignable to
Thing
. The special null value is explicitly permitted
here and would be displayed with the label given in the bundle.
You may also specify a context help ID for services or settings you are installing
(typically through a layer). Although ServiceType
s or
SystemOption
s themselves can return
help IDs (see ServiceType.getHelpCtx()
and SystemOption.getHelpCtx()
),
folders which represent their categories cannot use this mechanism. You may declare the desired
context ID (e.g. in the layer) by adding the file attribute helpID
for the folder's
primary file, with a value of type String
:
<folder name="Services"> <folder name="SomeCategory"> <attr name="helpID" stringvalue="the.help.ID"/> </folder> </folder>
You may also attach the help ID to individual services or options in the same way, by
adding the helpID
attribute to their primary files, most useful for service
objects which do not inherit from either ServiceType
or SystemOption
.
For compatibility with older modules which registered objects via module manifest, there is a bridge which makes many such objects available via lookup. The Modules API lists possible manifest section classes and notes where they may (and should) be replaced by direct lookup registration.
A guide is available to help you move old manifest-based settings to the lookup system.