Chapter 17. Workspace and Resources API

A program is never written in isolation but instead depends on other code, icons, data, and configuration files. An extendable IDE should provide access to wherever these artifacts are stored. In Eclipse, the artifacts are referred to as resources and are stored in a workspace. The FAQs in this chapter show how resources are managed in a workspace and what API is available to control and track their lifecycle.

FAQ 284: How are resources created?

The workspace is manipulated using resource handles. Resource handles are lightweight pointers to a particular project, folder, or file in the workspace. You can create a resource handle without creating a resource, and resources can exist regardless of whether any handles exist that point to them. To create a resource, you first have to create a resource handle and then tell it to create the resource. The following snippet uses resource handles to create a project, a folder, and a file.

IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IProject project = root.getProject("MyProject");
IFolder folder = project.getFolder("Folder1");
IFile file = folder.getFile("hello.txt");
//at this point, no resources have been created
if (!project.exists()) project.create(null);
if (!project.isOpen()) project.open(null);
if (!folder.exists())
   folder.create(IResource.NONE, true, null);
if (!file.exists()) {
   byte[] bytes = "File contents".getBytes();
   InputStream source = new ByteArrayInputStream(bytes);
   file.create(source, IResource.NONE, null);
}

This example defensively checks that the resource doesn’t already exist before trying to create it. This kind of defensive programming is a good idea because an exception is thrown if you try to create a resource that already exists. This way, the example can be run more than once in the same workspace without causing an error. The null parameters to the creation methods should be replaced by a progress monitor in a real application.

FAQ 285: Can I create resources that don’t reside in the file system?

No. Resources are strictly a file system-based model. They cannot be used to represent files on a remote server or in a database. Although this prevents a large amount of the platform’s functionality from being used for nonlocal applications, there were strong architectural reasons for taking this approach. A principal design goal of Eclipse from the start was to enable it to interoperate smoothly with other tools. Because most tools operate on resources in the file system, this is an important medium for interaction between applications. If Eclipse were built on an abstract resource model with no necessary connection with the file system, interaction between Eclipse plug-ins and non-Eclipse-aware tools would be very difficult.

Having said that, nothing requires you to use IResource as your model layer. Almost none of the base platform is tied to resources, and the text-editing framework can also operate on non-IResource models. If you want to build plug-ins that operate on files located remotely, you can define your own model layer to represent them and build views and editors that interact with that remote model rather than with the strictly local IResource model. In Eclipse 3.0, the RCP has no relationship at all with the IResource-based workspace. Only the Eclipse IDE plug-ins still retain a dependency on this model.

If you use resources, you will be tied to the local file system, but you will have a common layer that allows you to interoperate seamlessly with other tools. If you don’t want to be tied to the local file system, you can build your own model at the expense of lost integration with plug-ins that are not aware of it. Seamless integration of disparate tools based on a completely abstract resource model is a lofty idea, but like many lofty ideas, it is one that has yet to take flight.

FAQ 286: What is the difference between a path and a location?

In general, the term location represents physical file system paths, and path represents a resource’s logical position within the workspace. Many people are confused by this distinction, as both terms are represented by using the IPath data type.

IPath is in fact an abstract data structure that represents a series of slash-separated strings. Many people assume that paths always correspond to a file-system location, but this is not always the case. In fact, IPath objects are used in a variety of contexts for locating an object within a tree-like hierarchy. For example, a resource path, returned by IResource.getFullPath, represents the position of a resource within the workspace tree. The first segment is the name of the project, the last segment is the name of the file or folder, and the middle segments are the names of the parent folders in order between the project and the file or folder in question. This doesn’t always match the path of the resource in the file system!

The file-system location of a resource, on the other hand, is returned by the method IResource.getLocation. Methods on IContainer and IWorkspaceRoot can help you convert from one to the other. The following code converts from a path to a location:

IPath path = ...;
IWorkspace workspace = ResourcesPlugin.getWorkspace();
IWorkspaceRoot root = workspace.getRoot();
IResource resource = root.findMember(path);
if (resource != null) {
   location = resource.getLocation();
}

Note that in some situations, a resource can have a null location. In particular, resources whose project doesn’t exist and linked resources that are relative to a nonexistent path variable will have a null location.

Here is the converse code to convert from a location to the workspace paths that correspond to it:

IPath location = ...;
IFile[] files = root.findFilesForLocation(location);
IFolder[] folders = root.findContainersForLocation(location);
if (files.length > 0) {
   for (int i = 0; i < files.length; i++)
      path = files[i].getLocation();
} else {
   for (int i = 0; i < folders.length; i++)
      path = folders[i].getLocation();
}

As this snippet shows, a single file-system location can correspond to multiple resources. This is true because linked resources can point to locations inside other projects. Of course, the same file-system location can’t correspond to both files and folders at the same time.

When using API from the resources plug-in that involves IPath objects, always read the API javadoc carefully to see whether it deals with paths or locations. These different types of paths must never be mixed or used interchangeably, as they represent completely different things.

FAQ 287: When should I use refreshLocal?

Resources become out of sync with the workspace if they are changed directly in the file system without using Eclipse workspace API. If your plug-in changes files and folders in this way, you should manually synchronize the workspace with the file system using the method IResource.refreshLocal. Some common situations where this is necessary include:

  • When files are manipulated using java.io or java.nio, such as writing to a FileOutputStream.

  • When your plug-in calls an external editor that is not Eclipse-aware.

  • When you do file I/O using your own natives.

  • When you launch external builders, tools, or Ant scripts that modify files in the workspace.

Here is an example snippet that sets the contents of a file using a FileOutputStream, and then uses refreshLocal to synchronize the workspace. This example is a bit contrived, because you could easily use the workspace API in this case. Imagine that the actual file manipulation is buried in some third-party library that you can’t change, and this code makes more sense.

private void externalModify(IFile iFile) throws ... {
   java.io.File file = iFile.getLocation().toFile();
   FileOutputStream fOut = new FileOutputStream(file);
   fOut.write("Written by FileOutputStream".getBytes());
   iFile.refreshLocal(IResource.DEPTH_ZERO, null);
}

You might reasonably ask why the workspace does not refresh out of sync files automatically. The answer in Eclipse 3.0 is that it can, with some caveats. The problem is that there is no way to do this efficiently in a platform-neutral way. Some operating systems have callback mechanisms that send notifications when there are changes to particular sections of the file system, but this support doesn’t exist on all operating systems that Eclipse runs on. The only way to perform automatic refresh across all platforms is to have a background thread that periodically polls the file system for changes. This clearly can have a steep performance cost, especially since workspaces might easily contain tens of thousands of files.

The solution in Eclipse 3.0 is to have an auto-refresh capability that is turned off by default. On platforms that have efficient native support for change notification, this can be quite fast. On other platforms, change notification can be very slow. Users who have a need to modify files externally on a regular basis may gladly pay the performance cost to get automatic workspace refresh. Users who do not make external modifications to files, can leave auto-refresh off and avoiding paying a performance penalty for a feature they do not need. This follows the general performance rule of only introducing a performance hit if and when it is actually needed. Auto-refresh can be turned on by checking Refresh workspace automatically on the Workbench preference page.

The down side of auto-refresh is that programmatic clients of the workspace API still need to check for and defend against out of sync resources. Resources can be out of sync if auto-refresh is turned off, and they can even be out of sync when auto-refresh is turned on. If you are programmatically modifying files in the workspace externally, you should still perform a refreshLocal to make sure files come back in sync as quickly as possible.

If you are using Eclipse 2.1, you can install auto-refresh as a separate plug-in. This plug-in is hosted on the Eclipse Platform core team home page, to be found at the Eclipse Web site (http://eclipse.org), by clicking on Projects > The Eclipse Project > Platform > Core > Development Resources and Planning.

FAQ 288: How do I create my own tasks, problems, bookmarks, and so on?

Annotations can be added to resources in the workspace by creating IMarker objects. These markers are used to represent compile errors, to-do items, bookmarks, search results, and many other types of annotations. You can create your own marker types for storing annotations for use by your own plug-in. Each marker can store an arbitrary set of attributes. Attributes are keyed by a string, and the values can be strings, Booleans, or integers. The IMarker interface defines some common attribute types, but you are free to create your own attribute names for your markers. Here is an example snippet that creates a marker, adds some attributes, and then deletes it:

final IFile file = null;
IMarker marker = file.createMarker(IMarker.MARKER);
marker.setAttribute(IMarker.MESSAGE, "This is my marker");
marker.setAttribute("Age", 5);
marker.delete();

When markers are created, modified, or deleted, a resource change event will be broadcast, telling interested parties about the change. You can search for markers by using the findMarkers methods on IResource.

The org.eclipse.core.resources.markers extension point can be used to declaratively define new marker types. See its documentation for an explanation and examples.

Note

FAQ 288: How do I create my own tasks, problems, bookmarks, and so on?

FAQ 289 How can I be notified of changes to the workspace?

FAQ 304 Why don’t my markers appear in the editor’s vertical ruler?

FAQ 338 How do I create problem markers for my compiler?

eclipse.org article “Mark My Words”

Go to Platform Plug-in Developer Guide > Programmer’s Guide > Resources overview > Resource markers

FAQ 289: How can I be notified of changes to the workspace?

Resource change listeners are notified of most changes that occur in the workspace, including when any file, folder, or project is created, deleted, or modified. Listeners can also be registered for some special events, such as before projects are deleted or closed and before and after workspace autobuilds. Registering a resource change listener is easy:

IWorkspace workspace = ResourcesPlugin.getWorkspace();
IResourceChangeListener rcl = new IResourceChangeListener() {
   public void resourceChanged(IResourceChangeEvent event) {
   }
};
workspace.addResourceChangeListener(rcl);

Always make sure that you remove your resource change listener when you no longer need it:

workspace.removeResourceChangeListener(rcl);

Look at the javadoc for IWorkspace.addResourceChangeListener for more information on the various types of resource change events you can listen to and the restrictions that apply. It is important to keep performance in mind when writing a resource change listener. Listeners are notified at the end of every operation that changes the workspace, so any overhead that you add in your listener will degrade the performance of all such operations. If your listener needs to do expensive processing, consider off-loading some of the work into another thread, preferably by using a Job as described in FAQ 127.

Note

FAQ 289: How can I be notified of changes to the workspace?

FAQ 127 Does the platform have support for concurrency?

eclipse.org article “How You’ve Changed! Responding to resource changes in the Eclipse workspace”

Go to Platform Plug-in Developer Guide > Programmer’s Guide > Resources overview > Tracking resource changes

FAQ 290: How do I prevent builds between multiple changes to the workspace?

Every time resources in the workspace change, a resource change notification is broadcast, and autobuild gets a chance to run. This can become very costly if you are making several changes in succession to the workspace. To avoid these extra builds and notifications, it is very important that you batch all of your workspace changes into a single workspace operation. It is easy to accidentally cause extra builds if you aren’t very careful about batching your changes. For example, even creating and modifying attributes on IMarker objects will cause separate resource change events if they are not batched.

Two different mechanisms are available for batching changes. To run a series of changes in the current thread, use IWorkspaceRunnable. Here is an example of a workspace runnable that creates two folders:

final IFolder folder1 = ..., folder2 = ...;
workspace.run(new IWorkspaceRunnable() {
   public void run(IProgressMonitor monitor) {
      folder1.create(IResource.NONE, true, null);
      folder2.create(IResource.NONE, true, null);
   }
}, null);

The other mechanism for batching resource changes is a WorkspaceJob. Introduced in Eclipse 3.0, this mechanism is the asynchronous equivalent of IWorkspaceRunnable. When you create and schedule a workspace job, it will perform the changes in a background thread and then cause a single resource change notification and autobuild to occur. Here is sample code using a workspace job:

final IFolder folder1 = ..., folder2 = ...;
Job job = new WorkspaceJob("Creating folders") {
   public IStatus runInWorkspace(IProgressMonitor monitor)
      throws CoreException {
      folder1.create(IResource.NONE, true, null);
      folder2.create(IResource.NONE, true, null);
      return Status.OK_STATUS;
   }
};
job.schedule();

Note

FAQ 290: How do I prevent builds between multiple changes to the workspace?

FAQ 127 Does the platform have support for concurrency?

FAQ 306 What are IWorkspaceRunnable, IRunnableWithProgress, and WorkspaceModifyOperation?

FAQ 291: Why should I add my own project nature?

Project natures act as tags on a project to indicate that a certain tool is used to operate on that project. They can also be used to distinguish projects that your plug-in is interested in from the rest of the projects in the workspace. For example, natures can be used to filter declarative extensions that operate only on projects of a particular type. The propertyPages and popupMenus extension points allow you to filter enablement of an extension, based on various properties of the selected resource. One of the properties that this mechanism understands is the nature of a project. Here is an example of an actionSet declaration that operates only on files in projects with the PDE nature:

<extension point="org.eclipse.ui.popupMenus">
   <objectContribution
      objectClass="org.eclipse.core.resources.IFile"
      id="org.eclipse.pde.ui.featureToolSet">
      <filter
         name="projectNature"
         value="org.eclipse.pde.FeatureNature">
      </filter>
      ...

Another reason for using natures is to make use of the nature lifecycle methods when your plug-in is connected to or disconnected from a project. When a nature is added to a project for the first time, the nature’s configure method is called. When the nature is removed from the project, the deconfigure method is called. This is an opportunity to initialize metadata on the project and to associate additional attributes, such as builders, with the project.

Note

FAQ 291: Why should I add my own project nature?

eclipse.org article “Project Natures and Builders”

Go to Platform Plug-in Developer Guide > Programmer’s Guide > Resources overview > Project natures

FAQ 292: Where can I find information about writing builders?

Incremental project builders are used to transform resources in a project to produce some output. As the project changes, the builder is responsible for incrementally updating its output based on the nature of the changes. Some excellent resources are available on how builders work and how to create your own, so we won’t get into more detail here.

Note

FAQ 292: Where can I find information about writing builders?

FAQ 325 How do I implement an Eclipse builder?

eclipse.org article “Project Natures and Builders”

Go to Platform Plug-in Developer Guide > Programmer’s Guide > Resources overview > Incremental project builders

FAQ 293: How do I store extra properties on a resource?

Several mechanisms are available for attaching metadata to resources. The simplest are session and persistent properties, which affix arbitrary objects (for session properties) or strings (for persistent properties) to a resource keyed by a unique identifier. Session properties are discarded when the platform shuts down, and persistent properties are saved on disk and will be available the next time the platform starts up. Markers are intended to work as annotations to a particular location in a file but in reality can be used to attach arbitrary strings, integers, or Boolean values to a resource. Finally, synchronization information can be attached to a resource (see ISynchronizer). Sync info was designed for clients, such as repositories or deployment tools, that need to track differences between local resources and a remote copy. Such clients have some very special features, including the ability to maintain the metadata regardless of whether the resource exists. Although quite specialized, this form of metadata may be appropriate for use in your plug-in.

Table 17.1 provides a high-level view of the various forms of metadata, along with some of their key design features. Speed refers to the amount of CPU time required for typical access and storage operations. Footprint refers to the memory space required for storing the information during a session. Persistent properties are not stored in memory at all, except for a small cache, which makes them good memory citizens but terrible for performance. Notify refers to whether resource change notification includes broadcasting changes to this metadata. Note that this is not always a good thing: Performing a resource change broadcast has a definite performance cost, which will be incurred whether or not you care about the notification. Also, metadata that is included in resource change events cannot be changed by a resource change listener. So if you need a resource change listener to store some state information, you’re stuck with either session or persistent properties. Persistence describes when the information is written to disk. Full save happens when the platform is being shut down, and snapshot is a quick incremental save that happens every few minutes while the platform is running. Data types identify the Java data types that can be stored with that metadata facility.

Table 17.1. Forms of resource metadata

 

Speed

Footprint

Notify

Persistence

Types

Markers

Good

Medium

Yes

Save; snapshot

String, int, boolean

Sync info

Good

Medium

Yes

Save; snapshot

byte[]

Session Property

Fast

Medium

No

None

String

Persistent Property

Slow

None

No

Immediate

String

Keep in mind that the best solution for your particular situation isn’t always as simple as picking one of these four mechanisms. Sometimes a combination of these systems works better. For example, session properties can be used as a high-performance cache during an operation, and the information can then be flushed to a persistent format, such as persistent properties, when your plug-in is not in use. Finally, for large amounts of metadata, it is often better to store the information in a separate file. You can ask the platform to allocate a metadata area for your plug-in, using IProject.getWorkingLocation, or you can store metadata directly in the project’s content area so that it gets shared with the user’s repository.

Note

Forms of resource metadata

FAQ 288 How do I create my own tasks, problems, bookmarks, and so on?

FAQ 294 How can I be notified on property changes on a resource?

FAQ 294: How can I be notified on property changes on a resource?

It depends what you mean by properties. For some metadata stored on resources, such as markers and sync info, an IResourceChangeListener can be used to be notified when they change. Other metadata, such as session and persistent properties, has no corresponding change notification. This is a design trade-off, as tracking and broadcasting change notifications can be quite expensive. Session and persistent properties are designed to be used only by the plug-in that declared the property, so other plug-ins should never be tracking or changing properties declared by your plug-in.

Note

FAQ 294: How can I be notified on property changes on a resource?

FAQ 289 How can I be notified of changes to the workspace?

FAQ 293 How do I store extra properties on a resource?

FAQ 296 How can I be notified when the workspace is being saved?

FAQ 324 How do I react to changes in source files?

FAQ 295: How and when do I save the workspace?

If you’re using the Eclipse IDE workbench, you don’t. When it shuts down, the workbench will automatically save the workspace. The workspace will also perform its own periodic workspace saves, called snapshots, every once in a while. Note that the most essential information in the workspace—the files and folders that the user is working with—are always stored on disk immediately. Saving the workspace simply involves storing away metadata, such as markers, and its in-memory picture of the projects. The workspace is designed so that if a user pulls the computer power cord from the wall at any moment, the workspace will be able to restart in a consistent state with minimal loss of information.

Nonetheless, it is possible for your plug-in to explicitly request a workspace save or snapshot. If you are writing an RCP application, you are responsible for minimally invoking save before shutdown. The following example saves the workspace:

final MultiStatus status = new MultiStatus(...);
IRunnableWithProgress runnable = new IRunnableWithProgress() {
   public void run(IProgressMonitor monitor) {
      try {
         IWorkspace ws = ResourcesPlugin.getWorkspace();
         status.merge(ws.save(true, monitor));
      } catch (CoreException e) {
         status.merge(e.getStatus());
      }
   }
};
new ProgressMonitorDialog(null).run(false, false, runnable);
if (!status.isOK())
   ErrorDialog.openError(...);

Note that the save method can indicate minor problems by returning an IStatus object, or major problems by throwing an exception. You should check both of these results and react accordingly. To request a workspace snapshot, the code is almost identical: pass false as the first parameter to the save method.

Note

FAQ 295: How and when do I save the workspace?

FAQ 176 How do I make the workbench shutdown?

FAQ 296 How can I be notified when the workspace is being saved?

FAQ 296: How can I be notified when the workspace is being saved?

If your plug-in maintains a model based on the workspace, you will want to save your model to disk whenever the workspace is saved. This ensures that your model will be in sync with the workspace every time the platform starts up. Your plug-in can take part in the workspace save process by registering an ISaveParticipant. It is a common mistake to try to perform saving directly from your Plugin.shutdown method, but at this point it is too late to make changes to the workspace. The workspace is saved before any plug-ins start to shut down, so any changes made to files, markers, and other workspace state from your plug-in’s shutdown method will be lost.

The three kinds of save events are full workspace saves, snapshots, and project saves. Projects cannot be saved explicitly, but they are saved automatically when they are closed. Snapshots must be fast, saving only essential information. Full saves can take longer, and they must ensure that all information that will be needed in future sessions is persisted.

You must register your save participant at the start of each session. When you register your participant, you receive a resource delta describing all the changes that occurred since the last save you participated in. This allows your model to catch up with any changes that happened before your plug-in started up. This delta is exactly like the resource deltas provided to a resource change listener. After processing this delta, you can be sure that your model is perfectly in sync with the workspace contents.

After the initial registration, your save participant will be notified each time the workspace is saved.

Note

FAQ 296: How can I be notified when the workspace is being saved?

FAQ 295 How and when do I save the workspace?

Go to Platform Plug-in Developer Guide > Programmer’s Guide > Resources overview > Workspace save participation

FAQ 297: Where is the workspace local history stored?

Every time you modify a file in Eclipse, a copy of the old contents is kept in the local history. At any time, you can compare or replace a file with any older version from the history. Although this is no replacement for a real code repository, it can help you out when you change or delete a file by accident. Local history also has an advantage that it wasn’t really designed for: The history can also help you out when your workspace has a catastrophic problem or if you get disk errors that corrupt your workspace files. As a last resort, you can manually browse the local history folder to find copies of the files you lost, which is a bit like using Google’s cache to browse Web pages that no longer exist. Each file revision is stored in a separate file with a random file name inside the history folder. The path of the history folder inside your workspace is

.metadata/.plugins/org.eclipse.core.resources/.history/

You can use your operating system’s search tool to locate the files you are looking for. Although not the prettiest backup system, it sure beats starting over from scratch!

FAQ 298: How can I repair a workspace that is broken?

In some rare situations, the Eclipse workspace can become corrupt and unreadable. In particular, this can happen when VM errors, such as OutOfMemoryError, occur. The platform core team home page includes a plug-in that can be used to restore a workspace that has become corrupt. This plug-in will be able to restore only basic workspace metadata information, so metadata created by other plug-ins may still be lost. From eclipse.org, select Projects > Eclipse Project > Platform > Core, and then look for the workspace restorer plug-in on the development resources page. Be sure to also enter a bug report in the Eclipse Bugzilla with any available log information to help prevent such disasters from happening to others!

FAQ 299: What support does the workspace have for team tools?

Repository tools often need special control over how files in the workspace are managed. The workspace API includes special hooks that allow repository tools to reimplement certain methods, validate and veto file changes, and store synchronization state on resources. These facilities are generally well described in the Platform Plug-in Developer Guide, but here is a quick tour of special repository integration support in the workspace:

  • IFileModificationValidatorThis hook is called when a file is about to be edited and immediately prior to a file being saved. This gives repository providers a chance to check out the file being changed or to veto the modification. A repository provider can supply a validator by overriding getFileModificationValidator on RepositoryProvider.

  • IMoveDeleteHookThis hook allows repository providers to reimplement the resource move and delete methods. This is used both for validation prior to move and deletion and for transferring version history when a resource is moved. This hook is supplied by overriding getMoveDeleteHook on RepositoryProvider.

  • TeamHookThe workspace API developers realized that the existing hook methods could not be extended without breaking API compatibility. To avoid an explosion of specialized hook methods, the developers decided to consolidate future team-integration hooks in a single place. This hook is currently used only for validating linked resource creation, but any future hooks that are added for team plug-ins will appear here.

  • ISynchronizerThis API associates synchronization information, such as file timestamps, with a resource. The synchronization information is represented as arbitrary bytes, so you can store whatever information you want, using the synchronizer. The workspace will take care of storing and persisting this information across sessions and will broadcast resource change notifications when this information changes.

Note that although these facilities were designed for use by repository tools, sneaky plug-ins have been known to use them for other purposes. If you have very specialized needs in your plug-in, it may be justifiable to use some of these hooks yourself. For example, the plug-in development tools use these hooks for binary plug-in projects to prevent users from deleting content that is linked back into your install directory. Keep in mind that in the case of the hook methods, a project can have only one hook implementation. Using these hooks for your plug-in will prevent other repositories from being able to connect to those projects.

Note

ISynchronizer.

FAQ 310 What APIs exist for integrating repository clients into Eclipse?

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.229.111