Model Roadmap for Logical Model Integration

Here is a list of what model providers can do to take advantage of the Team logical model support:

  1. Adapt model elements in model views to ResourceMapping in order to allow resource based operations to appear on your model elements.
  2. Register a ModelProvider to ensure that your model is consulted when operations are performed on the resources related to your model.
  3. Use the ResourceChangeValidator when performing operations on resources in order to ensure that any potential side effects on the model elements related to those resources are made known to the user.
  4. Implement an IResourceMappingMerger in order to participate in headless merges that involve the resources related to your model.
  5. Register a teamContentProvider in order to participate in Team viewers such as merge previews.
  6. Provide an IHistoryPageSource to show logical model history in the Team history view.
  7. Use the Eclipse File System API to access the remote state of model projects.
  8. Use the SynchronizationStateTester API to ensure proper decoration of model elements that do not have a one-to-one mapping to resources.
  9. Use the Saveable API to manage the save life cycle of model elements.
  10. Use the ElementLocalHistoryPageSource API to show local history for sub-file model elements (e.g. Java methods)
  11. Use the ChangeTracker API to group related changes together.

The following sections describe each of these points in more detail. The org.eclipse.ui.examples.filesystem plug-in contain an example that illustrate several of these points. You can check the project out from the Git repository and use it as a reference while you are reading this tutorial. Disclaimer: The source code in the example plug-ins may change over time. To get a copy that matches what is used in this example, you can check out the project using the 3.3 version tag (most likely R3_3) or a date tag of June 30, 2007.

Resource Mappings

The Basic Resource Mapping API

The resource mapping API is purposely simple with logical model manipulations omitted. A client can't use this interface to display logical models or gain any interesting additional knowledge about it. It's purpose is simply to map one or more model elements to workspace resources.

The API consists of the following classes:

There are two types of plugins that should be interested in resource mappings. Those who provide a model that consists of, or is persisted in, resources in the workspace and those that want to perform operations on resources. The former is covered in the next section while the later is covered in the Repository Roadmap for Logical Model Integration.

Adapting a Model to a ResourceMapping

Plug-ins that adapted their model objects to IResource in order to get resource specific actions shown in the context menu can now adapt to ResourceMapping if a richer description of how the object adapts to resources is beneficial. However, they are not required to do so if there is no benefit. For instance a Java compilation unit (i.e. *.java file shown in a JDT view) that now currently adapts to IFile need not adapt to ResourceMapping since nothing is gained. However, a Java package should adapt to ResourceMapping in order to indicate that the package consists of only the files in the corresponding folder and not the subfolders.

The preferred way to adapt model elements to a resource mapping is to use an adapter factory. The following is the XML markup for contributing an adapter factory in a plug-in manifest.

   <extension
point="org.eclipse.core.runtime.adapters">
<factory
class="org.eclipse.example.library.logical.AdapterFactory"
adaptableType="org.eclipse.example.library.Book">
<adapter type="org.eclipse.core.resources.mapping.ResourceMapping"/>
</factory>
<factory
class="org.eclipse.example.library.logical.AdapterFactory"
adaptableType="org.eclipse.example.library.Library">
<adapter type="org.eclipse.core.resources.mapping.ResourceMapping"/>
</factory>
...
</extension>

The adapter factory implementation would look something like this:

public class AdapterFactory implements IAdapterFactory {
public Object getAdapter(Object adaptableObject, Class adapterType) {
if((adaptableObject instanceof EObject) && adapterType == ResourceMapping.class) {
return new EObjectResourceMapping((EObject)adaptableObject);
}
return null;
}

public Class[] getAdapterList() {
return new Class[] {ResourceMapping.class};
}
}

Model objects can implement the IAdaptable interface. When they do so, they must ensure that the Platform adapter manager is consulted. This can be done by either subclassing PlatformObject or by using the following line of code:

Platform.getAdapterManager().getAdapter(Object, Class)

The above is the preferable approach. However, the model object can implement the IAdaptable interface and provide a getAdapter(Class) implementation that creates returns an instance of ResourceMapping explicitly when asked for one. This is a more straightforward approach but the least desirable as the model must have explicit knowledge of the adaptation to resources.

In some cases, the provider of a logical model may not want their model to adapt to IResource in every context or may want the object to adapt differently for object contributions than for other contexts. The workbench UI provides a special intermediate adapter API, IContributorResourceAdapter, for this purpose. When objects are being adapted to IResource in the context of object contributions, the workbench first tries to adapt the resource to IContributorResourceAdapter before trying to adapt to IResource directly. A new sub-interface of this interface, IContributorResourceAdapter2, has been added which provides the same capability for ResourceMapping. The only difference is that the model provider should register a factory for IContributorResourceAdapter since the Workbench does an instanceof check to see if the contributed adapter is also an instance of IContributorResourceAdapter2.

The implementation of the ResourceMapping subclass for a Java package would look something like this.

public class JavaPackageResourceMapping extends ResourceMapping {
IPackageFragment package;
...
public getModelObject() {
return package;
}
public ResourceTraversals[] getTraversals(
ResourceMappingContext context,
IProgressMonitor monitor) {
return new ResourceTraversal[] {
new ResourceTraversal(
new IResource[] { package.getCorrespondingResource() },
IResource.DEPTH_ONE, IResource.NONE)
}
}
}

This is a fairly straightforward mapping so the implementation is not complex. The complexity of the resource mapping implementation will, of course, vary from model to model.

Resource Mapping Context

One of the advantages of a Resource Mapping API is that it allows plug-ins to implement any operations they desire in terms of resource mappings (e.g. Git pull, Git commit, Git tag, dirty decoration, etc.). However, the API that has been introduced so far deals only with the local state of the model. When working with a model that may be shared between developers, you end up in a situation where the remote state of the model (i.e. the state of the model that another user has checked-in to the repository) may differ from the state in the workspace. If you performed a Git pull, you would want the local state of the model to match the remote state even if it meant that additional files needed to be included or some files needed to be removed.

This is not an issue for some logical models. For instance, a java package is a container visited to a depth of one, regardless of the remote state of the model. Given this, a repository provider can easily determine that outgoing deletions should be included when committing or that incoming additions should be included when updating. However, the resources that constitute some logical models may change over time. For instance, the resources that constitute a model element may depend of the contents of a manifest file (or some other similar mechanism). In order for the resource mapping to return the proper traversal, it must access the remote contents of the manifest file (if it differs from the local contents) in order to see if there are additional resources that need to be included. These additional resources may not exist in the workspace but the repository provider would know how to make sure they did when the selected action was performed.

In order to support these more complex models, a RemoteResourceMappingContext can be passed to the ResourceMapping#getTraversals method. When a context is provided, the mapping can use it to ensure that all the necessary resources are included in the traversal. If a context is not provided, the mapping can assume that only the local state is of interest.

When does a ResourceMapping need to worry about the RemoteResourceMappingContext?

A ResourceMapping need only worry about a context supplied to the getTraversals method in cases were the resources that make up a model change over time and the relationship between the model and resources cannot be described by a simple traversal that is guaranteed to encompass those resources (and only those resources) that constitute the model. For example, although the resources of a Java package may change over time, the package can be described as a folder of depth of one so a resource mapping for java packages would not need to make use of the resource mapping context.

As a more complicated example, consider an HTML file that contains several images. Let's make the assumption that any images references from an HTML file are part of the model of that file. When updating the local contents of the HTML file from a repository, the user would expect that any new images would be included. The getTraversals method for a ResourceMapping for the HTML file model would look something like this:

public class HTMLResourceMapping extends ResourceMapping {
private HTMLFile htmlFile;
public ResourceTraversal[] getTraversals(ResourceMappingContext context,
IProgressMonitor monitor)
IResource[] resources = htmlFile.getResources();
if (context instanceof RemoteResourceMappingContext) {
// Look for any additional resources on the server
RemoteResourceMappingContext remoteContext = (RemoteResourceMappingContext)context;
IFile file = htmlFile.getFile();
if (remoteContext.hasRemoteChange(file, monitor)) {
IStorage storage = remoteContext.fetchRemoteContents(file, monitor);
IResource[] additionalResources = getReferences(storage.getContents());
resources = combine(resources, additionalResources);
}
if (remoteContext.isThreeWay() && remoteContext.hasLocalChange(file, monitor)) {
IStorage storage = remoteContext.fetchBaseContents(file, monitor);
IResource[] additionalResources = getReferences(storage.getContents());
resources = combine(resources, additionalResources);
}
}
return new ResourceTraversal[] {
new ResourceTraversal(resources, IResource.DEPTH_ZERO, IResource.NONE)};
}
}

Notice that there are two sets of resources included in the model: those derived from the local contents of the HTML file in the workspace and those obtained from the contents of the remote file and base file. In either of these two sets, there may be resources that do not exist in the workspace. For instance, the local HTML file may contain a relative link to an image that does not exist in the workspace. This resource should be included so that it will be fetched if it exists remotely. As for the remote file, it may contain a new copy that references additional images that should be fetched when the new remote contents are downloaded.

Model Providers

Model providers are a means to group related resource mappings together. Here is a link to the ModelProvider class. This class serves three main purposes:

  1. From a model provider, clients can then obtain additional API pieces for performing operations on a set of resource mappings using the adaptable mechanism. For example, the IResourceMappingMerger for the model is obtained by adapting the model provider.
  2. Given a set of file-system resources, clients can query whether a model provider has model elements persisted in those resources and, if it does, obtain the set of resource mappings that describe the relationship.
  3. For operations on a set of resources, the resource change validator will query the model providers in order to determine if there are any potential side effects of an operation that users should be made aware of. This is covered in a separate section on change validation.

The following is an example of a modelProvider extension definition.

   <extension
id="modelProvider"
name="Library Example"
point="org.eclipse.core.resources.modelProviders">
<modelProvider
class="org.eclipse.team.examples.library.adapt.LibraryModelProvider"
name="Library Example"/>
<extends-model id="org.eclipse.core.resources.modelProvider"/>
<enablement> <test property="org.eclipse.core.resources.projectNature" value="org.eclipse.team.examples.library.view.nature" />
</enablement>
</extension>

The LibraryModelProvider is a subclass of ModelProvider. The enablement rule is used to match resources that the Library model persists its model in. In the above example, the model provider will match any resource in a project that has the library nature.

Once the model provider is defined, the ResourceMapping#getModelProviderId() method should be overridden to return the id of the model provider.

   public String getModelProviderId() {
return "org.eclipse.team.examples.library.adapt.modelProvider";
}

To get the proper inverse mapping of resources to resource mapping for those resources that match your provider's enablement rule, you should also override one or both of the getMapping methods. The method that you need to override depends on whether your model has elements that contain multiple resources or not. If your model elements map to a single resource, you can override the method that accepts a singleIResource argument. Otherwise, you will need to override the method that accepts an array of resources. Here's an example using the single resource case.

The following example method wraps a library model file in an appropriate resource mapping. It also wraps folders that contain files that are of interest to the model provider.

public class LibraryModelProvider extends ModelProvider {
public ResourceMapping[] getMappings(IResource resource,
ResourceMappingContext context, IProgressMonitor monitor) {
if (isModelFile(resource)) {
// Return a resource mapping on the file
return new LibraryResourceMapping(resource);
} if (containsModelFiles(resource)) {
// Create a deep resource mapping on the container
return new LibraryContainerResourceMapping(resource);
}
// The resource is not of interest to this model provider
return null;
}
}

Clients can then access the model provider to determine whether the model providers cares about the resources that are about to be operated on. The next section describes API that will be provided to team operations that use the model provider API to determine the complete set of resource mappings to be operated on when a team operation is performed on a set of selected resources or model elements.

Resource Change Validation

Operations performed on resources should be validated first to ensure that the user is aware of any potential side effects. Here are the steps required to validate a resource change.

  1. Build up a description of the change using the IResourceChangeDescriptionFactory. The factory produces an IResourceDelta that mirrors what the resulting resource delta will look like once the operation is performed.
  2. Validate the change using the ResourceChangeValidator. The validator consults all the model providers that have registered an interest in the affected resources. The result is one or more status that contain the id of the originating model and a description of the potential side effect of the operation on the model.
  3. Make the user aware of any potential side effects from models that are unknown to the originator of the operation. For instance, if a Java refactoring has received a side effect from the Java model, it can be ignored since the refactoring understands the semantics of the Java model. However, if a side effect from the Library model is returned, it should be made known to the user since Java has no knowledge of the Library model.

Model-based Merging

When a Team provider is attempting a headless merge, it will do the following:

  1. Obtain the resource mappings from the selected elements
  2. Determine the model providers involved using the ResourceMapping#getModelProvider() method.
  3. Expand the scope of the operation to include all necessary resource mappings.
  4. Build up a description of the synchronization state between the local and remote. This description is provided to the models through the IMergeContext API.
  5. Adapt the model providers to IResourceMappingMerger.
  6. Invoke the validateMerge method on each merger, passing in the synchronization description, to ensure there is not some condition that should prevent an attempted merge.
  7. Delegate the merge to the model mergers to perform the merge.
  8. Model providers can delegate the merge of individual files back to the Team provider if they only want to control the order of the merge or may perform the merge themselves and signal to the team provider when they are done.

Model Content in Team Operation Views

The display of model elements in the context of a Team operation is made possible by the Common Navigator framework.

The above steps will allow models to appear in dialogs used by team operations. There are additional steps required to integrate into a merge preview.

History View

The following improvements have been made in the area of file history and model element history:

Remote Browsing

The following has been provided to support remote browsing:

Decorating Model Elements with Team State

Team providers can decorate model elements by converting their lightweight decorators to work for resource mappings in the same way object contributions are converted to work for resource mappings. However, there is one aspect of logical model element decoration that is problematic. If a model element does not have a one-to-one mapping to a resource, the model element may not receive a label update when the underlying resources change.

To address this issue, the ITeamStateProvider was introduced in order to give model providers access to state changes that may affect team decorations. In addition, model views can use a SynchronizationStateTester to determine when the labels of logical model elements need to be updated. This API relies on the ITeamStateProvider interface to determine when the team state of resource has changed and can be passed to a team decorator as part of an IDecorationContext.

Using the Saveable API

Logical model providers may use the Saveable API to manage the Save lifecycle of their model elements. If they do so, they should adapt their Saveable instances to a ResourceMapping to ensure that repository providers (and other tools) can determine which resources would be affected if the saveable was saved. For instance, this is important in the case where a repository provider wants to ensure that there are no open dirty editors on a set of resources before a repository operation is performed.

Showing the history of sub-file elements

The ElementLocalHistoryPageSource class can be used by models to show the local history for a sub-file element such as a Java method. History can be shown in the History view by calling the TeamUI#showHistory method with the model element and history page source as arguments. Models that call this also need to provide a subclass of StructureCreator as described in the Advanced compare techniques section. The history can also be shown in a dialog using the HistoryPageCompareEditorInput class. Subclasses can control whether the dialog is to be used for comparison purposes or as a dialog that supports replacing the current element with an element from the history.

Grouping related changes

Model providers that wish to ensure that changes to sets of resources are committed or checked-in to a repository together can subclass the ChangeTracker class. This class is used to track local changes and issue requests to the repository provider to group related changes together.