# Migration Guide for Sapphire 0.3

This documents covers changes that need to be made by Sapphire adopters as part of migrating to Sapphire 0.3 from a prior release. Only changes from the previous major release 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></id>
    </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<String> getName();
    void setName( String name );

    @Required

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

    Value<String> 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

Copyright (c) 2016 Oracle
Content made available under the terms of Eclipse Public License.