Resources and the file system

When the platform is running and the resources plug-in is active, the workspace is represented by an instance of IWorkspace, which provides protocol for accessing the resources it contains. An IWorkspace instance represents an associated collection of files and directories in one or more file systems. You can access the workspace from the resources plug-in class (defined in org.eclipse.core.resources).

   IWorkspace workspace = ResourcesPlugin.getWorkspace(); 

When the resources plug-in is not running, the workspace exists solely in the file system and is viewed or manipulated by the user via standard file-based tools. Let's look at what a workspace looks like on disk as we explain the resources plug-in API.

Our sample tree on disk

When you started the platform SDK, you were prompted to provide a workspace directory. This is the directory where various plug-ins store interesting metadata that is unique to a particular instance of the platform. By default, the resources plug-in stores each project in a sub-directory of the workspace directory. Within these subdirectories are the folders and files that each project contains.

Let's say your chose the directory c:\MySDK\workspace for your workspace. Inside this directory we find subdirectories named after the workspace's projects, MyWeb and MyServlet. These are called the projects' content directories. Content directories are created by the platform when the user creates a project.

Inside each directory, we find the files and folders within the project, laid out exactly the same as they are in the workspace's resource tree. All file names are the same, and the files' contents are the same whether accessed from the file system or from the workspace. The only surprise is the .project file, explained in a moment.

   C:\MySDK\workspace  (workspace root)
      .metadata\ (platform metadata directory
      MyWeb\ (project content directory for MyWeb)
	 .project
         index.html
         images\
            logo.png
      MyServlet\ (project content directory for MyServlet)
	 .project
         src\
            main.java
         bin\
            main.class

The platform has a special .metadata directory for holding platform internal information. The .metadata directory of a workspace is considered to be a "black box." Important information about the workspace structure, such as a project's references or a resource's properties, is stored in the metadata portion of the workspace and should only be accessed by tools through the platform API.  These files should never be edited or manipulated using generic file system API.

In addition, each project has its own .project file, where metadata about the project is kept.  This file is basically an on-disk equivalent of the information found in a project's IProjectDescription.  

Apart from the .metadata directory and the .project files, the folders and files in the workspace directory are fair game for other tools.  The files and folders can be manipulated by non-integrated tools, such as text editors and file system utilities.  The only issue is that the user must be careful when editing these files both in the workbench and externally.  (This is no different than when a user edits a file using two independent stand-alone tools.)  The workbench provides refresh operations to reconcile the workspace view of resources with the actual state in the file system and there is an option to periodically refresh the workspace based on the state of the file system.

Our sample tree in code

The resource API allows us to manipulate this resource tree in code. Here we will look at some code snippets for a quick taste of the resource API. The resource API is defined in a series of interfaces in org.eclipse.core.resources. There are interfaces for all of the resource types, such as IProject, IFolder, and IFile. Extensive common protocol is defined in IResource. We also make use of the org.eclipse.core.runtime interface IPath, which represents segmented paths such as resource or file system paths.

Manipulating resources is very similar to manipulating files using java.io.File.  The API is based on handles.  When you use API like getProject or getFolder, you are returned a handle to the resource.   There is no guarantee or requirement that the resource itself exists until you try to do something with the handle.  If you expect a resource to exist, you can use exists method to ensure this is the case.  

To navigate the workspace from a plug-in, we must first obtain the IWorkspaceRoot, which represents the top of the resource hierarchy in the workspace.

   IWorkspaceRoot myWorkspaceRoot = ResourcesPlugin.getWorkspace().getRoot();

Once we have a workspace root, we can access the projects in the workspace.

   IProject myWebProject = myWorkspaceRoot.getProject("MyWeb");
   // open if necessary
   if (myWebProject.exists() && !myWebProject.isOpen())
      myWebProject.open(null);

Before we can manipulate a project, we must open it. Opening the project reads the project's structure from disk and creates the in-memory object representation of the project's resource tree. Opening a project is an explicit operation since each open project consumes memory to represent the resource tree internally and open projects participate in various resource lifecycle events (such as building) which can be lengthy.  In general, closed projects cannot be accessed and will appear empty even though the resources are still present in the file system.

You'll notice that many of these resource examples pass a null parameter when manipulating resources. Many resource operations are potentially heavyweight enough to warrant progress reporting and user cancellation. If your code has a user interface, you will typically pass an IProgressMonitor, which allows the resources plug-in to report progress as the resource is manipulated and allows the user to cancel the operation if desired.  For now, we simply pass null, indicating no progress monitor.

Once we have an open project, we can access its folders and files, as well as create additional ones.  In the following example we create a file resource from the contents of a file located outside of our workspace.

   IFolder imagesFolder = myWebProject.getFolder("images");
   if (imagesFolder.exists()) {
      // create a new file
      IFile newLogo = imagesFolder.getFile("newLogo.png");
      FileInputStream fileStream = new FileInputStream(
         "c:/MyOtherData/newLogo.png");
      newLogo.create(fileStream, false, null);
      // create closes the file stream, so no worries.   
   }

In the example above, the first line obtains a handle to the images folder.  We must check that the folder exists before we can do anything interesting with it.  Likewise, when we get the file newLogo, the handle does not represent a real file until we create the file in the last line.  In this example, we create the file by populating it with the contents of logo.png.

The next snippet is similar to the previous one, except that it copies the newLogo file from the original logo rather than create a new one from its contents.

   IFile logo = imagesFolder.getFile("logo.png");
   if (logo.exists()) {
      IPath newLogoPath = new Path("newLogo.png");
      logo.copy(newLogoPath, false, null);
      IFile newLogo = imagesFolder.getFile("newLogo.png");
      ...
   }

Finally, we'll create another images folder and move the newly created file to it. We rename the file as a side effect of moving it.

   ...
   IFolder newImagesFolder = myWebProject.getFolder("newimages");
   newImagesFolder.create(false, true, null);
   IPath renamedPath = newImagesFolder.getFullPath().append("renamedLogo.png");
   newLogo.move(renamedPath, false, null);
   IFile renamedLogo = newImagesFolder.getFile("renamedLogo.png");

Many of the resource API methods include a force boolean flag which specifies whether resources that are out of synch with the corresponding files in the file system will be updated anyway.  See IResource for more information.  You can also use IResource.isSynchronized to determine whether a particular resource is in synch with the file system.

Mapping resources to disk locations

In the sample resource tree, we've assumed that all of the project content directories are in the workspace directory underneath the platform root directory (C:\MySDK\workspace). This is the default configuration for projects.  However, a project's content directory can be mapped to any arbitrary directory in some backing file system, perhaps even on a different machine.

The ability to map the location of a project independent of other projects allows the user to store the contents of a project in a place that makes sense for the project and the project team. A project's content directory should be considered "out in the open." This means that users can create, modify, and delete resources by using the workbench and plug-ins, or by directly using file system based tools and editors.

Resource path names are not complete file system paths. Resource paths are always based on the project's location (usually the workspace directory).  To obtain the full file system path to a resource, you must query its location using IResource.getLocationURI.  Howevever, you cannot use IProjectDescription.setLocation to change its location, because that method is just a simple setter for a data structure.  

Conversely, if you want to get the corresponding resource object given a file system path, you can use IWorkspaceRoot.findFilesForLocationURI or IWorkspaceRoot.findContainersForLocationURI.

Resource API and the file system

When we use the resources API to modify our workspace's resource tree, the files are changed in the file system in addition to updating our resource objects. What about changes to resource files that happen outside of the platform's API?

External changes to resources will not be reflected in the workspace and resource objects until detected by the resources plug-in. The resources plug-in also uses a mechanism appropriate for each particular native operating system for discovering external changes made in the file system. In addition, clients can use resource API to reconcile workspace and resource objects with the local file system quietly and without user intervention. The user can also explicitly force a refresh in the resource navigator view of the workbench.

Many of the methods in the resource APIs include a force parameter which specifies how resources that are out of sync with the file system should be handled. The API Reference for each method provides specific information about this parameter. Additional methods in the API allow programmatic control of file system refresh, such as IResource.refreshLocal(int depth, IProgressMonitor monitor). See IResource for information on correct usage and costs.

Plug-ins that wish to supply their own mechanism for periodically refreshing the workspace based on the state of the external file system may do so using the org.eclipse.core.resources.refreshProviders extension point. See Refresh providers for more information.