Many aspects of Sapphire can be extended by third parties.
The AdapterService provides means to extend the behavior of the adapt method in a given context.
For example, out of the box, Sapphire can adapt IModelElement to IProject assuming that the model is using an IFile for the underlying resource store. However, in some cases there may be no underlying IFile, so the default logic will no be able to find IProject. To solve this problem, we can introduce an adapter service to provide a custom method for adopting IModelElement to IProject.
Example
@GenerateImpl
@Service( impl = NewProjectFileOpAdapterService.class )
public interface INewProjectFileOp extends IModelElement
{
...
}
public class NewProjectFileOpAdapterService extends AdapterService
{
@Override
public <A> A adapt( Class<A> adapterType )
{
if( IProject.class == adapterType )
{
INewProjectFileOp op = context( INewProjectFileOp.class );
IProject project = op.getProject().resolve();
if( project != null )
{
return adapterType.cast( project );
}
}
return null;
}
}
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.
The ConversionService converts an object to the specified type. One common application is to convert an input (such as a file) to a resource when instantiating the model.
Sapphire.ConversionService.IFileToWorkspaceFileResourceStore |
---|
Capable of converting an IFile to a WorkspaceFileResourceStore or a ByteArrayResourceStore. |
Sapphire.ConversionService.ByteArrayResourceStoreToXmlResource |
Capable of converting a ByteArrayResourceStore to an XmlResource or a Resource. Conversion is only performed if resource store corresponds to a file with "xml" extension or if the context element type has XML binding annotations. |
A ConversionService can delegate to other conversion services to create a conversion chain. In fact, a common conversion of IFile to XmlResource is a chain of two ConversionService implementations.
Example
In the purchase order sample, a custom ConversionService is used because the default file extension for purchase order files is "po" rather than "xml" and PurchaseOrder element does not have XML binding annotations. The combination of these two factors prevent the framework-provided ConversionService implementations from engaging.
@Service( impl = PurchaseOrderResourceConversionService.class )
public interface PurchaseOrder extends IModelElement
{
...
}
public class PurchaseOrderResourceConversionService extends ConversionService
{
@Override
public <T> T convert( Object object, Class<T> type )
{
if( type == XmlResource.class || type == Resource.class )
{
final ByteArrayResourceStore store = service( MasterConversionService.class ).convert( object, ByteArrayResourceStore.class );
if( store != null )
{
return type.cast( new RootXmlResource( new XmlResourceStore( store ) ) );
}
}
return null;
}
}
Note the use of chaining as part of the presented ConversionService implementation. The input could be any number of things, but as long as another ConversionService implementation knows how to convert it to a ByteArrayResourceStore, this implementation will take the conversion the rest of the way to a Resource.
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.
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 );
The DiagramLayoutPersistenceService is responsible for persisting layout of the diagram, such a location and size of nodes, connection bend points, etc.
Unlike other services, DiagramLayoutPersistenceService is not defined by methods that must be implemented, but rather by its expected behavior.
Example
The architecture sample provides a comprehensive example of a custom implementation that persists layout in the same file as data.
The DragAndDropService provides means to implement drag-n-drop behavior in a diagram editor.
Example
public class MapDragAndDropService extends DragAndDropService
{
@Override
public boolean droppable( DropContext context )
{
return context.object() instanceof IFile;
}
@Override
public void drop( DropContext context )
{
IFile file = (IFile) context.object();
List locations = new ArrayList();
InputStream in = null;
try
{
in = file.getContents();
final BufferedReader br = new BufferedReader( new InputStreamReader( in ) );
for( String line = br.readLine(); line != null; line = br.readLine() )
{
if( line != null )
{
line = line.trim();
if( line.length() > 0 )
{
locations.add( line );
}
}
}
}
catch( CoreException e )
{
LoggingService.log( e );
}
catch( IOException e )
{
LoggingService.log( e );
}
finally
{
if( in != null )
{
try
{
in.close();
}
catch( IOException e ) {}
}
}
if( ! locations.isEmpty() )
{
SapphireDiagramEditorPagePart diagram = context( SapphireDiagramEditorPagePart.class );
Map map = context( Map.class );
Point initialDropPosition = context.position();
int x = initialDropPosition.getX();
int y = initialDropPosition.getY();
for( String locationName : locations )
{
if( ! map.hasLocation( locationName ) )
{
Location location = map.getLocations().insert();
location.setName( locationName );
DiagramNodePart locationNodePart = diagram.getDiagramNodePart(location);
locationNodePart.setNodeBounds( x, y );
x += 50;
y += 50;
}
}
}
}
}
The DragAndDropService is typically registered as part of diagram page definition in the sdef file.
<diagram-page>
<service>
<implementation>MapDropService</implementation>
</service>
</diagram-page>
The EqualityService provides means to implement equals() and hashCode() methods when the context object doesn't support implementing these methods directly. One such context is model elements, where the framework does not rely on a particular implementation of these methods, but having these methods behave in a way consistent with semantics of the data being modeled can be useful for other purposes.
Example
public class ContactEqualityService extends EqualityService
{
@Override
public boolean doEquals( Object obj )
{
Contact c1 = context( Contact.class );
Contact c2 = (Contact) obj;
return equal( c1.getLastName().getText(), c2.getLastName().getText() ) &&
equal( c1.getFirstName().getText(), c2.getFirstName().getText() );
}
@Override
public int doHashCode()
{
Contact c = context( Contact.class );
String lastName = c.getLastName().getText();
String firstName = c.getFirstName().getText();
return ( lastName == null ? 1 : lastName.hashCode() ) ^ ( firstName == null ? 1 : firstName.hashCode() );
}
private static boolean equal( Object obj1, Object obj2 )
{
if( obj1 == obj2 )
{
return true;
}
else if( obj1 != null && obj2 != null )
{
return obj1.equals( obj2 );
}
return false;
}
}
@Services( { @Service( impl = ContactEqualityService.class ), ... } )
public interface Contact extends IModelElement
{
ModelElementType TYPE = new ModelElementType( Contact.class );
...
}
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.
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.
##facts-servicess##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 );
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 );
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 );
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 );
The ListSelectionService functions as a conduit between the presentation layer and anything that may want to see or change the selection. The presentation layer pushes selection changes made by the user to ListSelectionService and at the same time listens for changes to selection coming from ListSelectionService.
An implementation of this service is provided with Sapphire. This service is not intended to be implemented by adopters.
Example
In this example, an action handler attaches a listener to the ListSelectionService to refresh action handler's enablement state when selection changes.
public class ExampleActionHandler extends SapphireActionHandler
{
@Override
public void init( SapphireAction action, ActionHandlerDef def )
{
super.init( action, def );
final ListSelectionService selectionService = action.getPart().service( ListSelectionService.class );
final Listener selectionListener = new Listener()
{
@Override
public void handle( Event event )
{
refreshEnablementState();
}
};
selectionService.attach( selectionListener );
attach
(
new Listener()
{
@Override
public void handle( Event event )
{
if( event instanceof DisposeEvent )
{
selectionService.detach( selectionListener );
}
}
}
);
}
}
The MasterConversionService converts an object to the specified type by delegating to available ConversionService implementations. If object is null or is already of desired type, the object is returned unchanged.
An implementation of this service is provided with Sapphire. This service is not intended to be implemented by adopters. See ConversionService instead.
Example
In this example, an IFile is converted to a Resource as part of instantiating the model. Note how this code is not aware of the details of the conversion or what type of a resource is created.
IFile file = project.getFile( "contacts.xml" )
Resource resource = ContactRepository.TYPE.service( MasterConversionService.class ).convert( file, Resource.class );
ContactRepository model = ContactRepository.TYPE.instantiate( resource );
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.
ProblemsTraversalService produces a problem-annotated traversal order through the content outline, which can be used to find the next error or warning from any location in the content outline.
An implementation of this service is provided with Sapphire. This service is not intended to be implemented by adopters.
ProblemsTraversalService extends DataService<ProblemsTraversalServiceData>
{
MasterDetailsContentNode findNextProblem( MasterDetailsContentNode reference, Status.Severity severity )
MasterDetailsContentNode findNextError( MasterDetailsContentNode reference )
MasterDetailsContentNode findNextWarning( MasterDetailsContentNode reference )
}
Example
public class ShowNextErrorActionHandler extends SapphireActionHandler
{
private ProblemsTraversalService service;
@Override
public void init( final SapphireAction action,
final ActionHandlerDef def )
{
super.init( action, def );
final MasterDetailsContentNode node = (MasterDetailsContentNode) getPart();
final MasterDetailsEditorPagePart page = node.nearest( MasterDetailsEditorPagePart.class );
this.service = page.service( ProblemsTraversalService.class );
final Listener listener = new Listener()
{
@Override
public void handle( final Event event )
{
refreshVisibility();
}
};
this.service.attach( listener );
attach
(
new FilteredListener<DisposeEvent>()
{
@Override
protected void handleTypedEvent( final DisposeEvent event )
{
ShowNextErrorActionHandler.this.service.detach( listener );
}
}
);
refreshVisibility();
}
private void refreshVisibility()
{
final MasterDetailsContentNode node = (MasterDetailsContentNode) getPart();
final MasterDetailsContentNode nextProblemNode = this.service.findNextError( node );
setVisible( nextProblemNode != null );
}
private PropertyEditorPart findFirstError( final List<SectionPart> sections )
{
for( SectionPart section : sections )
{
final PropertyEditorPart res = findFirstError( section );
if( res != null )
{
return res;
}
}
return null;
}
private PropertyEditorPart findFirstError( final SapphirePart part )
{
if( part != null )
{
if( part instanceof PropertyEditorPart )
{
if( part.validation().severity() == Status.Severity.ERROR )
{
return (PropertyEditorPart) part;
}
}
else if( part instanceof FormPart )
{
for( SapphirePart p : ( (FormPart) part ).getChildParts() )
{
final PropertyEditorPart result = findFirstError( p );
if( result != null )
{
return result;
}
}
}
else if( part instanceof PageBookPart )
{
return findFirstError( ( (PageBookPart) part ).getCurrentPage() );
}
}
return null;
}
@Override
protected Object run( final SapphireRenderingContext context )
{
final MasterDetailsContentNode node = (MasterDetailsContentNode) getPart();
final MasterDetailsContentNode nextProblemNode = this.service.findNextError( node );
if( nextProblemNode != null )
{
nextProblemNode.select();
final PropertyEditorPart firstProblemPropertyEditor = findFirstError( nextProblemNode.getSections() );
if( firstProblemPropertyEditor != null )
{
firstProblemPropertyEditor.setFocus();
}
}
return null;
}
}
VersionCompatibilityService determines whether a property is compatible with the version compatibility target. This in turn controls property enablement, validation and visibility.
In most situations, version compatibility can be expressed using an @Since or an @VersionCompatibility annotation. Both of these annotations support the expression language.
Example
@Type( base = Date.class )
@Since( "1.5" )
ValueProperty PROP_INITIAL_QUOTE_DATE = new ValueProperty( TYPE, "InitialQuoteDate" );
Value<Date> getInitialQuoteDate();
void setInitialQuoteDate( String value );
void setInitialQuoteDate( Date value );
When more control is necessary, a custom implementation of VersionCompatibilityService can be provided. A typical implementation will utilize VersionCompatibilityTargetService to determine the current version.
Example
@Type( base = Date.class )
@Service( impl = ExampleVersionCompatibilityService.class )
ValueProperty PROP_INITIAL_QUOTE_DATE = new ValueProperty( TYPE, "InitialQuoteDate" );
Value<Date> getInitialQuoteDate();
void setInitialQuoteDate( String value );
void setInitialQuoteDate( Date value );
public class ExampleVersionCompatibilityService extends VersionCompatibilityService
{
private VersionCompatibilityTargetService versionCompatibilityTargetService;
private Listener versionCompatibilityTargetServiceListener;
protected void initVersionCompatibilityService()
{
final IModelElement element = context( IModelElement.class );
final ModelProperty property = context( ModelProperty.class );
this.versionCompatibilityTargetService = VersionCompatibilityTargetService.find( element, property );
this.versionCompatibilityTargetServiceListener = new Listener()
{
@Override
public void handle( final Event event )
{
refresh();
}
};
this.versionCompatibilityTargetService.attach( this.versionCompatibilityTargetServiceListener );
}
@Override
protected Data compute()
{
final Version version = this.versionCompatibilityTargetService.version();
final String versioned = this.versionCompatibilityTargetService.versioned();
final boolean compatible = ...
return new Data( compatible, version, versioned );
}
@Override
public void dispose()
{
super.dispose();
if( this.versionCompatibilityTargetService != null )
{
this.versionCompatibilityTargetService.detach( this.versionCompatibilityTargetServiceListener );
}
}
}
VersionCompatibilityTargetService produces the version compatibility target to be referenced by VersionCompatibilityService.
When looking for the version compatibility target, the framework will first check the property, then the containing element, then the parent property and the parent element, etc. The search continues until version compatibility target is found or the model root is reached.
In most situations, version compatibility target can be expressed using an @VersionCompatibilityTarget annotation, which supports the expression language.
@VersionCompatibilityTarget( version = "${ Version }", versioned = "Purchase Order" )
@GenerateImpl
public interface PurchaseOrder extends IModelElement
{
ModelElementType TYPE = new ModelElementType( PurchaseOrder.class );
// *** Version ***
@Type( base = Version.class )
@DefaultValue( text = "2.0" )
ValueProperty PROP_VERSION = new ValueProperty( TYPE, "Version" );
Value<Version> getVersion();
void setVersion( String value );
void setVersion( Version value );
...
}
When more control is necessary, a custom implementation of VersionCompatibilityTargetService can be provided.
@Service( impl = ExampleVersionCompatibilityTargetService.class )
@GenerateImpl
public interface PurchaseOrder extends IModelElement
{
...
}
public class ExampleVersionCompatibilityTargetService extends VersionCompatibilityTargetService
{
@Override
protected void initContextVersionService()
{
// Listen on the source of the version and call refresh() when necessary.
}
@Override
protected Data compute()
{
Version version = ...
String versioned = ...
return new Data( version, versioned );
}
@Override
public void dispose()
{
super.dispose();
// Detach any listeners attached in the initContextVersionService() method.
}
}