/*******************************************************************************
* Copyright (c) 2000, 2010 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Francis Lynch (Wind River) - [301563] Save and load tree snapshots
*******************************************************************************/
package org.eclipse.core.internal.resources;
import java.io.*;
import java.net.URI;
import org.eclipse.core.filesystem.URIUtil;
import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
import org.eclipse.core.internal.localstore.SafeChunkyOutputStream;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
public class LocalMetaArea implements ICoreConstants {
/* package */static final String F_BACKUP_FILE_EXTENSION= ".bak"; //$NON-NLS-1$
/* package */static final String F_DESCRIPTION= ".workspace"; //$NON-NLS-1$
/* package */static final String F_HISTORY_STORE= ".history"; //$NON-NLS-1$
/* package */static final String F_MARKERS= ".markers"; //$NON-NLS-1$
/* package */static final String F_OLD_PROJECT= ".prj"; //$NON-NLS-1$
/* package */static final String F_PROJECT_LOCATION= ".location"; //$NON-NLS-1$
/* package */static final String F_PROJECTS= ".projects"; //$NON-NLS-1$
/* package */static final String F_PROPERTIES= ".properties"; //$NON-NLS-1$
/* package */static final String F_REFRESH= ".refresh"; //$NON-NLS-1$
/* package */static final String F_ROOT= ".root"; //$NON-NLS-1$
/* package */static final String F_SAFE_TABLE= ".safetable"; //$NON-NLS-1$
/* package */static final String F_SNAP= ".snap"; //$NON-NLS-1$
/* package */static final String F_SNAP_EXTENSION= "snap"; //$NON-NLS-1$
/* package */static final String F_SYNCINFO= ".syncinfo"; //$NON-NLS-1$
/* package */static final String F_TREE= ".tree"; //$NON-NLS-1$
/* package */static final String URI_PREFIX= "URI//"; //$NON-NLS-1$
/* package */static final String F_METADATA= ".metadata"; //$NON-NLS-1$
protected final IPath metaAreaLocation;
/**
* The project location is just stored as an optimization, to avoid recomputing it.
*/
protected final IPath projectMetaLocation;
public LocalMetaArea() {
super();
metaAreaLocation= ResourcesPlugin.getPlugin().getStateLocation();
projectMetaLocation= metaAreaLocation.append(F_PROJECTS);
}
/**
* For backwards compatibility, if there is a project at the old project description location,
* delete it.
*/
public void clearOldDescription(IProject target) {
Workspace.clear(getOldDescriptionLocationFor(target).toFile());
}
/**
* Delete the refresh snapshot once it has been used to open a new project.
*/
public void clearRefresh(IProject target) {
Workspace.clear(getRefreshLocationFor(target).toFile());
}
public void create(IProject target) {
java.io.File file= locationFor(target).toFile();
//make sure area is empty
Workspace.clear(file);
file.mkdirs();
}
/**
* Creates the meta area root directory.
*/
public synchronized void createMetaArea() throws CoreException {
java.io.File workspaceLocation= metaAreaLocation.toFile();
Workspace.clear(workspaceLocation);
if (!workspaceLocation.mkdirs()) {
String message= NLS.bind(Messages.resources_writeWorkspaceMeta, workspaceLocation);
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, null);
}
}
/**
* The project is being deleted. Delete all meta-data associated with the project.
*/
public void delete(IProject target) throws CoreException {
IPath path= locationFor(target);
if (!Workspace.clear(path.toFile()) && path.toFile().exists()) {
String message= NLS.bind(Messages.resources_deleteMeta, target.getFullPath());
throw new ResourceException(IResourceStatus.FAILED_DELETE_METADATA, target.getFullPath(), message, null);
}
}
public IPath getBackupLocationFor(IPath file) {
return file.removeLastSegments(1).append(file.lastSegment() + F_BACKUP_FILE_EXTENSION);
}
public IPath getHistoryStoreLocation() {
return metaAreaLocation.append(F_HISTORY_STORE);
}
/**
* Returns the local file system location which contains the META data for the resources plugin
* (i.e., the entire workspace).
*/
public IPath getLocation() {
return metaAreaLocation;
}
/**
* Returns the path of the file in which to save markers for the given resource. Should only be
* called for the workspace root and projects.
*/
public IPath getMarkersLocationFor(IResource resource) {
Assert.isNotNull(resource);
Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT);
return locationFor(resource).append(F_MARKERS);
}
/**
* Returns the path of the file in which to snapshot markers for the given resource. Should only
* be called for the workspace root and projects.
*/
public IPath getMarkersSnapshotLocationFor(IResource resource) {
return getMarkersLocationFor(resource).addFileExtension(F_SNAP_EXTENSION);
}
/**
* The project description file is the only metadata file stored outside the metadata area. It
* is stored as a file directly under the project location. For backwards compatibility, we also
* have to check for a project file at the old location in the metadata area.
*/
public IPath getOldDescriptionLocationFor(IProject target) {
return locationFor(target).append(F_OLD_PROJECT);
}
public IPath getOldWorkspaceDescriptionLocation() {
return metaAreaLocation.append(F_DESCRIPTION);
}
public IPath getPropertyStoreLocation(IResource resource) {
int type= resource.getType();
Assert.isTrue(type != IResource.FILE && type != IResource.FOLDER);
return locationFor(resource).append(F_PROPERTIES);
}
/**
* Returns the path of the file in which to save the refresh snapshot for the given project.
*/
public IPath getRefreshLocationFor(IProject project) {
Assert.isNotNull(project);
return locationFor(project).append(F_REFRESH);
}
public IPath getSafeTableLocationFor(String pluginId) {
IPath prefix= metaAreaLocation.append(F_SAFE_TABLE);
// if the plugin is the resources plugin, we return the master table
// location
if (pluginId.equals(ResourcesPlugin.PI_RESOURCES))
return prefix.append(pluginId); // master table
int saveNumber= getWorkspace().getSaveManager().getSaveNumber(pluginId);
return prefix.append(pluginId + "." + saveNumber); //$NON-NLS-1$
}
public IPath getSnapshotLocationFor(IResource resource) {
return metaAreaLocation.append(F_SNAP);
}
/**
* Returns the path of the file in which to save the sync information for the given resource.
* Should only be called for the workspace root and projects.
*/
public IPath getSyncInfoLocationFor(IResource resource) {
Assert.isNotNull(resource);
Assert.isLegal(resource.getType() == IResource.ROOT || resource.getType() == IResource.PROJECT);
return locationFor(resource).append(F_SYNCINFO);
}
/**
* Returns the path of the file in which to snapshot the sync information for the given
* resource. Should only be called for the workspace root and projects.
*/
public IPath getSyncInfoSnapshotLocationFor(IResource resource) {
return getSyncInfoLocationFor(resource).addFileExtension(F_SNAP_EXTENSION);
}
/**
* Returns the local file system location of the tree file for the given resource. This file
* does not follow the same save number as its plug-in. So, the number here is called
* "sequence number" and not "save number" to avoid confusion.
*/
public IPath getTreeLocationFor(IResource target, boolean updateSequenceNumber) {
IPath key= target.getFullPath().append(F_TREE);
String sequenceNumber= getWorkspace().getSaveManager().getMasterTable().getProperty(key.toString());
if (sequenceNumber == null)
sequenceNumber= "0"; //$NON-NLS-1$
if (updateSequenceNumber) {
int n= new Integer(sequenceNumber).intValue() + 1;
n= n < 0 ? 1 : n;
sequenceNumber= new Integer(n).toString();
getWorkspace().getSaveManager().getMasterTable().setProperty(key.toString(), new Integer(sequenceNumber).toString());
}
return locationFor(target).append(sequenceNumber + F_TREE);
}
public IPath getWorkingLocation(IResource resource, String id) {
return locationFor(resource).append(id);
}
protected Workspace getWorkspace() {
return (Workspace)ResourcesPlugin.getWorkspace();
}
public boolean hasSavedProject(IProject project) {
//if there is a location file, then the project exists
return getOldDescriptionLocationFor(project).toFile().exists() || locationFor(project).append(F_PROJECT_LOCATION).toFile().exists();
}
public boolean hasSavedWorkspace() {
return metaAreaLocation.toFile().exists() || getBackupLocationFor(metaAreaLocation).toFile().exists();
}
/**
* Returns the local file system location in which the meta data for the resource with the given
* path is stored.
*/
public IPath locationFor(IPath resourcePath) {
if (Path.ROOT.equals(resourcePath))
return metaAreaLocation.append(F_ROOT);
return projectMetaLocation.append(resourcePath.segment(0));
}
/**
* Returns the local file system location in which the meta data for the given resource is
* stored.
*/
public IPath locationFor(IResource resource) {
if (resource.getType() == IResource.ROOT)
return metaAreaLocation.append(F_ROOT);
return projectMetaLocation.append(resource.getProject().getName());
}
/**
* Reads and returns the project description for the given project. Returns null if there was no
* project description file on disk. Throws an exception if there was any failure to read the
* project.
*/
public ProjectDescription readOldDescription(IProject project) throws CoreException {
IPath path= getOldDescriptionLocationFor(project);
if (!path.toFile().exists())
return null;
IPath tempPath= getBackupLocationFor(path);
ProjectDescription description= null;
try {
description= new ProjectDescriptionReader(project).read(path, tempPath);
} catch (IOException e) {
String msg= NLS.bind(Messages.resources_readMeta, project.getName());
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, e);
}
if (description == null) {
String msg= NLS.bind(Messages.resources_readMeta, project.getName());
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, project.getFullPath(), msg, null);
}
return description;
}
/**
* Provides backward compatibility with existing workspaces based on descriptions.
*/
public WorkspaceDescription readOldWorkspace() {
IPath path= getOldWorkspaceDescriptionLocation();
IPath tempPath= getBackupLocationFor(path);
try {
WorkspaceDescription oldDescription= (WorkspaceDescription)new WorkspaceDescriptionReader().read(path, tempPath);
// if one of those files exist, get rid of them
Workspace.clear(path.toFile());
Workspace.clear(tempPath.toFile());
return oldDescription;
} catch (IOException e) {
return null;
}
}
/**
* Returns the portions of the project description that are private, and adds them to the
* supplied project description. In particular, the project location and the project's dynamic
* references are stored here. The project location will be set to <code>null</code> if the
* default location should be used. In the case of failure, log the exception and return
* silently, thus reverting to using the default location and no dynamic references. The current
* format of the location file is: UTF - project location int - number of dynamic project
* references UTF - project reference 1 ... repeat for remaining references
*/
public void readPrivateDescription(IProject target, IProjectDescription description) {
IPath locationFile= locationFor(target).append(F_PROJECT_LOCATION);
java.io.File file= locationFile.toFile();
if (!file.exists()) {
locationFile= getBackupLocationFor(locationFile);
file= locationFile.toFile();
if (!file.exists())
return;
}
try {
SafeChunkyInputStream input= new SafeChunkyInputStream(file, 500);
DataInputStream dataIn= new DataInputStream(input);
try {
try {
String location= dataIn.readUTF();
if (location.length() > 0) {
//location format < 3.2 was a local file system OS path
//location format >= 3.2 is: URI_PREFIX + uri.toString()
if (location.startsWith(URI_PREFIX))
description.setLocationURI(URI.create(location.substring(URI_PREFIX.length())));
else
description.setLocationURI(URIUtil.toURI(Path.fromOSString(location)));
}
} catch (Exception e) {
//don't allow failure to read the location to propagate
String msg= NLS.bind(Messages.resources_exReadProjectLocation, target.getName());
Policy.log(new ResourceStatus(IStatus.ERROR, IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), msg, e));
}
//try to read the dynamic references - will fail for old location files
int numRefs= dataIn.readInt();
IProject[] references= new IProject[numRefs];
IWorkspaceRoot root= getWorkspace().getRoot();
for (int i= 0; i < numRefs; i++)
references[i]= root.getProject(dataIn.readUTF());
description.setDynamicReferences(references);
} finally {
dataIn.close();
}
} catch (IOException e) {
//ignore - this is an old location file or an exception occurred
// closing the stream
}
}
/**
* Writes the workspace description to the local meta area. This method is synchronized to
* prevent multiple current write attempts.
*
* @deprecated should not be called any more - workspace preferences are now maintained in the
* plug-in's preferences
*/
public synchronized void write(WorkspaceDescription description) throws CoreException {
IPath path= getOldWorkspaceDescriptionLocation();
path.toFile().getParentFile().mkdirs();
IPath tempPath= getBackupLocationFor(path);
try {
new ModelObjectWriter().write(description, path, tempPath);
} catch (IOException e) {
String message= NLS.bind(Messages.resources_writeWorkspaceMeta, path);
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, null, message, e);
}
}
/**
* Write the private project description information, including the location and the dynamic
* project references. See <tt>readPrivateDescription</tt> for details on the file format.
*/
public void writePrivateDescription(IProject target) throws CoreException {
IPath location= locationFor(target).append(F_PROJECT_LOCATION);
java.io.File file= location.toFile();
//delete any old location file
Workspace.clear(file);
//don't write anything if there is no interesting private metadata
ProjectDescription desc= ((Project)target).internalGetDescription();
if (desc == null)
return;
final URI projectLocation= desc.getLocationURI();
final IProject[] references= desc.getDynamicReferences(false);
final int numRefs= references.length;
if (projectLocation == null && numRefs == 0)
return;
//write the private metadata file
try {
SafeChunkyOutputStream output= new SafeChunkyOutputStream(file);
DataOutputStream dataOut= new DataOutputStream(output);
try {
if (projectLocation == null)
dataOut.writeUTF(""); //$NON-NLS-1$
else
dataOut.writeUTF(URI_PREFIX + projectLocation.toString());
dataOut.writeInt(numRefs);
for (int i= 0; i < numRefs; i++)
dataOut.writeUTF(references[i].getName());
output.succeed();
} finally {
dataOut.close();
}
} catch (IOException e) {
String message= NLS.bind(Messages.resources_exSaveProjectLocation, target.getName());
throw new ResourceException(IResourceStatus.INTERNAL_ERROR, null, message, e);
}
}
}