Sapphire Developer Guide > Releases > 8

Enhancements in Sapphire 8

  1. Key Enhancements
    1. Simpler Element References
    2. Refactoring
    3. Event Suspension
    4. Outline Decorations
  2. Core
    1. Containment Determination
    2. Disposable
    3. EventDeliveryJob
    4. JobQueue
    5. List Index Null Lookup
    6. @Unique Null Coverage
  3. Expression Language
    1. Use in @PossibleValues
  4. Forms
    1. Java Package Browsing
    2. Check Box Group
  5. XML
    1. StandardXmlValueBindingImpl

Simpler Element References

A common use of references in the model is to refer to another model element. This has been possible in previous releases by implementing a custom ReferenceService, but getting this service implemented correctly is not trivial. The new @ElementReference annotation and ElementReferenceService abstract class greatly simplify this use case.

In many situations, the reference semantics can be specified declaratively using the @ElementReference annotation.

@Reference( target = Table.class )
@ElementReference( list = "/Tables", key = "Name" )

ValueProperty PROP_TABLE = new ValueProperty( TYPE, "Table" );

ReferenceValue<String,Table> getTable();
void setTable( String value );

When more control is necessary, a custom implementation of ElementReferenceService can be provided. This is necessary, for example, when the referenced elements are located in a different model or when the list and key are variable. ElementReferenceService provides a common implementation base for ReferenceService implementations that resolve to an Element.

@Reference( target = Table.class )
@Service( impl = ExampleElementReferenceService.class )

ValueProperty PROP_TABLE = new ValueProperty( TYPE, "Table" );

ReferenceValue<String,Table> getTable();
void setTable( String value );
public class ExampleElementReferenceService extends ElementReferenceService
{
    @Override
    protected void initReferenceService()
    {
        // Attach a listener if list or key are variable.
        // Call broadcast( new ListEvent() ) if the list changes.
        // Call broadcast( new KeyEvent() ) if the key changes.
        
        super.initReferenceService();
    }
    
    @Override
    protected ElementList<?> list()
    {
        ...
    }

    @Override
    protected String key()
    {
        ...
    }

    @Override
    public void dispose()
    {
        // Detach any listeners that were added during init.
        
        super.dispose();
    }
}

A PossibleValuesService implementation is automatically provided when @ElementReference annotation is used or ElementReferenceService is implemented.

Refactoring

When writing a value property, the originator of the change can now indicate that further model refactoring should be allowed. This flag is carried by the new ValuePropertyContentEvent and any listener can take action accordingly.

For instance, a ReferenceService implementation can update the reference when the target changes. The new @ElementReference and ElementReferenceService facilities provide such refactoring out of the box.

public class Value<T> extends Property
{
    /**
     * Updates the value of the property. This method variant does not allow further model refactoring.
     * 
     * @param content the new value for the property, either in typed form or as an equivalent string; null is allowed
     */
    
    void write( Object content )
    
    /**
     * Updates the value of the property.
     * 
     * @param content the new value for the property, either in typed form or as an equivalent string; null is allowed
     * @param refactor indicates whether refactoring actions can be taken as the result of the property change
     */
    
    void write( Object content, boolean refactor )
}
public final class ValuePropertyContentEvent extends PropertyContentEvent
{
    /**
     * Returns the property content before the change.
     */
    
    String before()
    
    /**
     * Returns the property content after the change.
     */
    
    String after()
    
    /**
     * Indicates whether the originating property change was flagged to allow refactoring. If true is returned, any listeners
     * can take action accordingly. For example, a ReferenceService implementation can update the reference when the target changes.
     */
    
    boolean refactor()
}

Event Suspension

Model events can now be suspended while performing a multi-step operation. This can be helpful in avoiding distracting flashing of the UI as the model goes through the intermediate states.

Element
{
    /**
     * Suspends all events related to this element and everything beneath it in the model tree. The suspended
     * events will be delivered when the suspension is released.
     * 
     * @return a handle that must be used to release the event suspension
     */
    
    Disposable suspend()
}
Property
{
    /**
     * Suspends all events related to this property and everything beneath it in the model tree. The suspended
     * events will be delivered when the suspension is released.
     * 
     * @return a handle that must be used to release the event suspension
     */
    
    Disposable suspend()
}
final ElementList<PurchaseOrderEntry> entries = po.getEntries();
final Disposable suspension = entries.suspend();

try
{
    final PurchaseOrderEntry entry = entries.insert();
    entry.setItem( "USB Cable" );
    entry.setQuantity( 2 );
    entry.setUnitPrice( "2.5" );
}
finally
{
    suspension.dispose();
}

Outline Decorations

Multiple text decorations can now be added to the nodes in the master-details editor page outline in order to show additional information in a way that is set apart from the main node label.

A definition of a text decoration is composed of text and an optional color. Both of these properties support Sapphire EL. When text is null, the decoration is not shown. Multiple decorations can be specified for one node.

<node-factory>
    <property>Categories</property>
    <case>
        <label>${ Name == null ? "Uncategorized" : Name }</label>
        <text-decoration>
            <text>${ ( Items.Size == 0 ? null : Concat( "(", Items.Size, ")" ) ) }</text>
        </text-decoration>
    </case>
</node-factory>

The framework API has changed accordingly.

@Label( standard = "text decoration" )
@XmlBinding( path = "text-decoration" )

public interface TextDecorationDef extends Element
{
    ElementType TYPE = new ElementType( TextDecorationDef.class );
    
    // *** Text ***
    
    @Type( base = Function.class )
    @Label( standard = "text" )
    @Required
    @XmlBinding( path = "text" )
    
    ValueProperty PROP_TEXT = new ValueProperty( TYPE, "Text" );
    
    Value<Function> getText();
    void setText( String value );
    void setText( Function value );
    
    // *** Color ***
    
    @Type( base = Function.class )
    @Label( standard = "color" )
    @DefaultValue( text = "#957D47")
    @XmlBinding( path = "color" )
    
    ValueProperty PROP_COLOR = new ValueProperty( TYPE, "Color" );
    
    Value<Function> getColor();
    void setColor( String value );
    void setColor( Function value );
}
public interface MasterDetailsContentNodeDef
{
    // *** Decorations ***
    
    @Type( base = TextDecorationDef.class )
    @Label( standard = "decorations" )
    @XmlListBinding( path = "" )
    
    ListProperty PROP_DECORATIONS = new ListProperty( TYPE, "Decorations" );
    
    ElementList<TextDecorationDef> getDecorations();
}
public final class TextDecoration
{
    public String text()
    public Color color()
}
public final class MasterDetailsContentNodePart
{
    public List<TextDecoration> decorations()
}

Containment Determination

The developer can now easily determine whether an element or a property is located within a model tree that has another element or a property as the root.

Element
{
    public boolean holds( Element element )
    public boolean holds( Property property )
}
Property
{
    public boolean holds( Element element )
    public boolean holds( Property property )
}

Disposable

Sapphire classes and interfaces that have a dispose() method now extend a common interface Disposable.

interface Disposable
{
    /**
     * Performs the necessary disposal steps.
     */
     
    void dispose()
}

EventDeliveryJob

Used by ListenerContext to deliver an Event to a Listener through a JobQueue.

final class EventDeliveryJob implements Runnable
{
    /**
     * Returns the listener to which the event is to be delivered.
     */
    
    public Listener listener()
    
    /**
     * Returns the event which is to be delivered to the listener.
     */
    
    public Event event()
}

JobQueue

A generic queue for processing jobs. Used by ListenerContext to deliver events to listeners. A single JobQueue can be shared by multiple ListenerContext instances in order to synchronize event delivery.

final class JobQueue<T extends Runnable>
{
    /**
     * Adds a job to the end of the queue.
     * 
     * @param job the job
     * @throws IllegalArgumentException if the job is null
     */
    
    public void add( T job )
    
    /**
     * Processes all of the jobs in the queue until the queue is empty or all of the remaining jobs are suspended
     * through the attached filters.
     */
    
    public void process()
    
    /**
     * Removes jobs from the queue that are rejected by the filter.
     * 
     * @param filter the filter
     * @throws IllegalArgumentException if the filter is null
     */
    
    public void prune( Filter<T> filter )
    
    /**
     * Suspends jobs that are rejected by the filter. The suspended jobs will again become available for
     * processing once the suspension is released. 
     * 
     * @param filter the filter
     * @return a handle that must be disposed to release the suspension
     * @throws IllegalArgumentException if the filter is null
     */
    
    public Disposable suspend( Filter<T> filter )
}

List Index Null Lookup

The developer can now lookup elements in an index that have a null value for the indexed property. Previously, null values were not indexed and an exception was thrown on lookup.

Set<Task> tasksWithNoComponent = repository.getTasks().index( "Component" ).elements( null );

@Unique Null Coverage

By default, the @Unique constraint does not apply to null values. This is appropriate in many cases, but sometimes the semantics dictate that at most one null should be allowed. The @Unique constraint can now be configured to cover this case.

interface ListEntry extends Element
{
    ElementType TYPE = new ElementType( ListEntry.class );
    
    // *** Value ***
    
    @Unique( ignoreNullValues = false )
    
    ValueProperty PROP_VALUE = new ValueProperty( TYPE, "Value" );
    
    Value<String> getValue();
    void setValue( String value );
}

Use EL in @PossibleValues

Use EL in the @PossibleValues when specifying a custom invalid value message.

@PossibleValues
( 
    property = "/Contacts/Name",
    invalidValueMessage = "Could not find contact \"${Name}\" in the repository" 
)

Java Package Browsing

A browse dialog is now provided for value properties of JavaPackageName type when in the context of a Java project.

@Type( base = JavaPackageName.class )

ValueProperty PROP_PACKAGE = new ValueProperty( TYPE, "Package" );
    
Value<JavaPackageName> getPackage();
void setPackage( String value );
void setPackage( JavaPackageName value );

Check Box Group

Sapphire already includes a slush bucket and a check box list presentation alternatives for a possible values list.

However, in some cases where the set of possible values is known to always be small, a more compact presentation is desired. The new check box group presentation fills this need.

The check boxes can be arranged either horizontally or vertically.

The presentation will utilize ValueImageService and ValueLabelService, if present on the list entry's value property. The services must be attached to the value property's global service context.

Applicability

  1. The property is a list property
  2. The list property has a PossibleValuesService
  3. There is exactly one possible member type
  4. The member type has exactly one property and that property is a value property
  5. The value property has @Unique annotation

Automatic Activation

This presentation does not activate automatically.

Manual Activation

The following style codes will activate this presentation as long as the applicability conditions are met.

<property-editor>
    <property>Colors</property>
    <style>Sapphire.PropertyEditor.CheckBoxGroup.Vertical</style>
</property-editor>

StandardXmlValueBindingImpl

StandardXmlValueBindingImpl can now be extended to customize standard binding behavior. In some cases, this may be more convenient than starting from scratch with XmlValueBindingImpl.

StandardXmlValueBindingImpl extends XmlValueBindingImpl
{
    protected XmlPath path
    protected boolean treatExistanceAsValue
    protected String valueWhenPresent
    protected String valueWhenNotPresent
    protected boolean removeNodeOnSetIfNull
    
    protected void initBindingMetadata()
    public String read()
    public void write( String value )
    public XmlNode getXmlNode()
}