Sapphire Developer Guide > Releases > 0.4

Enhancements in 0.4

  1. Expression Language
    1. Scale Function
    2. Aggregate Functions
    3. Use in @Derived
    4. Use in @DefaultValue
    5. Use in @InitialValue
  2. Services
    1. FactsService
    2. InitialValueService and @InitialValue
    3. PossibleTypesService
    4. ContentProposalService
    5. JavaTypeConstraintService
  3. User Interface
    1. Split Form
    2. Multi-Section Form Editor Page
  4. XML
    1. @XmlDocumentType Annotation
    2. @XmlSchema Annotation
    3. List and Element Property Binding Without Mappings
  5. Miscellaneous
    1. Model Copy Method

Scale Function

When working with decimals, controlling the scale of the number is very important, whether to decrease the scale in order to round the number or to increase the scale ahead of arithmetic operations.

Example

${ Scale( UnitPrice * Scale( Quantity, 2 ), 2 ) }

Aggregate Functions

Compute the sum, the average, the min or the max of numbers in a collection. Typically, these function takes the collection as the sole parameter. However, when the collection is a model element list, a second parameter may be necessary to specify the name (in the form of a string) of the list entry's value property to use in aggregation. If the the collection is a model element list and the second parameter is not specified, these functions will use list entry's first value property for aggregation.

Examples

${ Sum( Entries, 'Total' ) }
${ Avg( Entries, 'Total' ) }
${ Min( Entries, 'Total' ) }
${ Max( Entries, 'Total' ) }
${ Sum( List( 1.2, 3.4, 5.6, 7.8 ) ) }

Use in @Derived

Use expression language when specifying derived value in @Derived annotation.

@Type( base = BigDecimal.class )
@Derived( text = "${ Scale( UnitPrice * Scale( Quantity, 2 ), 2 ) }" )

ValueProperty PROP_TOTAL = new ValueProperty( TYPE, "Total" );

Value<BigDecimal> getTotal();

Use in @DefaultValue

Use expression language when specifying default value in @DefaultValue annotation.

@DefaultValue( text = "${ Parent().BillingInformation.Street }" )

ValueProperty PROP_STREET = new ValueProperty( TYPE, "Street" );

Value getStreet();
void setStreet( String street );

Use in @InitialValue

Use expression language when specifying initial value in @InitialValue annotation.

@InitialValue( text = "${ Parent().BillingInformation.Street }" )

ValueProperty PROP_STREET = new ValueProperty( TYPE, "Street" );

Value getStreet();
void setStreet( String street );

FactsService

When a property is described to a user in documentation one does it with a series of short statements that define its semantics, such as "must be specified" or "maximum value is 100". When a property is described to Sapphire one does it with a series of annotations, such as @Required or @NumericRange. This duplicate specification is a maintenance problem.

A FactsService provides a means to dynamically derive statements about property's semantics based on property's metadata. The derived facts can then be presented to the user as part of documentation, property editor information popup and in other relevant places.

The concept of deriving facts from metadata has existed in limited capacity in past releases. New for 0.4 is formalization of this system via FactsService API that allows adopters to participate in generation of facts on equal footing with the framework. Also new for 0.4 is a much longer list of annotations that generate corresponding facts.

See Documentation

InitialValueService and @InitialValue

The InitialValueService produces a value to assign to a property when the containing model element is created.

The concept of an initial value is different from a default value. The initial value is explicitly assigned to the property during containing model element's creation. This includes writing to the backing resource (such as an XML document). In comparison, the default value is used when null is read for a property from the backing resource. As such, the default value is only visible to model consumers (such as the user interface), while the initial value is persisted.

Whether you use an initial value or a default value is frequently dictated by the requirements of the backing resource. As an example, let's consider an XML document that stores phone numbers. In this XML document, the phone number element has a type child element which contains a value like home, mobile, work, etc. Let's further say that semantically, we wish to use mobile phone number type unless specified differently. Now, if the XML schema dictates that the phone number type element is required, we would need to specify "mobile" as the initial value. If the phone number type element is optional, it would be better to specify "mobile" as the default value.

See Documentation

PossibleTypesService

The set of possible types for a list or an element property is usually static and as such can be specified using @Type annotation. However, when the model needs to be extensible or the set of possible types needs to vary due to runtime conditions, a custom PossibleTypesService implementation can now be supplied.

See Documentation

ContentProposalService

The ContentProposalService provides a conduit for content assist in property editors. If a property has this service, content assist will be automatically enabled in the property editor. The manner in which content assist is presented is specific to the presentation, but usually involves a popup window with proposals, activated by some combination of key strokes (such as CTRL+SPACE).

The framework provides an implementation of ContentProposalService for properties with @PossibleValues annotation or a custom PossibleValuesService, but this service can also be implemented directly by adopters.

See Documentation

JavaTypeConstraintService

The JavaTypeConstraintService describes constraints on the Java type that a property can reference, such as the kind of type (class, interface, etc.) and the types that the type must extend or implement. The information provided by this service is used for validation, content assist and other needs.

The framework provides an implementation of JavaTypeConstraintService for properties with @JavaTypeConstraint annotation, but this service can also be implemented directly by adopters. This is particularly useful when Java type constraint can vary at runtime.

See Documentation

Split Form

Use available screen real estate more efficiently by splitting a form into two or more sections. The sections can be arranged vertically or horizontally. The developer can specify the desired initial distribution of available space among the sections and the provided sashes allow the user to adjust the space allocation as necessary at runtime.

A split form can nest inside another split form. This is particularly useful when the two split forms use different split orientation.

Example

In this example, a dialog is split vertically into two sections. The top section holds a list property editor while the bottom section holds the editor for the description property linked to the selection in the list property editor. The split form allows the user to control the amount of space allocated to the list property editor versus the description text field.

<dialog>
    <id>SplitFormDialog</id>
    <label>split form dialog</label>
    <width>600</width>
    <height>400</height>
    <scale-vertically>true</scale-vertically>
    <content>
        <split-form>
            <orientation>vertical</orientation>
            <scale-vertically>true</scale-vertically>
            <block>
                <weight>7</weight>
                <content>
                    <property-editor>
                        <property>List1</property>
                        <span>true</span>
                        <show-label>false</show-label>
                        <child-property>Entity</child-property>
                        <child-property>Size</child-property>
                        <scale-vertically>true</scale-vertically>
                    </property-editor>
                </content>
            </block>
            <block>
                <weight>3</weight>
                <content>
                    <separator>
                        <label>description</label>
                    </separator>
                    <switching-panel>
                        <list-selection-controller>
                            <property>List1</property>
                        </list-selection-controller>
                        <default-panel>
                            <content>
                                <label>Select an entry above to view or edit description.</label>
                            </content>
                        </default-panel>
                        <panel>
                            <key>SplitFormGalleryListEntry</key>
                            <content>
                                <property-editor>
                                    <property>Description</property>
                                    <span>true</span>
                                    <show-label>false</show-label>
                                    <scale-vertically>true</scale-vertically>
                                </property-editor>
                            </content>
                        </panel>
                        <scale-vertically>true</scale-vertically>
                    </switching-panel>
                </content>
            </block>
        </split-form>
    </content>
</dialog>

Multi-Section Form Editor Page

Create form editor pages with highly configurable section layouts. The feature is used in conjunction with split form feature to break the page into resizable blocks where sections are places.

Example

In this example, two multi-section form editor pages are used to organize purchase order entry fields.

<form-editor-page>
    <id>GeneralPage</id>
    <page-name>general</page-name>
    <page-header-text>purchase order</page-header-text>
    <content>
        <split-form>
            <orientation>horizontal</orientation>
            <scale-vertically>true</scale-vertically>
            <block>
                <content>
                    <section>
                        <label>general</label>
                        <content>
                            <property-editor>Id</property-editor>
                            <property-editor>Customer</property-editor>
                            <property-editor>InitialQuoteDate</property-editor>
                            <property-editor>OrderDate</property-editor>
                            <property-editor>FulfillmentDate</property-editor>
                        </content>
                    </section>
                </content>
            </block>
            <block>
                <content>
                    <section>
                        <label>billing information</label>
                        <content>
                            <with>
                                <path>BillingInformation</path>
                                <default-panel>
                                    <content>
                                        <property-editor>Name</property-editor>
                                        <property-editor>Organization</property-editor>
                                        <property-editor>Street</property-editor>
                                        <property-editor>City</property-editor>
                                        <property-editor>State</property-editor>
                                        <property-editor>ZipCode</property-editor>
                                    </content>
                                </default-panel>
                            </with>
                        </content>
                    </section>
                    <section>
                        <label>shipping information</label>
                        <content>
                            <with>
                                <path>ShippingInformation</path>
                                <default-panel>
                                    <content>
                                        <property-editor>Name</property-editor>
                                        <property-editor>Organization</property-editor>
                                        <property-editor>Street</property-editor>
                                        <property-editor>City</property-editor>
                                        <property-editor>State</property-editor>
                                        <property-editor>ZipCode</property-editor>
                                    </content>
                                </default-panel>
                            </with>
                        </content>
                    </section>
                </content>
            </block>
        </split-form>
    </content>
</form-editor-page>
<form-editor-page>
    <id>EntriesPage</id>
    <page-name>entries</page-name>
    <page-header-text>purchase order</page-header-text>
    <content>
        <split-form>
            <orientation>horizontal</orientation>
            <scale-vertically>true</scale-vertically>
            <block>
                <content>
                    <section>
                        <label>entries</label>
                        <content>
                            <property-editor>
                                <property>Entries</property>
                                <scale-vertically>true</scale-vertically>
                                <show-label>false</show-label>
                                <span>true</span>
                                <child-property>Item</child-property>
                                <child-property>Description</child-property>
                                <child-property>Quantity</child-property>
                                <child-property>UnitPrice</child-property>
                                <hint>
                                    <name>column.widths</name>
                                    <value>100:1,100:6,100:1,100:1,100:1</value>
                                </hint>
                                <child-property>Total</child-property>
                            </property-editor>
                        </content>
                        <scale-vertically>true</scale-vertically>
                    </section>
                </content>
                <weight>7</weight>
            </block>
            <block>
                <weight>3</weight>
                <content>
                    <section>
                        <label>summary</label>
                        <content>
                            <property-editor>Subtotal</property-editor>
                            <property-editor>Discount</property-editor>
                            <property-editor>Delivery</property-editor>
                            <property-editor>Total</property-editor>
                        </content>
                    </section>
                </content>
            </block>
        </split-form>
    </content>
</form-editor-page>

@XmlDocumentType Annotation

The new @XmlDocumentType annotation can be used when working with DTD-based documents to easily specify systemId or publicId/systemId pair. Sapphire XML binding will manage the XML DOCTYPE declaration.

Example

@XmlDocumentType( publicId = "-//Sapphire//DTD Root 1.0.0//EN", systemId = "http://www.eclipse.org/sapphire/example.dtd" )
@XmlBinding( path = "root" )

public interface Root extends IModelElement
{
    ...
}

The above declaration will create an XML document like the following.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE country PUBLIC "-//Sapphire//DTD Country 1.0.0//EN" "http://www.eclipse.org/sapphire/example.dtd">
<root>
    ...
</root>

@XmlSchema Annotation

The new @XmlSchema annotation can be used when working with XSD-based documents to easily specify schema location for a given namespace. Sapphire XML binding will manage the xsi:schemaLocation declaration.

Example

@XmlNamespace( uri = "http://www.eclipse.org/sapphire/example", prefix = "e" )
@XmlSchema( namespace = "http://www.eclipse.org/sapphire/example", location = "http://www.eclipse.org/sapphire/example/1.0" )
@XmlBinding( path = "e:root" )

public interface Root extends IModelElement
{
    ...
}

The above declaration will create an XML document like the following.

<?xml version="1.0" encoding="UTF-8"?>
<e:root 
    xmlns:e="http://www.eclipse.org/sapphire/example"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://www.eclipse.org/sapphire/example http://www.eclipse.org/sapphire/example/1.0">
    ...
</e:root>

To specify locations of multiple schemas, use @XmlSchemas annotation.

List and Element Property Binding Without Mappings

When declaratively specifying XML binding for a list or an element property, the adopter must tell the framework what XML element name to use for a given child model element type. In the past, the only way to do that was via the mappings attribute of @XmlListBinding or @XmlElementBinding annotation. These declarations can now be made via @XmlBinding annotations placed on the model element type. If the property's mappings do not contain a declaration for a given model element type, the framework will look for the type's @XmlBinding annotation.

Which approach you choose to use depends on the requirements. Explicitly specifying mappings is useful when the same model element type is used in different properties where it must bind to different XML element names. Using @XmlBinding on model element type instead of mappings is less verbose and is particularly useful in cases where a custom PossibleTypesService is provided. In those situations, list or element property's author may not be able to enumerate possible types at design time, so explicitly specifying mappings is not possible.

Example

The following two listings are equivalent.

@GenerateImpl
@XmlBinding( path = "root" )

public interface Root extends IModelElement
{
    ModelElementType TYPE = new ModelElementType( Root.class );

    // *** List ***

    @Type( base = Child.class, possible = { ChildA.class, ChildB.class } )
    @XmlListBinding( path = "" )

    ListProperty PROP_LIST = new ListProperty( TYPE, "List" );

    ModelElementList<Child> getList();

    ...
}

public interface Child extends IModelElement
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}

@GenerateImpl
@XmlBinding( path = "a" )

public interface ChildA extends Child
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}

@GenerateImpl
@XmlBinding( path = "b" )

public interface ChildB extends Child
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}
@GenerateImpl
@XmlBinding( path = "root" )

public interface Root extends IModelElement
{
    ModelElementType TYPE = new ModelElementType( Root.class );

    // *** List ***

    @Type( base = Child.class, possible = { ChildA.class, ChildB.class } )

    @XmlListBinding
    (
        mappings = 
        {
            @XmlListBinding.Mapping( element = "a", type = ChildA.class ),
            @XmlListBinding.Mapping( element = "b", type = ChildB.class )
        }
    )

    ListProperty PROP_LIST = new ListProperty( TYPE, "List" );

    ModelElementList<Child> getList();

    ...
}

public interface Child extends IModelElement
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}

@GenerateImpl

public interface ChildA extends Child
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}

@GenerateImpl

public interface ChildB extends Child
{
    ModelElementType TYPE = new ModelElementType( Child.class );

    ...
}

Model Copy Method

Easily copy content of model elements using the new copy method. Copy a few model elements or the entire model. Even copy across different resource stores.

Example

In this example, the copy method is used to duplicate the entities held in a list property.

ModelElementList<Entity> entities = model.getEntities();

for( int i = 0, n = entities.size; i < n; i++ )
{
    final Entity original = entities.get( i );
    final Entity duplicate = entities.addNewElement();
    duplicate.copy( original );
}

Here, an entire model is copied to a memory resource. Perhaps the original model instance was loaded from disk via an XML resource.

Model original = ...
Model duplicate = Model.TYPE.instantiate();
duplicate.copy( original );