Eclipse 3.2 - Model Roadmap for Logical Model Integration |
Version 3.2 M5
The Proposed Support for Logical Model Integration document outlines all the areas that are targeted to be addressed in 3.2 related to logical model integration support in Eclipse. The purpose of this document is to describe how model providers can make use of this API.
Here is a list of what model providers can do to take advantage of the this support:
The following sections describe each of these points in more detail. A pointer to a simple Library example is also provided.
Not all model providers want the save lifecycle of their models tied to an editor. The following support has been added to allow models to control the save lifecycle:
These interfaces are used by the workbench in the following way:
The following changes have been made to the problems view:
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:
ResourceMapping
class can be viewed here.
The methods of interest are:
Object getModelObject()
: The model object from which the
mapping was derived (or adapted).ResourceTraversal[] getTraversals(ResourceMappingContext, IProgressMonitor)
:
The resource traversal that cover the resources that constitute the model
object.ResourceTraversal
contains a set of resources and a depth flag that indicates the depth to which
the resources in the traversal are associated with the originating model object.
Resource traversals are provided to a client by a resource mapping in order
to describe the contents of a model in such a way that the client (e.g. a
repository provider) can perform its operations in as efficient a means as
possible. Methods of interest are:
getResources()
getDepth()
ResourceMappingContext
and RemoteResourceMappingContext
is a bit more complicated and is described later.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 ius covered in a separate Document
Plugins 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 plugin 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 must 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. The EObjectResourceMapping
example used above
is implemented in the example and has a more complicated implementation.
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. CVS update, CVS commit, CVS 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 CVS update, 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.
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 ned 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 are a means to group related resource mappings together. Here is a link to the ModelProvider class. This class serves three main purposes:
IResourceMappingMerger
for the model is obtained by adapting the modle provider.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.
Operations performed on resources should be validated first to ensure that the user is awar of any potential side effects. Here are the steps required to validate a resource change.
ResourceChangeDescriptionFactory
. The
factory produces an IResourceDelta
that mirrors what the resulting resource delta will look
like once the operation is performed.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 effetc of the operation on the model.When a Team provider is attempting a headless merge, it will do the following:
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.
The following improvements have been made in the area of file history and model element history:
The History view has been generalized to display the history of any object that adapts to an IHistoryPageSource. Thus, model providers can render the history for their logical model elements.
The following has been provided to support remote browsing:
Team decorations can be applied to model elements using ResourceMapping. However, the Team decorator will not know when to update the decorations on model elements that do not have a one-to-one mapping to files. The following support has been added to support label updates for this type of model element:
This example, available here, is built on a model that consists of libraries, books and writers.
Changes in version 3.2 M5