OpenOfficeA guide to language-independent UNO

Contents

Overview
UNOIDL
- Exceptions
- Service
- Service and implementation names
- UNOIDL vs CORBAIDL / MIDL
Core services
- Servicemanager
Core interfaces
- XInterface
- XComponent
- XUnoTunnel
- XAggregation
- Mandatory interfaces for services
- Uno runtimeenvironment and XMain
Properties and events

Overview

This document addresses application developers, that want to use UNO as a framework for development. It shall give the reader an complete introduction to the language independent part of UNO. A document focusing on the OpenOffice API can be downloaded from the sun-developer-connection. This document talks about core UNO only. There is also a language specific C++ introduction.

TODO:
- XAggregation
- XMain
- SimpleRegistry
- NestedRegistry

IDL

UNOIDL is a declarative language to define data types abstractly. It is similar to CORBA-IDL or MIDL, but differs slightly. If you are not familiar with IDL, you should browse a little in the api-idl-files to get an impression.

UNOIDL allows the developer to define UNO-datatypes independent of a programming language. Introducing a new datatype in UNOIDL is necessary to make it available in all programming languages supported by UNO. User-defined data-types are structs, enums, constant groups, exceptions, interfaces and services.

Interfaces

Interfaces contain methods and/or attributes. Interfaces ( or better interfacemethods and interfaceattributes ) must be filled with "flesh" by implementations.

Interfaces are always transported by reference (e.g. when an interface is mapped to another process, only a reference is mapped). All calls to the interface are transported to the original object (save acquire,release and [under certain circumstances] queryInterface-calls ).

Attributes behave like methods (set/get-methods can replace attributes), so attributes are never meant by value as someone may think. If you are in an UNO-environment, that natively supports attributes, UNO-attributes are a very fine thing to have. As a drawback to set/get methods, no exceptions can be specified at attributes.

All interfaces specified in UNOIDL must be derived from com.sun.star.uno.XInterface . Interface names should start with a X prefix.

Structs

A struct is a structured container for data (very much like C-structs). Structs are always transported by value ( e.g. as a method-argument or as a return value ).

Sequence

For every type in UNOIDL exists a sequence (or array) of this type, which itself is a new type. Sequences are transported by value. The number of elements within a sequence is determined at runtime. There are no C-style arrays in UNOIDL.

Exceptions

Exceptions is the UNO-mechanism to propagate errors to the caller. As structs, exception are a structured container for data. Exceptions cannot be a passed as a return value or method argument, but they can be transported in an any. There are two kinds of exceptions
  • User-defined exceptions
    The designer of an interface is responsible for declaring exceptions for every possible error condition that might occur. He/she should declare different exceptions for different reasons, if it makes sense for the caller to distinguish the error conditions. The implementation may throw the specified exceptions and exceptions derived from the specified exceptions. The implementation must not throw unspecified exceptions. This implies, that if no exception is specified, the implementation must not throw an exception ( save RuntimeExceptions see below).

    When a user-defined exception is thrown, the object should be left in the state as it has been before the call. If this cannot be guaranteed, the exception specification must describe the state of object; this is however disapproved, because it is very nasty for the caller.

    Every exception must be derived from com.sun.star.uno.Exception.
     module com {  module sun {  module star {  module uno {  
    exception Exception
    {
    	/** specifies a detailed message of the exception
    	    or an empty string if the callee does not describe
    	    the exception.
    	 */
    	string Message; 
     
    	/** an object that describes the reason for the
    	    exception. May be NULL if the callee does not
    	    describe the exception.
    	 */
    	com::sun::star::uno::XInterface Context; 
    }; 
    }; }; }; };
    The exception has two members. Message should contain a detailed human readable description of the error, which is very nice for debugging purposes, though in general it cannot be evaluated at runtime. The Context member should contain the object, that threw the exception.

    Example :
     module com {  module sun {  module star {  module beans {  
    [ uik(E227A3A8-33D6-11D1-AABE00A0-249D5590),
                                    ident( "XPropertySet", 1.0 ) ]
    interface XPropertySet: com::sun::star::uno::XInterface
    {
          [...]
        /** @returns 
    	     the value of the property with the specified name. 
    					 
    	@param PropertyName	 
    	     This parameter specifies the name of the property. 
    			 
    	@throws UnknownPropertyException	 
          	     if the property does not exist. 
    				 
    	@throws stardiv::uno::lang::WrappedTargetException  
    	     if the implementation has an internal reason for the
    	     exception.	In this case the original exception
    	     is wrapped into that WrappedTargetException.
    	 */
    	[const] any getPropertyValue( [in] string PropertyName ) 
    	   raises( com::sun::star::beans::UnknownPropertyException, 
    		   com::sun::star::lang::WrappedTargetException );
    	[...]
    };
    }; }; }; };

  • Runtime exceptions
    Runtime exceptions may be thrown by underlying bridges, if an unrecoverable error has occurred ( e.g. an interprocess connection broke down ). In UNO, the caller in general cannot know, if an object lives in his own process or not, so this leads to the following rule :

    The caller of an UNO-method must be aware, that every call may throw a com.sun.star.uno.RuntimeException (except acquire and release).

    This is independent of how many calls have been completed successfully before. Every caller should ensure, that its own object keeps in a consistent state even if a call to another object replied with a runtime exception. The caller should also ensure that no resource leaks occur (memory, file descriptor, etc.) in theses cases.

    If a Runtime exception occurs, the caller does not know, whether the call has been completed successfully or not.

    The com.sun.star.uno.RuntimeException is derived from com.sun.star.uno.Exception.

    An UNO-object-implementation is in general not allowed to throw a com.sun.star.uno.RuntimeException (one of the very rare cases is the DisposedException, see discussion about disposed objects). If it does ( maybe because the implementation must throw an exception, but it was not specified ), it is a HACK. This in general occurs, when the interface was poorly designed. The correct solution is to change the interface ( if it was not made public before ) or to switch to a new set of interfaces ( which often is quite a lot of work ... ).

Exception should not be misused as a new kind of programming flow mechanism. As a rule of thumb it should always be possible that during a session of a program no exception is thrown. If this is not the case, the interface design should be reviewed.

Service

A service is an abstract component specification. It contains a list of mandatory interfaces, that an implementation must export to be a valid implementation of the service. Furthermore, it specifies relations between interfaces. A service is uniquely identified by a service name . Additionally, properties can be specified, that the service must support.

There can be multiple implementations of a service. One component may implement multiple services.

Example :
 module com {  module sun {  module star {  module io {  
/**
    This service is a connection of an outputstream and
    an inputstream. All data written through the outputstream
    is buffered until it is read again
    from the input stream. The buffer should
    grow in 2 ^ x steps if necessary.
    The maximum size of the buffer is 2 ^ 31 -1.
 */
service Pipe
{ 
	interface com::sun::star::io::XOutputStream; 
	interface com::sun::star::io::XInputStream; 
}; 
}; }; }; };
Here com.sun.star.io.Pipe is the service name.

Service and implementation names

In UNO there is the notion of a service name and an implementation name.

The service name uniquely identifies a service as it was specified in IDL. If you try to instantiate an object at the servicemanager using the service name, you will get an object, that complies to the service specification. A service name is often derived from the name of the main interface (if available), the component should support(see example below), but should not have the X interfaceprefix.

The implementation name uniquely identifies one implementation of a service ( or multiple services ). The name is determined by the implementation, it does not appear in IDL. The suggested naming convention is company-prefix.comp.module.unique-name-in-module . If you try to instantiate an object at the servicemanager using the implementation name , you will get an instance of exactly that implementation. An implementation name is the equivalent to a concrete class name in Java.

Example : The SimpleRegistry-service allows to access a tree-structured-registry. The service name is com.sun.star.registry.SimpleRegistry . One implementation of this service can be found in the udk/stoc module. Therefor the implementation name is com.sun.star.comp.stoc.SimpleRegistry .

As a result of this concept, the servicemanager has the freedom to choose between multiple implementations of a certain service, but the application developer still has the possibility to access a certain implementation (however this should only be necessary in exceptional cases).

UNOIDL vs CORBA-IDL/MIDL.

feature UNOIDL CORBA-IDL MIDL
multiple inheritance of interfaces no yes no
exceptions yes yes no
exceptions with inheritance yes no no
mandatory superinterface yes no yes
C-style arrays no yes ?
Life cycle concept yes (refcounting) no yes (refcounting)

Core services

Servicemanager

Every UNO-runtime-environment ( C++, Java, scripting-languages, etc. ) offers a servicemanager. A servicemanager allows to instantiate a service by the service or implementation name. The servicemanager is created by the UNO-runtime at system startup. How services can be inserted in the servicemanager depends on the UNO-runtime-environment.

The servicemanager must support the interface com.sun.star.lang.XMultiServiceFactory.
 module com {  module sun {  module star {  module lang {  
[ uik(E227A3DE-33D6-11D1-AABE00A0-249D5590),
                        ident( "XMultiServiceFactory", 1.0 ) ]
interface XMultiServiceFactory: com::sun::star::uno::XInterface
{ 
	/** creates an instance of a component which supports the
	    services specified by the factory.
	 */
	com::sun::star::uno::XInterface
	     createInstance( [in] string aServiceSpecifier ) 
			raises( com::sun::star::uno::Exception ); 
 
	/** creates an instance of a component which supports the
		services specified by the factory, and initializes
		the new object with given arguments.
		@see XInitialization::init
	 */
	com::sun::star::uno::XInterface createInstanceWithArguments(
	        [in] string ServiceSpecifier, 
		[in] sequence Arguments ) 
		raises( com::sun::star::uno::Exception ); 
 
	/** @returns a sequence of all service identifiers
	 *           which can be instanciated.
	 */
	sequence getAvailableServiceNames(); 
}; 
}; }; }; };

createInstance first tries to find a service with this name. It this is not successful, it tries to find an implementation with this name. If one of the two lookups is successful, it instantiates the object and returns a XInterface reference to it.

If a service is a oneinstance (or singleton) service, the servicemanager instantiates it the first time it is requested, but keeps a hard reference to it (the servicemanager owns the object). If it is requested again, the service manager returns this reference. The reference is freed when, the servicemanager gets disposed, which usually occurs during application termination.

SimpleRegistry

[...] TODO

NestedRegistry

[...] TODO

Core interfaces

com.sun.star.uno.XInterface

com.sun.star.uno.XInterface must be used as a base interface for all other interfaces. XInterface offers three methods.
module com {  module sun {  module star {  module uno {  

[ uik(E227A391-33D6-11D1-AABE00A0-249D5590), ident( "XInterface", 1.0 ) ]
interface XInterface
{
	/** queries for a new interface to an existing UNO object.
	 */
	any queryInterface( [in] type aType ); 


	/** increases the reference counter by one.
	 */
	[oneway] void acquire(); 
 
	/** decreases the reference counter by one.
	    When the reference counter reaches 0, the object gets deleted.
	 */
	[oneway] void release(); 
}; 
 
}; }; }; };
acquire and release handle the lifecycle of an object. acquire increases the refcount of an object by one, release decreases it by one. If the refcount drops to zero, the object is destroyed. Note that it is not necessary, that every acquire and release call is delegated to the original object, e.g. a proxy, that has once acquired the original object, should take care of acquire and release calls, until its own refcount drops to zero (then it sends a release to the original object to compensate the first acquire). acquire and release must not throw exceptions ( even not RuntimeException ). This is not allowed, because it is not clear for the caller, whether the refcount was modified or not, which is fatal for a system based on refcounting.

Note that here each language binding is free to choose how to implement this. E.g. a Java programmer does not need to take care of refcounting.

The queryInterface method allows to asks an object for a certain interface. The object may either return a valid reference or an empty reference. There are certain specifications, a queryInterface must not violate.

  • If queryInterface on a specific object has once returned a valid interface reference for a given type, it must return the a valid reference for any successive queryInterface call with the same type.
  • If queryInterface on a specific object has once returned a null reference for a given type, it must always return a null reference for the same type.
  • If queryInterface on a reference A returns reference B, queryInterface on B for Type A must return interface reference A or calls made on the returned reference must be equivalent to calls made on reference A.
  • If queryInterface on a reference A returns reference B, queryInterface on A and B for XInterface must return the same interface reference (object identity).

com.sun.star.lang.XComponent

A general problem of a reference counted system is how to deal with cyclic references. Object A keeps a reference on object B and B keeps one (direct or indirect) on object A. Even if all external references to A and B are released, the objects are not destroyed, which results in a resource leak.

In UNO the developer must explicitly decide, when to break cyclic references. To support this concept, there exists the com.sun.star.lang.XComponent-interface.
 module com {  module sun {  module star {  module lang {  

interface XEventListener: com::sun::star::uno::XInterface
{ 
   /** gets called when the broadcaster is about to be disposed.
       All listeners and all other
       objects which reference the 
       broadcaster should release the references.
   */
   void disposing( [in] com::sun::star::lang::EventObject Source ); 
};

   /** controls the lifetime of components.

     Actually the real lifetime of an UNO object is controlled
     by references kept on interfaces of this object. But there
     are two distinct meanings in keeping a reference to an
     interface:
     1st to own the object and 2nd to know the object. Especially
     in case of cyclic references, the objects would never
     get destroyed.
     
     You are only allowed to keep references of interfaces
     to UNO objects if you are by definition the owner of that
     object or your reference is very temporary or you have
     registered an EventListener at that object and cleared
     the reference when "disposing" is called.
*/
[ uik(E227A39F-33D6-11D1-AABE00A0-249D5590), ident( "XComponent", 1.0 ) ]
interface XComponent: com::sun::star::uno::XInterface
{ 
    /** The owner of a component calls this method to get rid of it.

	Only the owner of this object calls the dispose method if
	the object should be destroyed. All objects and components
	must release the references to the objects. If the object is a
	broadcaster, then all listeners are removed and the method 
	is called on all listeners.
     */
     void dispose(); 
 
     /** adds an event listener to the object. 
	 The broadcaster fires the disposing method of this listener 
	 if the method is called.
     */
     void addEventListener( [in] XEventListener xListener ); 

     /** removes an event listener from the listener list.
	 It is a "noop" if the specified listener is not registered.
     */
     void removeEventListener( [in] XEventListener aListener ); 
}; 
}; }; }; };
When an object supports the XComponent interface, it allows other objects to add themselves as XEventListener. When the dispose-method gets called, the objects notifies all listeners via the XEventListener.disposing()-method and releases all interface references thus breaking the cyclic references. Note that an disposed object is in general unable to comply with its specification. So it is necessary to ensure, that an object is not disposed before calling it. A useful concept for this is an owner/user concept.

Only the owner of an object is allowed to call dispose and there can be only one owner per object. So the owner is always free to call the object. The user of an object knows, that the object may get disposed at some unspecified time. To get to know, when an object gets disposed, it adds itself as event listener and releases the interface reference to the object, when it gets notified. In this case, the user should not call removeEventListener (the disposing object releases the reference to the user anyway).

If an disposed object gets called, it should behave as passive as possible. If there are e.g. calls like add/remove listener, they should be ignored. However, if methods are called that make it impossible for the object to comply with its interface specification, it should throw a com.sun.star.lang.DisposedException , which is derived from RuntimeException. Note that this is one of the very rare cases, when an implementation should throw a RuntimeException. Note that the above described situation may occur in a multithreaded environment (except calls to the object are externally synchronized ).

The owner/user concept may not be appropriate in any case, especially when there are more than one possible owners. In theses cases, there should be no owner but only users. Note, that it is then possible, that dispose gets called twice (in a multithreaded scenario).

XComponent.dispose gets also called, when it has not been called before and the refcount of the object drops to zero. This may happen, when the listeners do not hold a reference on the object.

XComponent does not need to be implemented, when there is only one owner and no further users.

com.sun.star.lang.XAggregation

Aggregation denotes the ability to stick two or more objects together and behave like one single object, though the interfaces of all objects are accessible from the outside.
Objects are aggregated in a hierarchical way, we denote the object that aggregates as the master and the object(s), that is/ are aggregated as the slave(s), as sketched here:

Slave objects implement a special interface called com.sun.star.uno.XAggregation. The master has major control over the object, i.e. all calls to queryInterface() are delegated to the master. If the master does not support the demanded interface, it calls queryAggregation() on its slaves.

C++ implementation warning:

If you use the cppu::OWeakAggObject ([include cppuhelper/weakagg.hxx], or any WeakAggImplHelperN<> implementation helper, [include cppuhelper/implbaseN.hxx]) to implement the com.sun.star.uno.XAggregation interface making your object aggregatable, then beware of reference counting.
After the aggregation of objects has been done, the new unified object has to behave like one and also the lifecycle has to be controlled in a unified way. This is commonly implemented that after aggregation, only the master's reference count will be modified.
The master will hold a hard reference on its aggregated slaves. The OWeakAggObject slaves store a weak references to its delegator, i.e. its master thus not modifying the master's reference count. Any time there is an acquire() or release() call on a master interface, then the master reference count is modified; any time there is an acquire() or release() call on a slave interface, then the master reference count is modified by delegation of that call, too. So these calls modify uniquely one reference count and everything is fine, if the reference count of all salves is one (1) right after the aggregation. By the meaning of one the only interface reference to slaves is held by the master (which has hard references to its slaves). Any other references having modified the reference count, e.g. to three, will corrupt the reference count of the master in a undefined manner! They will perform a release() which will be delegated to the master! These bugs are hard to fix, so beware of this.
As a general rule: only use aggregation in a well defined manner when constructing a new object. Don't use aggregation randomly with objects which are already heavily in use! This concerns also a UNO specification: The set of interfaces an UNO object is supporting does not change during the lifetime of that object! So at the time an object is already in use and interfaces are published to other objects, it is not allowed to support more interfaces by aggregation.

com.sun.star.lang.XUnoTunnel

The com.sun.star.lang.XUnoTunnel interface allows to access implementation dependent data ( in general the this-pointer of an object ). XUnoTunnel.getSomething takes a byte sequence as argument. This bytesequence contains an identifier, that both the caller and the implementer must know. The implementer returns the this pointer of the object, if the bytesequence is equal to the bytesequence, the implementer has stored with the object.

This interface is usually used, to distinguish, if behind certain interface reference hides on object you know, or if it is a completely unknown object. See the C++ introduction about this interface, to see an example, how it works.

Note : The use of this interface is highly disapproved and is nothing else than a bad hack, because components that rely on the XUnoTunnel-interface in general can't be exchanged. It is only an admittance to existing code, that can be split into components only with a large amount of work. But if you are designing interfaces from scratch, DON'T USE IT !.
 module com {  module sun {  module star {  module lang {  
/** the interface to tunnel UNO. This means providing access to data
	or something else, which is not specified in the UNO-IDL.
*/ [ uik(22260a3c-e868-11d3-86c60050-04d8bbbe), ident( "XUnoTunnel", 1.0 ) ] interface XUnoTunnel: com::sun::star::uno::XInterface { /** call this method to get something which is not specified in UNO. The identifier specifies how the return is specified. The identifier must be global unique, because it is unknown where the implementation lies. */ hyper getSomething( [in] sequence< byte > aIdentifier ); }; }; }; }; };

Mandatory interfaces for a service

Every service should implement the interfaces com.sun.star.lang.XServiceInfo and com.sun.star.lang.XTypeProvider.
  • com.sun.star.lang.XServiceInfo
    This interface allows to retrieve the implementation name of an object and the supported service names. Some generic mechanism can use this information for certain purposes. ( e.g. a framework for testing components can compare the runtime capabilities of the object with the service specification ).
  • com.sun.star.lang.XTypeProvider
    Some applications ( e.g. scripting-engines ) need to know, which interfaces a UNO-object supports. The XTypeProvider.getTypes() method must return a list of all interfaces, that can be accessed via querInterface. The method XTypeProvider.getImplementationId() returns a hash value, that uniquely identifies the combination of interfaces returned by getTypes() (this is done for performance reasons).

    Note : One implementation id is associated with one set of types. There may be the same set of types which is associated with multiple implementation ids.

module com {  module sun {  module star {  module lang {  
/** provides information regarding which services are implemented.
 */
[ uik(E227A3E0-33D6-11D1-AABE00A0-249D5590),
                                  ident( "XServiceInfo", 1.0 ) ]
interface XServiceInfo: com::sun::star::uno::XInterface
{ 
	/** @returns 
	     an identifier for the implementation of this object. 
		 
	     In general this should be a qualified identifier like  
		"com.sun.star.comp.office.writer.SwTextDocument".
	 */
	string getImplementationName();
 
	/** asks whether a service is supported or not.
	 */
	boolean supportsService( [in] string ServiceName ); 
 
	/**
	@returns a sequence of the names of all supported services. 
        This includes the services which are indirectly supported because 
        they are part of the specification of directly supported services.
	 */
	sequence getSupportedServiceNames(); 
};

[ uik(B163A7B0-C432-11d3-9F070050-04D8BBC7),
                                  ident( "XTypeProvider", 1.0 ) ]
interface XTypeProvider: com::sun::star::uno::XInterface
{ 
	 
	/** @returns 
	    	Sequence of all types (usually interface types)
		provided by the object. I
		mportant: If the object aggregates other objects
		the sequence also has to contain all types
		supportet by the aggregated objects.
	 */
	sequence< type > getTypes(); 
	 
	/** @returns 
		a UUID as sequence of 16 bytes as an Id that can be
		used to unambiguously distinguish between two sets
		of types, e.g. to realise hashing functionality 
		when the object is introspected. Two objects that
		return the same UUID also have to return the same
		set of types in getTypes(). If an unique
		implementation Id cannot be provided this method has
		to return an empty sequence. 
		Important: If the object aggregates other objects
		           the UUID has to be unique 
		           for the whole combination of objects.
	 */
	sequence getImplementationId(); 
}; 

}; }; }; };

UNO-runtime environment and com.sun.star.lang.XMain

[...] TODO

Properties and events

Events

The event concept is almost identical to the Java event concept. Events occur at one object, e.g. a property is changed or a user mouse move event. These events should be broadcasted to other interested objects. In general there is a 1 to n relation between broadcaster and listener.

Vetoable events allows the listener to abort a certain action ( e.g. the modification of a property or the closure of an document). Thus the notification calls are synchronous calls ( performance hit for remote listeners ! ). If one listener vetos, the action is aborted. The return value can give information about the abort reason. The following values do NOT signal an abort: (any,void), (interfacereference,null), (number,0),( boolean, true ), (string,empty). Vetoable listener methods should be prefixed with approve.

There exist unicast- and multicast-event-broadcaster. Only one listener can be added to the unicast-variant, which results in less flexibility and is therefor disapproved. Unicast broadcaster throw a TooManyListenerException when a second listener is added.

In general the listeners methods should not throw exceptions.

Pay extreme attention in not locking any resources when firing listeners (e.g. mutexes), because the deadlock-likelihood is very high.

Listeners may be added during firing the listeners. It is implementation dependent, whether these listeners are notified or not by these event.

All listeners must be derived from com.sun.star.uno.XEventListener. All event-structs should be derived from com.sun.star.uno.EventObject. The EventObject contains the broadcaster of the message, which is in general the object, that the listener was added to.

Example :
struct MouseMoveEvent : EventObject
{
   long X;
   long Y;
};

interface XMouseMoveListener : XEventListener
{
   // MouseMove occurred
   void mouseMoved( [in]MouseMoveEvent Event );
};

struct ResizeEvent : EventObject
{
   // do not use unsigned types, java doesn't support it
   long width; 
   long height;
};

interface XResizeListener : XEventListener
{
    // approve resize?
    boolean approveResize( [in]ResizeEvent Event );
    // resize occurred
    void resized( [in]ResizeEvent Event );
};

interface XVclComponent : XInterface
{ 
   void setLocation( [in] long X, [in] long Y );

   // add Listener to the broadcaster list
   void addMouseMoveListener( [in]XMouseMoveListener Listener ); 

   // remove Listener from the broadcaster list
   void removeMouseMoveListener( [in]XMouseMoveListener Listener ); 

   // add Resize-Listener to the broadcaster list
   void addResizeListener( [in]XMouseMoveListener Listener ); 

   // remove Resize-Listener from the broadcaster list
   void removeResizeListener( [in]XMouseMoveListener Listener ); 
};

Properties

Beside interfaces, uno-objects can export properties. E.g. in services, properties can be specified beside interfaces. Properties are in general implemented using XPropertySet , XFastPropertySet and XMultiPropertySet interfaces if the property concept is not supported directly by the language.

Properties allow generic tools ( such as a property editor ) to modify UNO-components at runtime. There exists 3 property categories :

  • Simple properties
    Simple properties can simply be modified. No one can get a notification of this change.
  • Bound properties
    On modification, a bound property fires a PropertyChangeEvent to all listeners. Listeners can be added using the add/removePropertyChangeListener( string propertyName, XPropertyChangeListener ) methods at the XPropertySet interface. If a property is set to a value it already had before, the listeners are not fired.
  • Constrained properties
    For a constrained property, XVetoableChangeListener can be added/removed. A constrained property fires before the property is changed. If one of the listeners does not want the property to change, the listener must throw a PropertyVetoException. If a property is set to a value it already had before, the listeners are not fired.

Author: Joerg Budischewski ($Date: 2001/08/09 13:34:28 $)
Copyright 2001 Sun Microsystems, Inc., 901 San Antonio Road, Palo Alto, CA 94303 USA.