Wednesday, December 18, 2013

Announcing Sapphire 0.7 Release

On behalf of all who contributed, I am very proud to announce the availability of the Sapphire 0.7 release. This is the biggest release in Sapphire’s history. It includes major enhancements, such the new diagram node shape definition language, runtime bytecode generation of implementation classes, improved API for property instances, and much more.

Enhancements
Migration Guide
Developer Guide
Downloads

Sapphire 0.7 : Diagram Node Shape Language

The keystone feature of Sapphire 0.7 release is the brand new diagram node shape definition language. Use it to construct complex diagram editors with the same ease as forms.

DiagramNodeShapeLanguage-1

The language is composed of six primitives.

TextDefText - Displays static text or content from the model, as specified by an expression. If the expression contains a reference to a single property, the property can be edited in place by double-clicking on the text.

LineDefLine - Displays a vertical or a horizontal line. Typically used as a separator. Line style, weight and color can be set as appropriate.

SpacerDefSpacer - Leaves a blank space of specified size. Frequently in conjunction with a visibility rule.

ImageDefImage - Displays an image. The image can be produced by an expression for a dynamic effect.

ValidationMarkerDefValidation Marker - Displays an image communicating the validation status of the immediate context. The user can click on the image to get the validation details and the related actions.

RectangleDefRectangle - Contains one or more shape in a rectangular space with an optional border and background. The contained shapes are arranged either vertically, horizontally or in a stack, as specified by the rectangle's layout.

ShapeFactoryDefShape Factory - Produces a shape for each entry in a list property using the specified shape templates. The list items can be added, removed and re-arranged through actions exposed by the shape factory. Drag-n-drop is also supported as a means to re-order the list items.

Friday, December 13, 2013

Sapphire 0.7 : Improved Localization for Java

Within Java code, such as an implementation of a service, Sapphire has previously relied on the NLS class copied from Eclipse platform. The developer experience has been improved.

  • The static fields holding localizable text are able to exist in any class, not just a dedicated resource bundle class.

  • Formatting messages using localizable text as a template is more fluid.

    Resources.message.format( x, y )  vs  NLS.bind( Resources.message, x, y )

  • The original text can be specified using an annotation, concentrating text closer to the point of use and avoiding numerous problems associated with maintaining text in discrete resource files. When time comes to do the translation, an annotation processor or a similar build system can be used to extract text into resource files. Only the translated versions of the resource files need to be distributed. The original text will be read from the annotations at runtime.

Before After
public class Validator
{
    public String validate( Integer value, Integer max )
    {
        if( value == null )
        {
            return Resources.mustBeSpecifiedMessage;
        }
        else if( max != null && value.intValue() > max.intValue() )
        {
            return NLS.bind
            (
                Resources.mustNotBeLargerThanMessage, 
                max
            );
        }

        return null;
    }

    private static final class Resources extends NLS 
    {
        public static String mustBeSpecifiedMessage;
        public static String mustNotBeLargerThanMessage;

        static 
        {
            initializeMessages
            (
                Validator.class.getName(),
                Resources.class
            );
        }
    }
}

# Content of Validator.properties file

mustBeSpecifiedMessage = Value must be specified.
mustNotBeLargerThanMessage = Value must not be larger than {0}.
public class Validator
{
    @Text( "Value must be specified." )
    private static LocalizableText mustBeSpecifiedMessage;

    @Text( "Value must not be larger than {0}." )
    private static LocalizableText mustNotBeLargerThanMessage;

    static
    {
        LocalizableText.init( Validator.class );
    }

    public String validate( Integer value, Integer max )
    {
        if( value == null )
        {
            return mustBeSpecifiedMessage.text();
        }
        else if( max != null && value.intValue() > max.intValue() )
        {
            return mustNotBeLargerThanMessage.format( max );
        }

        return null;
    }
}

Sapphire 0.7 : List Index Facility

The scalability and performance of some features can benefit from constant time lookup of list entries based on the value of a member property.

A list can have one or more indexes that are created on first request. Once created, an index is shared by all consumers of the list and updates itself automatically. The index can also notify listeners when it changes.

Index<T extends Element>
{
    ElementList<T> list()
    ValueProperty property()
    T element( String key )
    Set<T> elements( String key )
    attach( Listener listener )
    detach( Listener listener )
}

ElementList<T extends Element>
{
    Index<T> index( ValueProperty property )
    Index<T> index( String property )
}

A quick lookup is easy to write.

Task task = repository.getTasks().index( "Id" ).element( "1234" );

Multiple elements that share the same key value can be retrieved as a group.

List<Task> tasks = repository.getTasks().index( "Component" ).elements( "SDK" );

Listening for changes to the index as opposed to the whole list can help reduce the number of times an expensive operation is performed.

Index<Task> index = repository.getTasks().index( "Component" );
List<Task> tasks = index.elements( "SDK" );

Listener listener = new Listener()
{
    @Override
    public void handle( Event event )
    {
        // Do something when the index has changed.
    }
}

index.attach( listener );

...

index.detach( listener );

Thursday, December 12, 2013

Sapphire 0.7 : New EL Functions and Properties

Many new functions have been added to the expression language.

Absolute

Returns the absolute path of a value for properties with a RelativePathService.

${ Path.Absolute }
Content

Returns the content of a value or a transient. For value properties, the default is taken into account, if applicable.

${ PurchaseOrder.FulfillmentDate.Content }
Enabled

Returns the enablement of a property.

${ PurchaseOrder.FulfillmentDate.Enabled }

In the context of a property editor, Enabled function can also be called with zero arguments. This accesses the enablement of the property editor part.

<property-editor>
    <property>FormLoginPage</property>
    <visible-when>${ Enabled() }</visible-when>
</property-editor>
EndsWith

Tests if a string ends with the specified suffix.

${ Path.EndsWith( ".png" ) }
Fragment

Returns a fragment of a string. The fragment starts at the index specified by the second argument and extends to the character before the index specified by the third argument. The length of the fragment is end index minus start index.

  • A negative start index is normalized to zero.
  • A start index exceeding the length of the input is normalized to the length of the input.
  • An end index exceeding the length of the input is normalized to the length of the input.
  • An end index that is smaller than the start index is normalized to the start index.
${ Value.Fragment( 3, 6 ) }
${ Fragment( "abcdef", 0, 3 ) }
Head

Returns a fragment of a string starting at the beginning and not exceeding the specified length.

  • A negative fragment length is normalized to zero.
  • A fragment length exceeding the length of the input is normalized to the length of the input.
${ Value.Head( 3 ) }
${ Head( "abcdef", 3 ) }
Index

Determines the index of a model element within its parent list.

${ This.Index }
Matches

Determines whether a string matches a regular expression. The full semantics are specified by Java's String.matches() function.

${ Entity.Name.Matches( "[a-z][a-z0-9]*" ) }
Message

Returns the message from a validation result.

${ PurchaseOrder.FulfillmentDate.Validation.Message }
Parent

Returns the parent of the given part. An implementation of this function for model elements was added in an earlier release.

${ Part.Parent.Validation.Severity }
${ Part.Parent.Parent.Validation.Severity }
Part

Returns the context part.

${ Part.Validation.Severity }
Severity

Returns the severity of a validation result.

${ PurchaseOrder.FulfillmentDate.Validation.Severity }
Size

Determines the size of a collection, a map, an array or a string.

${ PurchaseOrder.Entries.Size }
${ PurchaseOrder.BillingInformation.Name.Size }
${ Size( "abcdef" ) }
StartsWith

Tests if a string starts with the specified prefix.

${ Path.StartsWith( ".." ) }
State Function

Returns the root element of editor page's persistent state, allowing access to various state properties. This is particularly useful when the persistent state is extended with custom properties wired to custom actions, as it allows any EL-enabled facility to integrate with the custom state property.

In the following example, a custom state property is used to control whether content outline node label for an item in the catalog sample should include the manufacturer.

<node-factory>
    <property>Items</property>
    <case>
        <label>
        ${ 
             Name == null 
             ? "Item" 
             : (
                   State().ShowManufacturer && Manufacturer != null 
                   ? Concat( Manufacturer, " ", Name ) 
                   : Name
               )
         }
         </label>
    </case>
</node-factory>
Tail

Returns a fragment of a string starting at the end and not exceeding the specified length.

  • A negative fragment length is normalized to zero.
  • A fragment length exceeding the length of the input is normalized to the length of the input.
${ Value.Tail( 3 ) }
${ Tail( "abcdef", 3 ) }
Text

Returns the text of a value, taking into account the default, if applicable.

${ PurchaseOrder.FulfillmentDate.Text }
This

In situations where EL context is established by a model element, it can be useful to directly reference that element in order to pass it to functions. Mirroring Java, the context now exposes "This" property.

In this example, the expression computes the index of the context model element within its parent list.

${ This.Index }
Validation

Returns the validation result of a property or a part.

${ PurchaseOrder.FulfillmentDate.Validation }
${ Part.Validation }

Sapphire 0.7 : EL Functions as Properties

Any single argument EL function can now be accessed using property notation. Note that functions have a lower precedence than properties. If a conflict with a property is encountered, function notation must be used to disambiguate.

The following expressions are equivalent. The last variant is new for this release.

${ Size( PurchaseOrder.Entries ) }
${ PurchaseOrder.Entries.Size() }
${ PurchaseOrder.Entries.Size }

Sapphire 0.7 : Use EL for Validation

The new @Validation annotation allows an expression to be used to define a validation rule rather than implementing a custom ValidationService. This leads to a model that is easier to understand and maintain.

@Type( base = BigDecimal.class )
@DefaultValue( text = "0" )
@NumericRange( min = "0" )
    
@Validation
(
    rule = "${ Discount <= Subtotal + Delivery }",
    message = "Discount must not exceed subtotal plus delivery charge."
)

ValueProperty PROP_DISCOUNT = new ValueProperty( TYPE, "Discount" );

Value<BigDecimal> getDiscount();
void setDiscount( String value );
void setDiscount( BigDecimal value );

Multiple rules can be specified by using @Validations annotation, the message can be formulated using an expression, and the optional severity attribute allows the developer to make a rule failure either an error or a warning.

@Validations
(
    {
        @Validation
        (
            rule = "${ Path == null || Path.StartsWith( '/' ) }",
            message = "Path \"${ Path }\" must start with a slash."
        ),
        @Validation
        (
            rule = "${ Path == null || Path.StartsWith( HomePath ) }",
            message = "Path \"${ Path }\" is not within the home folder.",
            severity = Status.Severity.WARNING
        )
    }
)

ValueProperty PROP_PATH = new ValueProperty( TYPE, "Path" );

Value<String> getPath();
void setPath( String value );

Sapphire 0.7 : On-Demand Element Compilation

In past releases, Sapphire used a Java annotation processor linked to @GenerateImpl annotation to produce implementation classes for model element interfaces at build time. This system has been replaced by on-demand compilation straight to Java bytecode. When application code instantiates an element for the first time, Sapphire will automatically compile it and use the compiled result for the duration of the JVM instance.

This approach has several advantages.

  1. Easier getting started process since customizing the build to compile implementation classes is no longer necessary
  2. Smaller footprint for applications as implementation classes do not need to be distributed and stored
  3. Faster element instantiation as the bytecode generator is faster than most disk systems

Wednesday, December 11, 2013

Sapphire 0.7 : Conditional Wizard Pages

Define wizards with pages that appear based on a condition.

<wizard>
    <id>PurchaseComputerWizard</id>
    <element-type>org.eclipse.sapphire.samples.po.PurchaseComputerOp</element-type>
    <page>
        <id>PurchaseComputerWizard.Importance</id>
        <label>Expected Usage</label>
        <description>The expected usage of the computer determines the optimal components.</description>
        <content>
            <property-editor>PerformanceImportance</property-editor>
            <property-editor>StorageImportance</property-editor>
            <property-editor>GamingImportance</property-editor>
        </content>
    </page>
    <page>
        <id>PurchaseComputerWizard.Performance</id>
        <label>Performance</label>
        <description>The processor and memory selection affects the overall performance of the system.</description>
        <visible-when>${ PerformanceImportance == 3 }</visible-when>
        <content>
            ...
        </content>
    </page>
</wizard>

Sapphire 0.7 : Color Browsing

Define a color value property using the provided Color type and Sapphire will supply a browse dialog.

@Type( base = Color.class )

ValueProperty PROP_COLOR = new ValueProperty( TYPE, "Color" );
    
Value<Color> getColor();
void setColor( String value );
void setColor( Color value );

ColorBrowsing

Sapphire 0.7 : Radio Buttons with Images

The radio buttons property editor presentation now uses value images, when available.

enum FileType
{
    @Label( standard = "Java" )
    @Image( path = "JavaFile.png" )
    
    JAVA,
    
    @Label( standard = "XML" )
    @Image( path = "XmlFile.png" )
    
    XML,
    
    @Label( standard = "text" )
    @Image( path = "TextFile.png" )
    
    TEXT
}

@Type( base = FileType.class )

ValueProperty PROP_TYPE = new ValueProperty( TYPE, "Type" );

Value<FileType> getType();
void setType( String value );
void setType( FileType value );

RadioButtonImages

Sapphire 0.7 : Improved Date Support

As the Sapphire 0.7 release nears, I will be making a series of posts to highlight some of the exciting new features of this release. This is the first post in the series.

Date value properties are easier to define and the user experience is significantly improved. The formats specified by the developer using the new @Serialization annotation are visible as text overlay, in the property editor assistance popup and in the context help.

@Type( base = Date.class )
@Serialization( primary = "yyyy-MM-dd", alternative = "MM/dd/yyyy" )

ValueProperty PROP_ORDER_DATE = new ValueProperty( TYPE, "OrderDate" );

Value<Date> getOrderDate();
void setOrderDate( String value );
void setOrderDate( Date value );

Dates-1

Dates-2

The browse button opens a calendar, making it easy to quickly select the correct date.

Dates-3