# Migration Guide for Sapphire 0.4

This documents covers changes that need to be made by Sapphire adopters as part of migrating to Sapphire 0.4 from a prior release. Only changes from the previous major 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() );
        }
    }

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