ECT Component Design/Implementation Guidelines/Notes
Chris Greenhalgh 2005-04-22
General
Each ECT component is contained in (currently) its own JAR file, along
with any component-specific supporting classes and a BeanInfo class. This JAR file
("capability") is normally deployed via the download/java/components
directory. Any supporting JAR files are DLLs deployed via the download/java/common directory.
BeanInfo
Currently, every ECT component must have its own BeanInfo class (i.e. a
class which implements the java.beans.BeanInfo
interface) and whose name (including package) is the same as that of
the main component class followed by BeanInfo. E.g. the component class
foo.bar.MyComponent has an
accompanying Bean Info class foo.bar.MyComponentBeanInfo
in the same JAR file. This requirement is enforced by equip.ect.ContainerManagerHelper
which looks inside component JAR files and determines which class
(currently only the first one found) is the main component class to be
instantiated for that capability. [in future, properties in the
manifest might be used to avoid the need for explicit BeanInfo classes
and/or multiple components might be allowed in a single JAR]
BeanDescriptor
Every Bean's BeanInfo provides a Bean descriptor (java.beans.BeanDescriptor)
which can include:
- name - programmatic name, i.e. class
- displayName - intended to be shown to a user
- shortDescription - a short description(!)
- expert (boolean) - for expert rather than "normal" users
- hidden (boolean) - for tool use only
- preferred (boolean) - if particularly important for presenting to
humans
- an open set of named attributes
- the bean's class (java.lang.Class)
- the bean's customizer class if any (java.jang.Class)
It is suggested that for ECT:
- displayName be used as the normally presented bean name, e.g. in
editor views.
- shortDescription be provided as a general description of the
component, optionally using HTML text formatting, to be available to
users in the editor views for both the capability and any instances of
that component.
- preferred be used for end-user configurable (normally
experience-time) properties
- expert be used for anciliary properties not essential to normal
configuration or use
- hidden not normally be used
- neither preferred nor expert be used for normal
configuration/assembly properties
- attributes (to be determined) be used for specifying e.g.
semantic concepts
PropertyDescriptors
Each component property is described by a Property Descriptor (java.beans.PropertyDescriptor).
This identifies the properties "external" name, its set and get methods
(normally both, or just a get method for read-only properties), the
property's type (java class, normally automatically determined) and the
same additional information as for a Bean Description, above. It is
suggested that the same convention is used.
Icon
A Bean Info can provide an icon for its bean, in one or more
combinations of 16x16 or 32x32 and mono or colour. 16x16 colour is
recommended as a minimum, with a transparent background.
BeanInfo Implementation
Fully Implemented BeanInfo
Up to now most/all components have implemented a BeanInfo class by
extending the java convenience class java.beans.SimpleBeanInfo and
overriding (usually only) the getPropertyDescriptors method in order to
describe its properties with their setters and getters. So far most
components have not provided the additional information identified
above.
Introspected BeanInfo
Currently a BeanInfo class must exist to allow the component to be
detected and exported. However, if the provided BeanInfo class returns null (not a zero sized array)
from any of its methods then reflection will be used to determine the
component's properties, etc. using the normal java bean idiom (e.g.
"public T getN()", "public void setN(T)" for properties). While this
avoids having to implement a (tedious) BeanInfo class, it does not
allow additional human-oriented information to be provided (see above).
Javadoc-generated BeanInfo
There is now a custom Java Doclet which will generate a BeanInfo file
from the corresponding Bean .java file, using the standard
method-naming convention and incorporating additional information from
a subset of the Bean's Javadoc comments. See the target "beaninfotest"
in build.xml to see how to do this (in that case for the Template
bean). Currently the new BeanInfo file will be generated in the
top-level directory, and it is suggested that this is copied to the
component directory and checked in there. The Javadoc entries and tags
used are:
- The whole class's javadoc comment
- First sentance becomes the BeanInfo shortDescription, which is
shown as a tooltip in the graph editor.
- Whole comment (minus tags) is published as a "htmlDescription" attribute.
- If a @displayName
tag is specified then this value rather than the class name is used as
the Bean/component displayName
- If a @preferred
tag is specified then preferred is set to true in the BeanDescriptor
- If a @expert tag
is specified then expert is set to true in the BeanDescriptor
- The get method java comment for a read-only attribute, and the
set method java comment for a read-write attribute
- Details as per whole class/bean
Future possibility: File-configured BeanInfo
How about using an XML file to create the BeanInfo...
Note that the Introspector will share BeanInfo classes, so each
component will need to have its own unique BeanInfo class. However,
this could just be a trivial (no code) extension of a common class,
which based on its own name would read a resource and create
appropriate Descriptors...
Furthermore, the resource could be autogenerated by a custom Doclet (or
similar?!) processing the component source, so that the same
information is included in the Javadocs for the component...
Life-cycle and Persistence
Creation
All bean must have a public no-args constructor, which will be used to
create instances. This should leave the bean in a usable but
unconfigured state.
Destruction
If the bean makes use of resources that need to be released (e.g.
COM ports), then it must implement a method with signature "public void stop()" which will
be called by the container when the component is being
stopped/destroyed, and which should release all resources.
Note that external problems, e.g. termporary loss of remote
services, may cause the "same" component instance to be stopped and
subsequently recreated within the same container. Therefore it is
important that - if at all possible - stopping a component does return
all resources to a state where a similar component can be created anew.
Configuration
In general a component should be prepared to accept changes to its
input (writable) properties in any order and at any time. However this
is quite difficult with some types of components, e.g. those
interfacing to particular hardware devices. In this case a two-phase
life-cycle model is provided, with the phases:
- unconfigured - as created, ready to be configured (e.g. to have a
COM port and baud rate specified).
- configured (also known as running) - all configuration
information has been provided and the component is now in a distinct
active state; further configuration may now be impossible.
While unconfigured the various configuration-specific parameters
should be set (and settable). Once fully configured the property
"configured" (if present) should be set to true. The component should
now respond to any other (non-configuration) properties.
NB to allow the container
persistence and configuration manager to correctly initialise such
components it is required that all properties that must be set before
the component is fully configured begin with the string prefix
"config". If the component needs to know when it has been configured
then it should provide a binary property called "configured". This will
be set after the other "config..." properties and before any other
writeable properties.
Events
At present, only property change events are supported by the
container.
Note that the container relies on the name and value in the property
change events to track changes to properties being set. Therefore the
editor (and other external components) will only "see" the change made
to a property if a corresponding property change event is fired by the
component in each set method, with the correct property name (as
specified in the component's BeanInfo, NOT its actual method name).
Property Types
Most properties should have simple types. In particular, the system
has a number of standard (best effort) type coercions, e.g. between all
java primitive types, and also strings and 1D arrays of any of these.
These types should be used wherever possible for simplicity and
interoperability. E.g. the graphical editor views and sets all property
values as strings, and the component's container uses this coercion to
get a value of the appropriate target type.
If a property's type is a 1D array of a Serializable class then the
container will normally assume that these are sub-components of the
parent component, and will expose each element of the array to the
infrastructure as a sub-component with its own properties, etc. It will
add a synthetic "parent" property identifying the parent component. A
sub-component should also have a property of type string called
"persistentChild", which is used a key for sub-component persistence.
I.e. if a parent component is recreated by the persistence mechanism,
then the new child components will be matched to equivalent old child
components based on having the same value for their "persistentChild"
property. Therefore this must be consistent and meaningful (e.g. a X10
subcomponent corresponding to a particular lamp module should always
have a consistent persistentChild property value, typically the X10
address of that particular module).
Other properties which are not Serializable are currently assumed to
direct references to internal Java objects that should be passed
directly by reference between components. Currently, this will not work
between components in different JVMs. This is used by the video and
audio framework components to allow video consuming components to get
rapid access to individual video frames via a shared object without
each operation invocation being routed via the dataspace. Note that
this also means that such invocations are not logged and cannot be
replayed.