Sapphire Developer Guide

Services

Many aspects of Sapphire can be extended by third parties.

ContentProposalService

The ContentProposalService provides a conduit for content assist in property editors. If a property has this service, content assist will be automatically enabled in the property editor. The manner in which content assist is presented is specific to the presentation, but usually involves a popup window with proposals, activated by some combination of key strokes (such as CTRL+SPACE).

The framework provides an implementation of ContentProposalService for properties with @PossibleValues annotation or a custom PossibleValuesService, but this service can also be implemented directly by adopters.

DependenciesAggregationService

The DependenciesAggregationService combines the data from all applicable dependencies services in order to produce a single set of dependencies. A dependency is a model paths that points to parts of the model that the property depends on. A property listens on all of its dependencies and triggers refresh when any of the dependencies change.

An implementation of this service is provided with Sapphire. This service is not intended to be implemented by adopters. See DependenciesService instead.

DependenciesService

The DependenciesService produces the set of model paths that point to parts of the model that the property depends on. A property listens on all of its dependencies and triggers refresh when any of the dependencies change.

Although custom implementations are supported, in most cases the supplied implementation that is configured via @DependsOn annotation should be sufficient.

Example

@DependsOn( "Name" )

ValueProperty PROP_ID = new ValueProperty( TYPE, "Id" );

Value<String> getId();
void setId( String value );

Other annotations, such as @NoDuplicates can also inject implied dependencies (via their own DependenciesService implementations). For instance, placing @NoDuplicates annotation on a Name property automatically adds "#/Name" dependency.

If declarative approach is not sufficient, a custom DependenciesService implementation can be supplied.

Example

public class CustomDependenciesService extends DependenciesService
{
    @Override
    protected DependenciesServiceData compute()
    {
        // Compute the list of dependencies.
        
        List<ModelPath> dependencies = new ArrayList<ModelPath>();
        
        ...
        
        return new DependenciesServiceData( dependencies );
    }
}
@Service( impl = CustomDependenciesService.class )

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

Value<String> getName();
void setName( String value );

FactsAggregationService

The FactsAggregationService combines the data from all applicable facts services in order to produce a single list of facts. A fact is a short statement describing property semantics.

An implementation of this service is provided with Sapphire. This service is not intended to be implemented by adopters. See FactsService to contribute facts.

FactsService

When a property is described to a user in documentation one does it with a series of short statements that define its semantics, such as "must be specified" or "maximum value is 100". When a property is described to Sapphire one does it with a series of annotations, such as @Required or @NumericRange. This duplicate specification is a maintenance problem.

A FactsService provides a means to dynamically derive statements about property's semantics based on property's metadata. The derived facts can then be presented to the user as part of documentation, property editor information popup and in other relevant places.

A single facts service can produce multiple facts and multiple facts services can be active concurrently for a given property. See FactsAggregationService for an easier way to consume all facts.

Sapphire includes a number of FactsService implementations.

ID Description
Sapphire.FactsService.JavaTypeConstraint Creates fact statements about Java type property's constraints by using semantical information specified by @JavaTypeConstraints annotation.
Sapphire.FactsService.Static Creates fact statements about property by using static content specified in @Fact and @Facts annotations.
Sapphire.FactsService.DefaultValue Creates fact statements about property's default value by using semantical information specified by DefaultValueService and @DefaultValue annotation.
Sapphire.FactsService.InitialValue Creates fact statements about property's initial value by using semantical information specified by InitialValueService and @InitialValue annotation.
Sapphire.FactsService.NumericRange Creates fact statements about numeric value property's range by using semantical information specified by @NumericRange annotation.
Sapphire.FactsService.Required Creates fact statements about property's optionality by using semantical information specified by @Required annotation.
Sapphire.FactsService.ReadOnly Creates fact statements about property's read-only state by using semantical information specified by @ReadOnly annotation.
Sapphire.FactsService.CountConstraint Creates fact statements about list property's count constraint by using semantical information specified by @CountConstraint annotation.
Sapphire.FactsService.AbsolutePath Creates fact statements about property's absolute path requirement by using semantical information specified by @AbsolutePath annotation.
Sapphire.FactsService.MustExist Creates fact statements about existence requirement on the entity referenced by property's value by using semantical information specified by @MustExist annotation.
Sapphire.FactsService.NoDuplicates Creates fact statements about value property's uniqueness constraint by using semantical information specified by @NoDuplicates annotation.
Sapphire.FactsService.FileExtensions Creates fact statements about valid file extensions for property's value by using semantical information specified by @FileExtensions annotation.
Sapphire.FactsService.ValidFileSystemResourceType Creates fact statements about valid file system resource type (file or folder) for property's value by using semantical information specified by @ValidFileSystemResourceType annotation.
Sapphire.FactsService.Deprecated Creates fact statements about property's deprecated state by using semantical information specified by @Deprecated annotation.
Sapphire.FactsService.ProjectRelativePath Creates fact statements about property's relative to the project path requirement by using semantical information specified by @ProjectRelativePath annotation.
Sapphire.FactsService.WorkspaceRelativePath Creates fact statements about property's relative to the workspace path requirement by using semantical information specified by @WorkspaceRelativePath annotation.

Example

This screen capture shows user experience with some of the provided FactsService implementation. See if you can match facts in the screen capture to service implementations above.

Adopters can provide custom FactService implementations either globally using Sapphire extension system or at the property level using @Service annotation.

Example

A simple global FactsService implementation that is triggered by a hypothetical @Since property annotation.

public class SinceVersionFactsService extends FactsService
{
    @Override
    protected void facts( List facts )
    {
        Since since = property().getAnnotation( Since.class );
        facts.add( "Since version " + since.version() + "." );
    }

    public static class Factory extends ServiceFactory
    {
        @Override
        public boolean applicable( ServiceContext context,
                                   Class<? extends Service> service )
        {
            return context.find( ModelProperty.class ).hasAnnotation( Since.class );
        }

        @Override
        public Service create( ServiceContext context,
                               Class<? extends Service> service )
        {
            return new SinceVersionFactsService();
        }
    }
}

The service implementation is registered in META-INF/sapphire-extension.xml file.

<extension xmlns="http://www.eclipse.org/sapphire/xmlns/extension">
    <service>
        <id>Example.SinceVersionFactsService</id>
        <type>org.eclipse.sapphire.services.FactsService</type>
        <context>Sapphire.Property.Instance</context>
        <factory>example.SinceVersionFactsService$Factory</factory>
    </service>
</extension>

Facts can also be statically specified for a given property by using @Fact annotation. Use @Facts annotation to specify multiple facts. The facts contained in these annotations are surfaced by an included FactsService implementation (id:Sapphire.FactsService.Static).

Example

// *** ExampleOne ***

@Fact( statement = "Important fact.")

ValueProperty PROP_EXAMPLE_ONE = new ValueProperty( TYPE, "ExampleOne" );

Value<String> getExampleOne();
void setExampleOne( String value );

// *** ExampleMultiple ***

@Facts( { @Fact( statement = "First important fact." ), @Fact( statement = "Second important fact." ) } )

ValueProperty PROP_EXAMPLE_MULTIPLE = new ValueProperty( TYPE, "ExampleMultiple" );

Value<String> getExampleMultiple();
void setExampleMultiple( String value );

FileExtensionsService

The FileExtensionsService produces the list of file extensions that are allowed for a path value property.

Although custom implementations are supported, in most cases the supplied implementation that is configured via @FileExtensions annotation should be sufficient. In many cases, specifying file extensions is as simple as listing them with a comma in between.

Example

@Type( base = Path.class )
@AbsolutePath
@MustExist
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@FileExtensions( expr = "jar,zip" )

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

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

File extensions can also be specified via an expression that takes into account values of other properties.

Examples

@FileExtensions( expr = "${ Extension }" )
@FileExtensions( expr = "${ LossyFormat ? "jpeg,jpg" : "png,gif" }" )

If declarative approach is not sufficient, a custom FileExtensionsService implementation can be supplied.

Example

public class CustomFileExtensionsService extends FileExtensionsService
{
    @Override
    protected void initFileExtensionsService()
    {
        // Optionally register listeners to invoke refresh method when the list of extensions
        // may need to be updated.
    }

    @Override
    protected FileExtensionsServiceData compute()
    {
        // Compute the list of extensions.
        
        List<String> extensions = new ArrayList<String>();
        
        ...
        
        return new FileExtensionsServiceData( extensions );
    }

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

        // Remove any listeners that were added during initialization.
    }
}
@Type( base = Path.class )
@AbsolutePath
@MustExist
@ValidFileSystemResourceType( FileSystemResourceType.FILE )
@Service( impl = CustomFileExtensionsService.class )

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

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

InitialValueService

The InitialValueService produces a value to assign to a property when the containing model element is created.

The concept of an initial value is different from a default value. The initial value is explicitly assigned to the property during containing model element's creation. This includes writing to the backing resource (such as an XML document). In comparison, the default value is used when null is read for a property from the backing resource. As such, the default value is only visible to model consumers (such as the user interface), while the initial value is persisted.

Whether you use an initial value or a default value is frequently dictated by the requirements of the backing resource. As an example, let's consider an XML document that stores phone numbers. In this XML document, the phone number element has a type child element which contains a value like home, mobile, work, etc. Let's further say that semantically, we wish to use mobile phone number type unless specified differently. Now, if the XML schema dictates that the phone number type element is required, we would need to specify "mobile" as the initial value. If the phone number type element is optional, it would be better to specify "mobile" as the default value.

In many situations, the initial value is static and should be configured using @InitialValue annotation.

Example

@Required
@PossibleValues( values = { "home", "mobile", "work", "other" }, invalidValueSeverity = Status.Severity.OK )
@InitialValue( text = "mobile" )
    
ValueProperty PROP_TYPE = new ValueProperty( TYPE, "Type" );
    
Value<String> getType();
void setType( String type );

When the initial value varies due to runtime conditions, a custom implementation of InitialValueService can be provided.

Example

public class PhoneTypeInitialValueService extends InitialValueService
{
    @Override
    protected void initInitialValueService()
    {
        // Register listeners to invoke refresh() method when the initial value
        // may have changed.
    }
    
    @Override
    protected InitialValueServiceData compute()
    {
        // Compute the initial value.
        
        String value;
        
        ...
        
        return new InitialValueServiceData( value );
    }

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

        // Remove any listeners that were added during initialization.
    }
}
@Required
@PossibleValues( values = { "home", "mobile", "work", "other" }, invalidValueSeverity = Status.Severity.OK )
@Service( impl = PhoneTypeInitialValueService.class )
    
ValueProperty PROP_TYPE = new ValueProperty( TYPE, "Type" );
    
Value<String> getType();
void setType( String type );

JavaTypeConstraintService

The JavaTypeConstraintService describes constraints on the Java type that a property can reference, such as the kind of type (class, interface, etc.) and the types that the type must extend or implement. The information provided by this service is used for validation, content assist and other needs.

In majority of situations, the Java type constraint is static and should be configured using @JavaTypeConstraint annotation. The framework provides an implementation of JavaTypeConstraintService that works with this annotation.

Example

@Type( base = JavaTypeName.class )
@Reference( target = JavaType.class )
@JavaTypeConstraint( kind = JavaTypeKind.CLASS, type = "javax.servlet.Filter" )

ValueProperty PROP_SERVLET_FILTER_IMPL = new ValueProperty( TYPE, "ServletFilterImpl" );

ReferenceValue<JavaTypeName,JavaType> getServletFilterImpl();
void setServletFilterImpl( JavaTypeName value );
void setServletFilterImpl( String value );

When the Java type constraint varies due to runtime conditions, a custom implementation of JavaTypeConstraintService can be provided.

Example

public class CustomJavaTypeConstraintService extends JavaTypeConstraintService
{
    @Override
    protected void initJavaTypeConstraintService()
    {
        // Register listeners to invoke refresh() method when Java type constraint
        // may have changed.
    }
    
    @Override
    protected JavaTypeConstraintServiceData compute()
    {
        // Compute Java type constraint.
        
        List<JavaTypeKind> kinds = new ArrayList<JavaTypeKind>();
        List<String> types = new ArrayList<String>();
        JavaTypeConstraintBehavior behavior;
        
        ...
        
        return new JavaTypeConstraintServiceData( kinds, types, behavior );
    }

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

        // Remove any listeners that were added during initialization.
    }
}
@Type( base = JavaTypeName.class )
@Reference( target = JavaType.class )
@Service( impl = CustomJavaTypeConstrainService.class )

ValueProperty PROP_SERVLET_FILTER_IMPL = new ValueProperty( TYPE, "ServletFilterImpl" );

ReferenceValue<JavaTypeName,JavaType> getServletFilterImpl();
void setServletFilterImpl( JavaTypeName value );
void setServletFilterImpl( String value );

PossibleTypesService

The PossibleTypesService enumerates the possible child element types for a list or an element property. Each returned type is required to derive from the property's base type.

In majority of situations, the set of possible types is static and should be configured using @Type annotation. The framework provides an implementation of PossibleTypesService that works with this annotation.

Example

@Type( base = Shape.class, possible = { Circle.class, Triangle.class, Rectangle.class } )
    
ListProperty PROP_SHAPES = new ListProperty( TYPE, "Shapes" );
    
ModelElementList<Shape> getShapes();

When the set of possible types varies due to model extensibility or runtime conditions, a custom implementation of PossibleTypesService can be provided.

Example

public class ShapesPossibleTypesService extends PossibleTypesService
{
    @Override
    protected void initPossibleTypesService()
    {
        // Register listeners to invoke refresh() method when the list of possible types
        // may have changed.
    }

    @Override
    protected PossibleTypesServiceData compute()
    {
        // Compute the list of possible types.
        
        List<ModelElementType> types = new ArrayList<ModelElementType>();
        
        ...
        
        return new PossibleTypesServiceData( types );
    }

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

        // Remove any listeners that were added during initialization.
    }
}
@Type( base = Shape.class )
@Service( impl = ShapesPossibleTypesService.class )
    
ListProperty PROP_SHAPES = new ListProperty( TYPE, "Shapes" );
    
ModelElementList<Shape> getShapes();

If the set of possible types is not specified via @Type annotation or via a custom PossibleTypesService implementation, the set of possible types is defined to be a singleton set composed of the property's base type.