Lifecycle of objects in Registry/Convertor APIs

In order to correctly use Registry and Convertor APIs the lifecycle of objects managed by these APIs must be explained. There is several design decisions which were made and which will be described in this document. They could be summarized as:
  • bindings are singletons
  • stateless convertors
  • merged context does not create new instance
  • no autosave
The behaviour has changed in several aspects compared to existing Filesystems/Datasystems APIs. The motivation for these changes were simplification of APIs and optimization for the most common usecases.

Bindings are singletons

The Registry API treats objects it creates as singletons. Once the object is created and as long as some non-Registry code is holding a reference to the object, subsequent calls will return the same object. Current implementation weakly references all created objects.

Stateless convertors

The Convertor API is stateless. Convertor is factory which does not need any state to produce instance from persited data. Persisted data should contain everything necessary to recreate the instance.

This is different from core/settings convertors which passes state to their convertor and allow to create different instances from one persisted data.

Merged context does not create new instance

Merged context does not create new instances. It delegates object creation to its delegating contexts. That means the bindings are treated as singletons on merged context too.

This is different from how MultiFilesystem works. The MultiFilesystem which merged set of filesystems creates new MultiFileObject for each merged file and for this file object the Datasystems creates new DataObject which produce new instace through InstanceCookie. In this case it make sense to influence object creation and produce difference instace for regular FileObject and for MultiFileObject. In merged context there is only one object and it should be the same without regard whether it was created from the regular Context or from the Merged Context.

No autosave

Modified object retrieved from Registry is not autosaved. The modified object must be bound to Registry to be persisted otherwise the modification is lost after application reload.

This is different from InstanceDataObject which saves modified instances automatically.

Illustrative examples

Several illustrative examples are provided here which demonstrate incorrect usage of APIs or suggest solution for some usecases.

Example 1: Shared object with a state

This example demonstrates that special care must be taken during design of API of objects which will be shared by multiple clients in Registry. As was said above the Registry treats objects as singletons. That means that multiple foreign clients can retrieve the same instance and depending on the API of that object they can simultaneously mutate the state of the object what can results in unexpected random failures. Let's demonstrate it on example:

Example of wrong usage:

Imagine following public API class:

class Compiler {
    // configure compilation classpath
    public void setClasspath(String classpath);
    // compile set of files according to configured classpath
    public void compile(Set files);
}

The design of the class expects that first the class is instantiated, then the classpath is set and then the files can be compiled. If instance of this class is stored in Registry and two clients want to use it concurrently to compile a file it will occasionally not work correctly. They share the same instance and they are changing the state of the object (the classpath) each other. The result is random failures during the compilation of files because of incorrect classpath.

Fixed example:

The above example must be solved by registering compiler factory class to Registry instead. The modified classes could look like:

class Compiler {
    // compile set of files
    public void compile(Set files);
}

class CompilerFactory {
    // create compiler for the given classpath
    public Compiler createCompiler(String classpath);
}

This design expects that CompilerFactory creates private instance of Compiler for the given classpath and this Compiler instance can be then used for compilation of set of files. Only instances of CompilerFactory would be allowed to be registered in Registry. Any client can retrieve the (singleton) instance of CompilerFactory and use it whatever way without clashing with other clients, because the product (the Compiler) is created per client and nobody else has access to it.

Note:

It is important to emphasize that this is relevant only in case the Compiler class is public API. If the Compiler class from first wrong example was private API which no foreign module could access then the example is perfectly OK if the module owner is aware of the singletonness of the instance.

Example 2: Merged context usages

It must be kept in mind that behaviour of Merged context is different then MultiFilesystem behavoiur. The Merged context does not create new instances but delegates calls to its delegating contexts.

Imagine following case. Editor defines registry context per each MIMEtype and modules can register to these contexts actions which should be visible in editor popup menu. The editor popup menu is generated for edited file by going from the most specific MIMEtype to the less specific one and by merging all found actions. For example for Ant script opened in editor the MIMEtype contexts searched for the actions are: text/x-ant+xml, text/xml and text/plain. Merged context can be used to do the merging of these contexts. The instantiation of an action through the merged context will be delegated to the underlaying context on which the action was found.

That means it is irrelevant whether the action is created directly from context owning the action or from merged context - the result will be the same action. That's also reason why the convertors are stateless, because the context through which the action is created should not influence it.

If there are cases where this behaviour is not desired the usecases must be provided. Generally it can be workarounded by creating always new instance of Registry SPI Context and merging these instead of the default one. But reasons for that should be well thought.

Example 3: Context based instances

There are cases where instance is defined as path to some other context and is created from the content of that context. Such a definition of instance allows foreign modules to register their instances into the folder. The potential problem here is with addressing - how to resolve the context path to real Context instance when there is multiple Registry instances.

For Default Registry this is not issue. Any absolute context name can be easily resolved to Default Registry.

For any non-default registry the situation is different. The convertors are stateless - they do not know the context (the registry instance) in which they operate and so they are not able to resolve context path. Nevertheless the real compelling usecases where this is needed must be provided if this issue needs to be resolved generally. Till now the only usecase is Looks API. And after the talk with Hrebejk (Looks API owner) even this case can be resolved differently. The represented object used by Looks should know project it belongs to and so according to this information it should be able to find correct project registry.

Generally, whenever this is a problem the context path must be resolved outside of Registry/Convertor API. Imagine following theoretical case. The JMenu instance bound in registry is defined as context path to context which contains individual actions. The persisted instance looks like:

<menu xmlns=".../menu_ns">
     <context>/some/context/with/menu/items</context>
</menu>

If this instance is defined on Default Registry then convertor registered for this namespace can directly resolve the path to context from Default Registry and create the instance of the JMenu.

If this registration should be used on non-default registry then convertor cannot resolve the path and cannot create the JMenu. Instead of that the instance of interface like this should be returned:

public interface MenuDescriptor {
    JMenu create(Context c);
}

The instance keeps the context path as String and postpones resolving to create(Context) method. Client who knows the Context from which the object was read will call create(Context) factory method to get the JMenu instance.

If several compelling usecases are provided the solution could be generalized and supported directly in Registry API. For example there can be in API of Registry interface:

public interface ContextSensitiveInstance {
    Object create(Context c);
}

which must be implemented by all object which are resolving context path. The Registry API would check for the presence of this interface directly in the method api.Context.getObject(String, Object) and do the translation of the object automatically in the Registry API by calling the ContextSensitiveInstance.create(Context) method with proper context. The API client would get the translated object.

Links

Project Features

About this Project

openide was started in November 2009, is owned by Antonin Nebuzelsky, and has 78 members.
By use of this website, you agree to the NetBeans Policies and Terms of Use (revision 20160708.bf2ac18). © 2014, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo
 
 
Close
loading
Please Confirm
Close