Sapphire Developer Guide > Releases > 0.3

Migration Guide for 0.3

This documents covers code changes that need to be made by Sapphire adopters as part of migrating to 0.3 release. Only changes from the previous milestone are covered.

Table of Contents

  1. Modularity Changes
  2. ReferenceValue Changes
  3. Services
  4. Validation
  5. Enablement
  6. Whitespace Handling
  7. Java Support
  8. HTML Content Presentation
  9. Related Content
  10. Composite Reference
  11. Section Description
  12. Function Name Method
  13. With Directive
  14. Model Element Image
  15. Required Annotation
  16. Master Details Content Node Factory
  17. Jump Action Handler
  18. Show In Source Action

Modularity Changes

The contents of some bundles have changes and several new bundles have been created. These changes may require adopters to modify Requite-Bundle declarations and import statements.

Old Bundle Old Classes New Bundle New Classes
org.eclipse.sapphire.modeling
org.eclipse.sapphire.modeling.xml.*
org.eclipse.sapphire.modeling.xml.annotations.*
org.eclipse.sapphire.modeling.xml.schema.*
org.eclipse.sapphire.modeling.xml
unchanged
org.eclipse.sapphire.modeling
org.eclipse.sapphire.modeling.java.*
org.eclipse.sapphire.java
org.eclipse.sapphire.java.*
org.eclipse.sapphire.modeling
org.eclipse.sapphire.modeling.WorkspaceFileResourceStore
org.eclipse.sapphire.workspace
org.eclipse.sapphire.workspace.WorkspaceFileResourceStore
org.eclipse.sapphire.modeling
org.eclipse.sapphire.modeling.annotations.EclipseWorkspacePath
org.eclipse.sapphire.workspace
org.eclipse.sapphire.workspace.WorkspaceRelativePath
org.eclipse.sapphire.ui
org.eclipse.sapphire.ui.xml.*
org.eclipse.sapphire.ui.swt.xml.editor
org.eclipse.sapphire.ui.swt.xml.editor.*

The core dependency on IStatus, IPath and IProgressMonitor types from org.eclipse.core.runtime bundle has been eliminated. These classes have been replaced with Status, Path and ProgressMonitor classes in org.eclipse.sapphire.modeling bundle. StatusBridge, PathBridge and ProgressMonitorBridge classes are available in org.eclipse.sapphire.platform bundle when back-n-forth conversion is necessary.

Before After
public class CustomValidationService extends ModelPropertyValidationService<Value<String>> 
{
    public IStatus validate() 
    {
        if( ... )
        {
            return createErrorStatus( Resources.errorMessage );
        }

        return Status.OK_STATUS;
    }
}
public class CustomValidationService extends ModelPropertyValidationService<Value<String>> 
{
    public Status validate() 
    {
        if( ... )
        {
            return Status.createErrorStatus( Resources.errorMessage );
        }

        return Status.createOkStatus();
    }
}

When necessary, use StatusBridge class from org.eclipse.sapphire.platform bundle to convert back-n-forth between Sapphire's Status object and Eclipse's IStatus object.

@PossibleValues( service = CustomPossibleValuesService.class, invalidValueSeverity = IStatus.OK )
@PossibleValues( service = CustomPossibleValuesService.class, invalidValueSeverity = Status.Severity.OK )
public class CustomPossibleValuesService extends PossibleValuesService
{
    ...

    public int getInvalidValueSeverity( String value ) 
    {
        if( ... )
        {
            return IStatus.ERROR;
        }
        else if( ... )
        {
            return IStatus.WARNING;
        }

        return IStatus.OK;
    }
}
public class CustomPossibleValuesService extends PossibleValuesService
{
    ...

    public Status.Severity getInvalidValueSeverity( String value ) 
    {
        if( ... )
        {
            return Status.Severity.ERROR;
        }
        else if( ... )
        {
            return Status.Severity.WARNING;
        }

        return Status.Severity.OK;
    }
}
@Type( base = IPath.class )
@Required
@MustExist
@WorkspaceRelativePath
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@ValidFileExtensions( "xml" )

ValueProperty PROP_LOCATION = new ValueProperty( TYPE, "Location" );

Value<IPath> getLocation();
void setLocation( String location );
void setLocation( IPath location );

These migration sites will not be obvious via problems at compile times. Search for "Value<IPath>".

@Type( base = Path.class )
@Required
@MustExist
@WorkspaceRelativePath
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@ValidFileExtensions( "xml" )

ValueProperty PROP_LOCATION = new ValueProperty( TYPE, "Location" );

Value<Path> getLocation();
void setLocation( String location );
void setLocation( Path location );
public class CustomBasePathsProvider extends BasePathsProviderImpl
{
    public List<IPath> getBasePaths( IModelElement element )
    {
        ...
    }
}
public class CustomRelativePathService extends RelativePathService
{
    public List<Path> roots()
    {
        ...
    }
}
public class ProjectRootsBasePathsProvider extends BasePathsProviderImpl
{
    public List<IPath> getBasePaths( IModelElement element )
    {
        final List<IPath> paths = new ArrayList<IPath>();

        for( IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects() )
        {
            paths.add( project.getLocation() );
        }
        
        return paths;
    }
}
public class ProjectRootsRelativePathService extends RelativePathService
{
    public List<Path> roots()
    {
        final List<Path> paths = new ArrayList<Path>();

        for( IProject project : ResourcesPlugin.getWorkspace().getRoot().getProjects() )
        {
            paths.add( PathBridge.create( project.getLocation() ) );
        }
        
        return paths;
    }
}

When necessary, use PathBridge class from org.eclipse.sapphire.platform bundle to convert back-n-forth between Sapphire's Path object and Eclipse's IPath object.

Sapphire extension system no longer requires a flag from Eclipse extension system in order to discover extensions.

Before After
<plugin>

    ...

    <extension point="org.eclipse.sapphire.modeling.extension"/>
  
</plugin>
remove

Sapphire now loads all resources (such as images and sdef files) from the class loader. To access resources from another bundle use standard OSGi facilities to require another bundle or to import packages. To make re-use from other bundles easier, place images and sdef files into exported packages along with Java source code.

Before After
<definition>

    <import>
      <bundle>org.sample.foo</bundle>
      <package>org.sample.foo</package>
      <package>org.sample.foo.xyz</package>
      <definition>sdef/Foo.sdef</definition>
    </import>

    <import>
      <bundle>org.sample.bar</bundle>
      <package>org.sample.bar</package>
    </import>

    ...

</definition>
<definition>

    <import>
      <package>org.sample.foo</package>
      <package>org.sample.foo.xyz</package>
      <definition>org/sample/foo/Foo.sdef</definition>
      <package>org.sample.bar</package>
    </import>

    ...

</definition>

Note that multiple import elements are no longer supported.

@Image( path = "org.sample.foo/images/something.png" )
@Image( path = "images/something.png" )

The above migration is sufficient if the annotated type is located in the same plugin as the image. If the image is located in a different plugin, then you need to arrange to place the image in an exported package and adjust the path in the annotation accordingly.

ReferenceValue Changes

The ReferenceValue class now takes two type parameters instead of one. The first is the type of the reference. This used to be assumed to always be a string. The second is the type of what the reference resolves to.

Before After
ReferenceValue<ImageDescriptor> getImage();
ReferenceValue<String,ImageDescriptor> getImage();

Services

A few changes have been made to the services API.

Before After
public class MyService extends ModelElementService
{
    public void init( final IModelElement element )
    {
        super.init( element );

        ...
    }
}
public class MyService extends ModelElementService
{
    public void init( final IModelElement element,
                      final String[] params )
    {
        super.init( element, params );

        ...
    }
}
@Reference( target = IComponent.class, service = ComponentReferenceService.class )
@Reference( target = IComponent.class )
@Service( impl = ComponentReferenceService.class )
@PossibleValues( service = CustomPossibleValuesService.class )
@Service( impl = CustomPossibleValuesService.class )
@DefaultValue( service = CustomDefaultValueService.class )
@Service( impl = CustomDefaultValueService.class )
@BasePathsProvider( CustomBasePathsProvider.class )
@Service( impl = CustomRelativePathService.class )
public class CustomBasePathsProvider extends BasePathsProviderImpl
{
    public List<Path> getBasePaths( IModelElement element )
    {
        ...
    }
}
public class CustomRelativePathService extends RelativePathService
{
    public List<Path> roots()
    {
        ...
    }
}

Validation

Validators are now model element services. They can be registered globaly via the extension system or at the level of an individual property using the @Service annotation.

Before After
@Validator( service = MyValidator.class )
@Service( impl = MyValidationService.class )
@Validators
(
    @Validator( impl = MyValidator1.class ),
    @Validator( impl = MyValidator2.class )
)
@Services
(
    @Service( impl = MyValidationService1.class ),
    @Service( impl = MyValidationService2.class )
)
public class CustomValidator extends ModelPropertyValidator<Value<?>>
{
    @Override
    public void init( String[] params )
    {
        ...
    }

    public IStatus validate( Value<?> value )
    {
        ...
    }
}
public class CustomValidationService extends ModelPropertyValidationService<Value<?>>
{
    @Override
    public void init( IModelElement element,
                      ModelProperty property,
                      String[] params )
    {
        ...
    }

    public Status validate()
    {
        final Value<?> value = target();

        ...
    }
}
@Validator( impl = UniqueValueValidator.class )
@NoDuplicates
public class CustomValidator extends UniqueValueValidator
{
    ...
}
public class CustomValidationService extends UniqueValueValidationService
{
    ...
}

Enablement

Enablement is now handled by true model element services. They can be registered globaly via the extension system or at the level of an individual property using the @Service annotation.

Before After
@Enablement( service = CustomEnablementService.class )
@Service( impl = CustomEnablementService.class )
public class CustomEnablementService extends EnablementService
{
    @Override
    public void init( IModelElement element,
                      ModelProperty property,
                      String[] params )
    {
        super.init( element, property, params );

        ...
    }

    @Override
    public boolean isEnabled() 
    {
        ...
    }
}
public class CustomEnablementService extends EnablementService
{
    @Override
    protected void initEnablementService( IModelElement element,
                                          ModelProperty property,
                                          String[] params )
    {
        super.initEnablementService( element, property, params );

        ...
    }

    @Override
    public boolean state()
    {
        refresh();
        return super.state();
    }

    @Override
    protected boolean compute() 
    {
        ...
    }
}

The above represents the quickest way to migrate an existing enablement service, but is far from ideal. A better solution would be to not override the state() method with a refresh on every invocation. To do that, the service would need to listen on changes to data used in the compute() method and call refresh() method when that data changes.

Whitespace Handling

Whitespace handling has been made more flexible via the new @Whitespace annotation and the ValueNormalizationService API. The collapseWhitespace attribute on @XmlValueBinding annotation is no longer supported.

Before After
@XmlValueBinding( path = "description", collapseWhitespace = true )
@XmlValueBinding( path = "description" )
@Whitespace( collapse = true )

Java Support

Support for writing models that reference Java types has been improved. All properties that that hold Java type names should now be modeled as a reference from JavaTypeName to JavaType in order to receive validation and content assist support.

Before After
@Type( base = JavaTypeName.class )
@JavaTypeConstraints( ... )
@MustExist

ValueProperty PROP_IMPL_CLASS = new ValueProperty( TYPE, "ImplClass" );

Value<JavaTypeName> getImplClass();
void setImplClass( String value );
void setImplClass( JavaTypeName value );
@Type( base = JavaTypeName.class )
@Reference( target = JavaType.class )
@JavaTypeConstraint( ... )
@MustExist

ValueProperty PROP_IMPL_CLASS = new ValueProperty( TYPE, "ImplClass" );

ReferenceValue<JavaTypeName,JavaType> getImplClass();
void setImplClass( String value );
void setImplClass( JavaTypeName value );

HTML Content Presentation

Support for presenting HTML content in Sapphire UI was introduced in the 0.2 release in the form of @HtmlContent annotation and a read-only property editor. This approach proved to be too inflexible and has been replaced with a dedicated HTML UI part that can display HTML content that's statically specified, drawn from the model or even fetched remotely.

Before After
@HtmlContent
remove
<property-editor>
  <property>MessageBody</property>
  <hint>
    <name>show.label</name>
    <value>false</value>
  </hint>
</property-editor>
<html>
  <content>${ MessageBody }</content>
  <show-border/>
</html>

See New Feature Documentation

Related Content

The ability to augment a property editor with an auxiliary property editor has been generalized into a more powerful related content feature.

Before After
<property-editor>
  <property>MainProperty</property>
  <aux-property-editor>RelatedProperty</aux-property-editor>
</property-editor>
<property-editor>
  <property>MainProperty</property>
  <related-content>
    <property-editor>RelatedProperty</property-editor>
  </related-content>
</property-editor>
<property-editor>
  <property>MainProperty</property>
  <aux-property-editor>RelatedProperty1</aux-property-editor>
  <aux-property-editor>RelatedProperty2</aux-property-editor>
</property-editor>
<property-editor>
  <property>MainProperty</property>
  <related-content>
    <property-editor>
      <property>RelatedProperty1</property>
      <related-content>
        <property>RelatedProperty2</property>
      </related-content>
    </property-editor>
  </related-content>
</property-editor>

See New Feature Documentation

Composite Reference

The composite-ref construct has been replaced with a more flexible include construct which allows any form part to be included.

Before After
<composite-ref>
  <id>bug.report</id>
</composite-ref>
<include>bug.report</include>
<composite-ref>
  <id>bug.report</id>
  <param>
    <name>SomeParam</name>
    <value>SomeValue</value>
  </param>
</composite-ref>
<include>
  <part>bug.report</part>
  <param>
    <name>SomeParam</name>
    <value>SomeValue</value>
  </param>
</include>

The following Eclipse search/replace regular expression can be used to perform this migration (only works when there are no params):

Scope:    *.sdef
Search:   (?s)<composite-ref>.*?<id>(.*?)</id>.*?</composite-ref>
Replace:  <include>\1</include>

Section Description

The description construct under section has been removed. Use label and spacer to achieve the same result.

Before After
<section>
  <description>description</description>
</section>
<section>
  <content>
    <label>description</label>
    <spacer/>
  </content>
</section>

Function Name Method

Implementations of custom expression language functions (classes that extend Function) need to be updated to implement the required new name method. The returned name should be consistent with the name used when registering the function in sapphire-extension.xml file. The new method is used during toString() execution on a parsed expression.

Before After
public class CustomFunction extends Function
{
    @Override
    public FunctionResult evaluate( FunctionContext context )
    {
        ...
    }
}
public class CustomFunction extends Function
{
    @Override
    public String name()
    {
        return "Custom";
    }

    @Override
    public FunctionResult evaluate( FunctionContext context )
    {
        ...
    }
}

With Directive

The with directive capabilities have been expanded to allow ancestor element access via a path. To accommodate this feature, the "property" XML element in the sdef syntax has been renamed to "path".

Before After
<with>
    <property>SomeProperty</property>
    <default-panel>
        <content>
            ...
        </content>
    </default-panel>
</with>
<with>
    <path>SomeProperty</path>
    <default-panel>
        <content>
            ...
        </content>
    </default-panel>
</with>

The following Eclipse search/replace regular expression can be used to perform this migration:

Scope:    *.sdef
Search:   (?s)<with>(.*?)<property>(.*?)</property>(.*?)</with>
Replace:  <with>\1<path>\2</path>\3</with>

Model Element Image

The API for model element image provider has been changed to unify it with the model element services API and to support notification of image changes.

Before After
@Image( small = "..." )
@Image( path = "..." )
@Image( provider = AttendeeImageProvider.class )

public interface IAttendee extends IModelElement
{
    ...
}

...

public class AttendeeImageProvider extends ImageProvider
{
    private static final String IMG_PERSON 
        = SapphireSamplesPlugin.PLUGIN_ID + "/org/eclipse/sapphire/samples/contacts/Contact.png";

    private static final String IMG_PERSON_FADED 
        = SapphireSamplesPlugin.PLUGIN_ID + "/org/eclipse/sapphire/samples/contacts/ContactFaded.png";

    @Override
    public String getSmallImagePath( IModelElement element )
    {
        if( ( (IAttendee) element ).isInContactsDatabase().getContent() )
        {
            return IMG_PERSON;
        }
        else
        {
            return IMG_PERSON_FADED;
        }
    }

    @Override
    public String getSmallImagePath( ModelElementType type )
    {
        return IMG_PERSON;
    }
}
@Image( path = "Contact.png" )
@Service( impl = AttendeeImageService.class )

public interface IAttendee extends IModelElement
{
    ...
}

...

public class AttendeeImageService extends ImageService
{
    private static final ImageData IMG_PERSON 
        = ImageData.readFromClassLoader( IContact.class, "Contact.png" );

    private static final ImageData IMG_PERSON_FADED 
        = ImageData.readFromClassLoader( IContact.class, "ContactFaded.png" );

    private ModelPropertyListener listener;

    @Override
    public void init( IModelElement element, String[] params )
    {
        super.init( element, params );
        
        this.listener = new ModelPropertyListener()
        {
            @Override
            public void handlePropertyChangedEvent( final ModelPropertyChangeEvent event )
            {
                notifyListeners( new ImageChangedEvent( AttendeeImageProviderService.this ) );
            }
        };
        
        element.addListener( this.listener, IAttendee.PROP_IN_CONTACTS_DATABASE.getName() );
    }

    @Override
    public ImageData provide()
    {
        if( ( (IAttendee) element() ).isInContactsDatabase().getContent() )
        {
            return IMG_PERSON;
        }
        else
        {
            return IMG_PERSON_FADED;
        }
    }

    @Override
    public void dispose()
    {
        super.dispose();
        element().removeListener( this.listener, IAttendee.PROP_IN_CONTACTS_DATABASE.getName() );
    }
}

The following Eclipse search/replace regular expression can be used to perform some of this migration:

Scope:    *.java
Search:   (?s)@Image\((.*?)small(.*?)=(.*?)"(.*?)"(.*?)\)
Replace:  @Image\(\1path\2=\3"\4"\5\)

Required Annotation

The NonNullValue annotation has been renamed to Required to better reflect its broader usage with both value and element properties.

Before After
@NonNullValue

ValueProperty PROP_NAME = new ValueProperty( TYPE, "Name" );

Value getName();
void setName( String name );
@Required

ValueProperty PROP_NAME = new ValueProperty( TYPE, "Name" );

Value getName();
void setName( String name );

The following Eclipse search/replace regular expressions can be used to perform this migration:

Scope:    *.java
Search:   (?s)@NonNullValue
Replace:  @Required

Scope:    *.java
Search:   (?s)import org\.eclipse\.sapphire\.modeling\.annotations\.NonNullValue;
Replace:  import org\.eclipse\.sapphire\.modeling\.annotations\.Required;

Master Details Content Node Factory

The syntax of master details content node factory has been modified to better reflect its broader usage with both list and element properties.

Before After
<node-list>
    <property>HeterogeneousList</property>
    <node-template>
        <model-element-type>IChildElementWithInteger</model-element-type>
        <label>${ StringValue == null ? "&lt;child-with-integer&gt;" : StringValue }</label>
        <section>
            <label>child element with integer</label>
            <content>
                <property-editor>StringValue</property-editor>
                <property-editor>IntegerValue</property-editor>
            </content>
        </section>
    </node-template>
    <node-template>
        <model-element-type>IChildElementWithEnum</model-element-type>
        <label>${ StringValue == null ? "&lt;child-with-enum&gt;" : StringValue }</label>
        <section>
            <label>child element with enum</label>
            <content>
                <property-editor>StringValue</property-editor>
                <property-editor>EnumValue</property-editor>
            </content>
        </section>
    </node-template>
    <node-template>
        <model-element-type>IChildElement</model-element-type>
        <label>${ StringValue == null ? "&lt;child&gt;" : StringValue }</label>
        <section>
            <label>child element</label>
            <content>
                <property-editor>StringValue</property-editor>
            </content>
        </section>
    </node-template>
</node-list>
<node-factory>
    <property>HeterogeneousList</property>
    <case>
        <model-element-type>IChildElementWithInteger</model-element-type>
        <label>${ StringValue == null ? "&lt;child-with-integer&gt;" : StringValue }</label>
        <section>
            <label>child element with integer</label>
            <content>
                <property-editor>StringValue</property-editor>
                <property-editor>IntegerValue</property-editor>
            </content>
        </section>
    </case>
    <case>
        <model-element-type>IChildElementWithEnum</model-element-type>
        <label>${ StringValue == null ? "&lt;child-with-enum&gt;" : StringValue }</label>
        <section>
            <label>child element with enum</label>
            <content>
                <property-editor>StringValue</property-editor>
                <property-editor>EnumValue</property-editor>
            </content>
        </section>
    </case>
    <case>
        <model-element-type>IChildElement</model-element-type>
        <label>${ StringValue == null ? "&lt;child&gt;" : StringValue }</label>
        <section>
            <label>child element</label>
            <content>
                <property-editor>StringValue</property-editor>
            </content>
        </section>
    </case>
</node-factory>
<node-ref>CommonNode</node-ref>
<node-include>CommonNode</node-include>
<node-list-ref>CommonNodeFactory</node-list-ref>
<node-include>CommonNodeFactory</node-include>

The following Eclipse search/replace regular expressions can be used to perform this migration:

Scope:    *.sdef
Search:   (?s)<node-list>
Replace:  <node-factory>

Scope:    *.sdef
Search:   (?s)</node-list>
Replace:  </node-factory>

Scope:    *.sdef
Search:   (?s)<node-template>
Replace:  <case>

Scope:    *.sdef
Search:   (?s)</node-template>
Replace:  </case>

Scope:    *.sdef
Search:   (?s)<node-ref>
Replace:  <node-include>

Scope:    *.sdef
Search:   (?s)</node-ref>
Replace:  </node-include>

Scope:    *.sdef
Search:   (?s)<node-list-ref>
Replace:  <node-include>

Scope:    *.sdef
Search:   (?s)</node-list-ref>
Replace:  </node-include>

Jump Action Handler

Jump action handler API has been improved slightly to make it less likely that a subclass will override enablement logic from the parent class rather than augmenting it.

Before After
public class CustomJumpActionHandler extends SapphireJumpActionHandler
{
    @Override
    public void refreshEnablementState()
    {
        setEnabled( ... );
    }
}
public class CustomJumpActionHandler extends SapphireJumpActionHandler
{
    @Override
    protected boolean computeEnablementState()
    {
        if( super.computeEnablementState() == true )
        {
            return ...
        }
        
        return false;
    }
}

Show In Source Action

The "show in source" action has been generalized to apply to other contexts besides the content outline. This necessitated change of the id.

Before After
Sapphire.Outline.ShowInSource
Sapphire.ShowInSource