Sapphire Developer Guide > Releases > 0.4

Migration Guide for 0.4

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

Table of Contents

  1. Documentation
  2. Hints
  3. ValidFileExtensions
  4. Services
  5. Possible Types
  6. Action Handler Dispose
  7. @XmlRootBinding
  8. PropertyEditorAssistContribution

Documentation

The "href" attribute of the @Documentation.Topic has been renamed to "url" for consistency with other API.

Before After
@Documentation
( 
    content = "Sample 32-bit integer property.",
    topics = 
    {
        @Topic( label = "wikipedia", href = "http://en.wikipedia.org/wiki/Integer" )
    }
)

ValueProperty PROP_INTEGER = new ValueProperty( TYPE, "Integer" );

Value getInteger();
void setInteger( String val );
void setInteger( Integer val );
@Documentation
( 
    content = "Sample 32-bit integer property.",
    topics = 
    {
        @Topic( label = "wikipedia", url = "http://en.wikipedia.org/wiki/Integer" )
    }
)

ValueProperty PROP_INTEGER = new ValueProperty( TYPE, "Integer" );

Value getInteger();
void setInteger( String val );
void setInteger( Integer val );

The method for attaching custom code to produce documentation content has changed to align with Sapphire services architecture and for general API consistency.

Before After
public class IntegerDocumentationProvider extends DocumentationProviderImpl 
{
    @Override
    public DocumentationData getDocumentationData() 
    {
        return new DocumentationData() 
        {
            @Override
            public String getContent() 
            {
                return "Sample 32-bit integer property.";
            }

            @Override
            public List<DocumentationResource> getTopics() 
            {
                final DocumentationResource resource 
                    = new DocumentationResource( "wikipedia", "http://en.wikipedia.org/wiki/Integer" );

                return Collections.singletonList( resource );
            }
        };
    }
}
public class IntegerDocumentationService extends DocumentationService 
{
    @Override
    public String content()
    {
        return "Sample 32-bit integer property.";
    }

    @Override
    public List<Topic> topics()
    {
        final Topic topic = new Topic( "wikipedia", "http://en.wikipedia.org/wiki/Integer" );
        return Collections.singletonList( topic );
    }
}
@DocumentationProvider( impl = IntegerDocumentationProvider.class )

ValueProperty PROP_INTEGER = new ValueProperty( TYPE, "Integer" );

Value getInteger();
void setInteger( String val );
void setInteger( Integer val );
@Service( impl = IntegerDocumentationService.class )

ValueProperty PROP_INTEGER = new ValueProperty( TYPE, "Integer" );

Value getInteger();
void setInteger( String val );
void setInteger( Integer val );

Hints

Some of the hints used to customize behavior in the UI definitions have been turned into explicit properties.

Before After
<property-editor>
    <hint>
        <name>show.label</name>
        <value>false</value>
    </hint>
</property-editor>
<property-editor>
    <show-label>false</show-label>
    <span>true</span>
</property-editor>

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*show.label\s*</name>\s*<value>\s*false\s*</value>\s*</hint>
Replace:  \1<show-label>false</show-label>\n\1<span>true</span>

Before After
<property-editor>
    <hint>
        <name>show.label.above</name>
        <value>true</value>
    </hint>
</property-editor>
<property-editor>
    <span>true</span>
</property-editor>

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*show.label.above\s*</name>\s*<value>\s*true\s*</value>\s*</hint>
Replace:  \1<span>true</span>

Before After
<property-editor>
    <hint>
        <name>width</name>
        <value>100</value>
    </hint>
</property-editor>
<property-editor>
    <width>100</width>
</property-editor>
<composite>
    <hint>
        <name>width</name>
        <value>100</value>
    </hint>
</composite>
<composite>
    <width>100</width>
</composite>

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*width\s*</name>\s*<value>\s*([0-9]*)\s*</value>\s*</hint>
Replace:  \1<width>\2</width>

Before After
<property-editor>
    <hint>
        <name>height</name>
        <value>5</value>
    </hint>
</property-editor>
<property-editor>
    <height>75</height>
</property-editor>

Multiply the old height hint value by 15 to get the same effect. Only do this conversion for the height hint and only for property editors.

<composite>
    <hint>
        <name>height</name>
        <value>100</value>
    </hint>
</composite>
<composite>
    <height>100</height>
</composite>

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*height\s*</name>\s*<value>\s*([0-9]*)\s*</value>\s*</hint>
Replace:  \1<height>\2</height>

Before After
<property-editor>
    <hint>
        <name>margin.left</name>
        <value>1u</value>
    </hint>
</property-editor>
<property-editor>
    <margin-left>20</margin-left>
</property-editor>

The margin.left hint supported two suffixes to designate units. The "px" suffix designated pixels, while "u" suffix designated units of 20 pixels. The new property takes a number of pixels with no units suffix.

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*margin.left\s*</name>\s*<value>\s*([0-9a-z]*)\s*</value>\s*</hint>
Replace:  \1<margin-left>\2</margin-left>

Before After
<property-editor>
    <hint>
        <name>expand.vertically</name>
        <value>true</value>
    </hint>
</property-editor>
<property-editor>
    <scale-vertically>true</scale-vertically>
</property-editor>

Scope:    *.sdef
Search:   ([ \t]*)<hint>\s*<name>\s*expand.vertically\s*</name>\s*<value>\s*([a-z]*)\s*</value>\s*</hint>
Replace:  \1<scale-vertically>\2</scale-vertically>

Before After
<html>
    <expand-vertically/>
</html>
<html>
    <scale-vertically>true</scale-vertically>
</html>

Scope:    *.sdef
Search:   <expand-vertically */>
Replace:  <scale-vertically>true</scale-vertically>

ValidFileExtensions

The @ValidFileExtensions annotation has been replaced with @FileExtensions annotation that supports Sapphire expression language.

Before After
@Type( base = Path.class )
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@ValidFileExtensions( { "png", "gif", "jpeg" } )

ValueProperty PROP_FILE_PATH = new ValueProperty( TYPE, "FilePath" );

Value<Path> getFilePath();
void setFilePath( String value );
void setFilePath( Path value );
@Type( base = Path.class )
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@FileExtensions( expr = "png,gif,jpeg" )

ValueProperty PROP_FILE_PATH = new ValueProperty( TYPE, "FilePath" );

Value<Path> getFilePath();
void setFilePath( String value );
void setFilePath( Path value );

See Documentation

Services

The services API has undergone changes to make it more general and to cleanup various inconsistencies.

Before After
public class CustomService extends ModelElementService
{
    @Override
    public void init( IModelElement element, String[] params )
    {
        super.init( element, params );

        String p1 = params[ 0 ];
        String p2 = params[ 1 ];

        ...
    }

    public Object perform()
    {
        IModelElement element = element();

        ...
    }
}
public class CustomService extends Service
{
    @Override
    protected void init()
    {
        super.init();

        IModelElement element = context( IModelElement.class );

        String p1 = params( "p1" );
        String p2 = params( "p2" );

        ...
    }

    public Object perform()
    {
        IModelElement element = context( IModelElement.class );

        ...
    }
}
public class CustomServiceFactory extends ModelElementServiceFactory
{
    @Override
    public boolean applicable( IModelElement element,
                               Class<? extends ModelElementService> service )
    {
        ...
    }

    @Override
    public ModelElementService create( IModelElement element,
                                       Class<? extends ModelElementService> service )
    {
        ...
    }
}
public class CustomServiceFactory extends ServiceFactory
{
    @Override
    public boolean applicable( ServiceContext context,
                               Class<? extends Service> service )
    {
        IModelElement element = context.find( IModelElement.class );

        ...
    }

    @Override
    public Service create( ServiceContext context,
                           Class<? extends Service> service )
    {
        IModelElement element = context.find( IModelElement.class );

        ...
    }
}
<extension>
    <model-element-service>
        <id>Sample.CustomService</id>
        <type>org.eclipse.sapphire.samples.CustomService</type>
        <factory>org.eclipse.sapphire.samples.internal.CustomServiceFactory</factory>
    </model-element-service>
</extension>
<extension>
    <service>
        <id>Sample.CustomService</id>
        <type>org.eclipse.sapphire.samples.CustomService</type>
        <factory>org.eclipse.sapphire.samples.internal.CustomServiceFactory</factory>
        <context>Sapphire.Element.Instance</context>
    </service>
</extension>
public class CustomService extends ModelPropertyService
{
    @Override
    public void init( IModelElement element, ModelProperty property, String[] params )
    {
        super.init( element, property, params );

        String p1 = params[ 0 ];
        String p2 = params[ 1 ];

        ...
    }

    public Object perform()
    {
        IModelElement element = element();
        ModelProperty property = property();

        ...
    }
}
public class CustomService extends Service
{
    @Override
    protected void init()
    {
        super.init();

        IModelElement element = context( IModelElement.class );
        ModelProperty property = context( ModelProperty.class );

        String p1 = params( "p1" );
        String p2 = params( "p2" );

        ...
    }

    public Object perform()
    {
        IModelElement element = context( IModelElement.class );
        ModelProperty property = context( ModelProperty.class );

        ...
    }
}
public class CustomServiceFactory extends ModelPropertyServiceFactory
{
    @Override
    public boolean applicable( IModelElement element,
                               ModelProperty property,
                               Class<? extends ModelPropertyService> service )
    {
        ...
    }

    @Override
    public ModelPropertyService create( IModelElement element,
                                        ModelProperty property,
                                        Class<? extends ModelPropertyService> service )
    {
        ...
    }
}
public class CustomServiceFactory extends ServiceFactory
{
    @Override
    public boolean applicable( ServiceContext context,
                               Class<? extends Service> service )
    {
        IModelElement element = context.find( IModelElement.class );
        ModelProperty property = context.find( ModelProperty.class );

        ...
    }

    @Override
    public Service create( ServiceContext context,
                           Class<? extends Service> service )
    {
        IModelElement element = context.find( IModelElement.class );
        ModelProperty property = context.find( ModelProperty.class );

        ...
    }
}
<extension>
    <model-property-service>
        <id>Sample.CustomService</id>
        <type>org.eclipse.sapphire.samples.CustomService</type>
        <factory>org.eclipse.sapphire.samples.internal.CustomServiceFactory</factory>
    </model-property-service>
</extension>
<extension>
    <service>
        <id>Sample.CustomService</id>
        <type>org.eclipse.sapphire.samples.CustomService</type>
        <factory>org.eclipse.sapphire.samples.internal.CustomServiceFactory</factory>
        <context>Sapphire.Property.Instance</context>
    </service>
</extension>
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@Service( impl = CustomService.class, params = { "a", "b" } )

Value<String> getSample();
void setSample( String value );
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@Service
(
    impl = CustomService.class, 
    params = 
    { 
        @Service.Param( name = "p1", value = "a" ), 
        @Service.Param( name = "p2", value = "b" ) 
    }
)

Value<String> getSample();
void setSample( String value );
<extension>
    <value-serialization-service>
        <type>org.eclipse.sapphire.samples.Circle</type>
        <impl>org.eclipse.sapphire.samples.internal.CircleSerializationService</impl>
    </value-serialization-service>
</extension>
<extension>
    <service>
        <id>Sample.ValueSerializationService.Circle</id>
        <type>org.eclipse.sapphire.services.ValueSerializationService</type>
        <context>Sapphire.Property.Instance</context>
        <factory>org.eclipse.sapphire.samples.internal.CircleSerializationServiceFactory</factory>
    </service>
</extension>

public class CircleSerializationServiceFactory extends ServiceFactory
{
    @Override
    public boolean applicable( ServiceContext context,
                               Class<? extends Service> service )
    {
        ValueProperty property = context.find( ValueProperty.class ); 
        return ( property != null && property.isOfType( Circle.class ) );
    }

    @Override
    public Service create( ServiceContext context,
                           Class<? extends Service> service )
    {
        return new CircleSerializationService();
    }
}
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@Type( base = Circle.class )
@ValueSerialization( service = CircleSerializationService.class )

Value<Circle> getSample();
void setSample( String value );
void setSample( Circle value );
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@Type( base = Circle.class )
@Service( impl = CircleSerializationService.class )

Value<Circle> getSample();
void setSample( String value );
void setSample( Circle value );
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@DerivedValue( service = SampleDerivedValueService.class )

Value<String> getSample();
ValueProperty PROP_SAMPLE = new ValueProperty( TYPE, "Sample" );

@Derived
@Service( impl = SampleDerivedValueService.class )

Value<String> getSample();
public class CustomValidationService extends ModelPropertyValidationService<Value<String>>
{
    @Override
    public Status validate()
    {
        final Value<String> value = target();

        ...
    }
}
public class CustomValidationService extends ValidationService
{
    @Override
    public Status validate()
    {
        final Value<String> value = context( IModelElement.class ).read( context( ValueProperty.class ) );

        ...
    }
}
public class CustomDefaultValueService extends DefaultValueService
{
    @Override
    public String getDefaultValue()
    {
        String value;

        ...

        return value;
    }

    ...
}
public class CustomDefaultValueService extends DefaultValueService
{
    @Override
    protected DefaultValueServiceData data()
    {
        refresh();
        return super.data();
    }

    @Override
    protected DefaultValueServiceData compute()
    {
        String value;

        ...

        return new DefaultValueServiceData( value );
    }

    ...
}

The above represents the quickest way to migrate an existing default value service, but is far from ideal. A better solution would be to not override the data() 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.

public class CustomDependenciesService extends DependenciesService
{
    @Override
    protected void compute( Set<ModelPath> dependencies )
    {
        dependencies.add( ... );
        dependencies.add( ... );

        ...
    }

    ...
}
public class CustomDependenciesService extends DependenciesService
{
    @Override
    protected DependenciesServiceData compute()
    {
        List<ModelPath> dependencies = new ArrayList<ModelPath>();

        dependencies.add( ... );
        dependencies.add( ... );

        ...

        return new DependenciesServiceData( dependencies );
    }

    ...
}
public class CustomDerivedValueService extends DerivedValueService
{
    @Override
    public String getDerivedValue()
    {
        String value;

        ...

        return value;
    }

    ...
}
public class CustomDerivedValueService extends DerivedValueService
{
    @Override
    protected DerivedValueServiceData compute()
    {
        String value;

        ...

        return new DerivedValueServiceData( value );
    }

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

    @Override
    protected boolean compute()
    {
        boolean enablement;

        ...

        return enablement;
    }

    ...
}
public class CustomEnablementService extends EnablementService
{
    @Override
    protected void initEnablementService()
    {
        final IModelElement element = context( IModelElement.class );
        final ModelProperty property = context( ModelProperty.class );
        final String p1 = param( "p1" );
        final String p2 = param( "p2" );

        ...
    }

    @Override
    protected EnablementServiceData compute()
    {
        boolean enablement;

        ...

        return new EnablementServiceData( enablement );
    }

    ...
}
public class CustomFileExtensionsService extends FileExtensionsService
{
    @Override
    protected void initFileExtensionsService( IModelElement element,
                                              ModelProperty property,
                                              String[] params )
    {
        ...
    }

    @Override
    protected void compute( List<String> extensions )
    {
        extensions.add( ... );
        extensions.add( ... );

        ...
    }

    ...
}
public class CustomFileExtensionsService extends FileExtensionsService
{
    @Override
    protected void initFileExtensionsService()
    {
        final IModelElement element = context( IModelElement.class );
        final ModelProperty property = context( ModelProperty.class );
        final String p1 = param( "p1" );
        final String p2 = param( "p2" );

        ...
    }

    @Override
    protected FileExtensionsServiceData compute()
    {
        List<String> extensions = new ArrayList<String>();

        extensions.add( ... );
        extensions.add( ... );

        ...

        return new FileExtensionsServiceData( extensions );
    }

    ...
}
public class CustomImageService extends ImageService
{
    @Override
    protected void init()
    {
        super.init();

        // Listen on entities that influence image choice.
        // Call broadcast() when something has changed.

        ...
    }

    @Override
    public ImageData provide()
    {
        ImageData image;

        ...

        return image;
    }

    ...
}
public class CustomImageService extends ImageService
{
    @Override
    protected void initImageService()
    {
        // Listen on entities that influence image choice.
        // Call refresh() when something has changed.

        ...
    }

    @Override
    protected ImageServiceData compute()
    {
        ImageData image;

        ...

        return new ImageServiceData( image );
    }

    ...
}
ModelService.Listener listener = new ModelService.Listener()
{
    public void handleEvent( ModelService.Event event )
    {
        ...
    }
}

service.addListener( listener );

...

service.removeListener( listener );

...

while inside service
{
    notifyListeners( new ModelService.Event( this ) );
}
Listener listener = new Listener()
{
    public void handle( Event event )
    {
        ...
    }
}

service.attach( listener );

...

service.detach( listener );

...

while inside service
{
    broadcast();
}
@Type( base = Date.class )

@ValueSerialization
(
    service = DateSerializationService.class, 
    params = { "yyyy.MM.dd", "MM/dd/yyyy" }
)

ValueProperty PROP_DATE = new ValueProperty( TYPE, "Date" );

Value getDate();
void setDate( String value );
void setDate( Date value );
@Type( base = Date.class )

@Service
(
    impl = DateSerializationService.class, 
    params =
    { 
        @Service.Param( name = "format-1", value = "yyyy.MM.dd" ), 
        @Service.Param( name = "format-2", value = "MM/dd/yyyy" )
    }
)

ValueProperty PROP_DATE = new ValueProperty( TYPE, "Date" );

Value getDate();
void setDate( String value );
void setDate( Date value );
public class CustomValidationService extends UniqueValueValidationService<String>
{
    @Override
    protected boolean isUniqueValue( Value<String> value )
    {
        ...
    }
}
public class CustomValidationService extends UniqueValueValidationService
{
    @Override
    protected boolean isUniqueValue( Value<?> value )
    {
        ...
    }
}

All core services have been consolidated in a single package. Some of them were renamed in the process.

Before After
org.eclipse.sapphire.modeling.DefaultValueService org.eclipse.sapphire.services.DefaultValueService
org.eclipse.sapphire.modeling.DerivedValueService org.eclipse.sapphire.services.DerivedValueService
org.eclipse.sapphire.modeling.EnablementService org.eclipse.sapphire.services.EnablementService
org.eclipse.sapphire.modeling.ImageService org.eclipse.sapphire.services.ImageService
org.eclipse.sapphire.modeling.PossibleValuesService org.eclipse.sapphire.services.PossibleValuesService
org.eclipse.sapphire.modeling.ReferenceService org.eclipse.sapphire.services.ReferenceService
org.eclipse.sapphire.modeling.RelativePathService org.eclipse.sapphire.services.RelativePathService
org.eclipse.sapphire.modeling.StandardValueNormalizationService org.eclipse.sapphire.services.StandardValueNormalizationService
org.eclipse.sapphire.modeling.ModelPropertyValidationService org.eclipse.sapphire.services.ValidationService
org.eclipse.sapphire.modeling.ValueImageService org.eclipse.sapphire.services.ValueImageService
org.eclipse.sapphire.modeling.ValueLabelService org.eclipse.sapphire.services.ValueLabelService
org.eclipse.sapphire.modeling.ValueNormalizationService org.eclipse.sapphire.services.ValueNormalizationService
org.eclipse.sapphire.modeling.serialization.DateSerializationService org.eclipse.sapphire.services.DateSerializationService
org.eclipse.sapphire.modeling.serialization.ValueSerializationService org.eclipse.sapphire.services.ValueSerializationService
org.eclipse.sapphire.modeling.validation.PathValidationService org.eclipse.sapphire.services.PathValidationService
org.eclipse.sapphire.modeling.validation.UniqueValueValidationService org.eclipse.sapphire.services.UniqueValueValidationService

Possible Types

The set of possible types for a list or an element property is no longer restricted to be static. As such, previous API for retrieving possible types for a property has been replaced with a more flexible service API.

Before After
List<ModelElementType> types = property.getAllPossibleTypes();
SortedSet<ModelElementType> types = element.service( property, PossibleTypesService.class ).types();
List<Class<?>> types = property.getAllPossibleTypeClasses();
List<Class<?>> types = new ArrayList<Class<?>>();

for( ModelElementType type : element.service( property, PossibleTypesService.class ).types() )
{
    types.add( type.getModelElementClass() );
}

Action Handler Dispose

The pattern of overriding the dispose method on SapphireActionHandler has been replaced with a dispose event. This architecture eliminates resource leaks caused by failing to call super.dispose() when overriding the dispose method.

Before After
public class ExampleActionHandler extends SapphireActionHandler
{
    ...

    @Override
    public void dispose()
    {
        super.dispose();

        // Dispose logic goes here.
    }
}
public class ExampleActionHandler extends SapphireActionHandler
{
    ...

    @Override
    public void init( SapphireAction action, ISapphireActionHandlerDef def )
    {
        super.init( action, def );

        attach
        (
            new Listener()
            {
                @Override
                public void handle( Event event )
                {
                    if( event instanceof DisposeEvent )
                    {
                        // Dispose logic goes here.
                    }
                }
            }
        );
    }
}

@XmlRootBinding

The @XmlRootBinding annotation has been re-worked to reduce functional overlap with other annotations and to allow the XML binding API to more easily adapt to different kinds of XML document type specification technologies. Scenarios previously handled by @XmlRootBinding annotation are now handled by different combinations of @XmlBinding, @XmlNamespace, @XmlSchema and @XmlDocumentType annotations.

Before After
@XmlRootBinding( elementName = "root" )
@XmlBinding( path = "root" )
@XmlRootBinding
(
    elementName = "root",
    namespace = "http://www.eclipse.org/sapphire/example"
)
@XmlNamespace( uri = "http://www.eclipse.org/sapphire/example" )
@XmlBinding( path = "root" )
@XmlRootBinding
(
    elementName = "root",
    namespace = "http://www.eclipse.org/sapphire/example",
    schemaLocation = "http://www.eclipse.org/sapphire/example/1.0",
    defaultPrefix = "e"
)
@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" )

PropertyEditorAssistContribution

The PropertyEditorAssistContribution API has changed to remove dependencies on SWT/JFace and to make contribution immutable once created.

Before After
public class CustomContributor extends PropertyEditorAssistContributor
{
    @Override
    public void contribute( PropertyEditorAssistContext context )
    {
        PropertyEditorAssistContribution contribution = new PropertyEditorAssistContribution();

        contribution.setText( ... );
        contribution.setImage( "CustomImageId", (Image) ... );

        contribution.setHyperlinkListener
        (
            new HyperlinkAdapter()
            {
                @Override
                public void linkActivated( HyperlinkEvent event )
                {
                    ...
                }
            }
        );

        PropertyEditorAssistSection section = context.getSection( ... );
        section.addContribution( contribution );
    }
}
public class CustomContributor extends PropertyEditorAssistContributor
{
    @Override
    public void contribute( PropertyEditorAssistContext context )
    {
        PropertyEditorAssistContribution.Factory contribution = PropertyEditorAssistContribution.factory();

        contribution.text( ... );
        contribution.image( "CustomImageId", (ImageData) ... );

        contribution.link
        (
            "CustomActionId",
            new Runnable()
            {
                public void run()
                {
                    ...
                }
            }
        );

        PropertyEditorAssistSection section = context.getSection( ... );
        section.addContribution( contribution.create() );
    }
}