/*******************************************************************************
* Copyright (c) 2000, 2006 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
*******************************************************************************/
package org.eclipse.core.internal.resources;
import java.io.*;
import java.util.*;
import org.eclipse.core.internal.localstore.SafeChunkyInputStream;
import org.eclipse.core.internal.localstore.SafeFileInputStream;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.internal.watson.IPathRequestor;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
//
public class Synchronizer implements ISynchronizer {
protected Workspace workspace;
protected SyncInfoWriter writer;
// Registry of sync partners. Set of qualified names.
protected Set registry= new HashSet(5);
public Synchronizer(Workspace workspace) {
super();
this.workspace= workspace;
this.writer= new SyncInfoWriter(workspace, this);
}
/**
* @see ISynchronizer#accept(QualifiedName, IResource, IResourceVisitor, int)
*/
public void accept(QualifiedName partner, IResource resource, IResourceVisitor visitor, int depth) throws CoreException {
Assert.isLegal(partner != null);
Assert.isLegal(resource != null);
Assert.isLegal(visitor != null);
// if we don't have sync info for the given identifier, then skip it
if (getSyncInfo(partner, resource) != null) {
// visit the resource and if the visitor says to stop the recursion then return
if (!visitor.visit(resource))
return;
}
// adjust depth if necessary
if (depth == IResource.DEPTH_ZERO || resource.getType() == IResource.FILE)
return;
if (depth == IResource.DEPTH_ONE)
depth= IResource.DEPTH_ZERO;
// otherwise recurse over the children
IResource[] children= ((IContainer)resource).members();
for (int i= 0; i < children.length; i++)
accept(partner, children[i], visitor, depth);
}
/**
* @see ISynchronizer#add(QualifiedName)
*/
public void add(QualifiedName partner) {
Assert.isLegal(partner != null);
registry.add(partner);
}
/**
* @see ISynchronizer#flushSyncInfo(QualifiedName, IResource, int)
*/
public void flushSyncInfo(final QualifiedName partner, final IResource root, final int depth) throws CoreException {
Assert.isLegal(partner != null);
Assert.isLegal(root != null);
IWorkspaceRunnable body= new IWorkspaceRunnable() {
public void run(IProgressMonitor monitor) throws CoreException {
IResourceVisitor visitor= new IResourceVisitor() {
public boolean visit(IResource resource) throws CoreException {
//only need to flush sync info if there is sync info
if (getSyncInfo(partner, resource) != null)
setSyncInfo(partner, resource, null);
return true;
}
};
root.accept(visitor, depth, true);
}
};
workspace.run(body, root, IResource.NONE, null);
}
/**
* @see ISynchronizer#getPartners()
*/
public QualifiedName[] getPartners() {
return (QualifiedName[])registry.toArray(new QualifiedName[registry.size()]);
}
/**
* For use by the serialization code.
*/
protected Set getRegistry() {
return registry;
}
/**
* @see ISynchronizer#getSyncInfo(QualifiedName, IResource)
*/
public byte[] getSyncInfo(QualifiedName partner, IResource resource) throws CoreException {
Assert.isLegal(partner != null);
Assert.isLegal(resource != null);
if (!isRegistered(partner)) {
String message= NLS.bind(Messages.synchronizer_partnerNotRegistered, partner);
throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message));
}
// namespace check, if the resource doesn't exist then return null
ResourceInfo info= workspace.getResourceInfo(resource.getFullPath(), true, false);
return (info == null) ? null : info.getSyncInfo(partner, true);
}
protected boolean isRegistered(QualifiedName partner) {
Assert.isLegal(partner != null);
return registry.contains(partner);
}
/**
* @see #savePartners(DataOutputStream)
*/
public void readPartners(DataInputStream input) throws CoreException {
SyncInfoReader reader= new SyncInfoReader(workspace, this);
reader.readPartners(input);
}
public void restore(IResource resource, IProgressMonitor monitor) throws CoreException {
// first restore from the last save and then apply any snapshots
restoreFromSave(resource);
restoreFromSnap(resource);
}
protected void restoreFromSave(IResource resource) throws CoreException {
IPath sourceLocation= workspace.getMetaArea().getSyncInfoLocationFor(resource);
IPath tempLocation= workspace.getMetaArea().getBackupLocationFor(sourceLocation);
if (!sourceLocation.toFile().exists() && !tempLocation.toFile().exists())
return;
try {
DataInputStream input= new DataInputStream(new SafeFileInputStream(sourceLocation.toOSString(), tempLocation.toOSString()));
try {
SyncInfoReader reader= new SyncInfoReader(workspace, this);
reader.readSyncInfo(input);
} finally {
input.close();
}
} catch (Exception e) {
//don't let runtime exceptions such as ArrayIndexOutOfBounds prevent startup
String msg= NLS.bind(Messages.resources_readMeta, sourceLocation);
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e);
}
}
protected void restoreFromSnap(IResource resource) {
IPath sourceLocation= workspace.getMetaArea().getSyncInfoSnapshotLocationFor(resource);
if (!sourceLocation.toFile().exists())
return;
try {
DataInputStream input= new DataInputStream(new SafeChunkyInputStream(sourceLocation.toFile()));
try {
SyncInfoSnapReader reader= new SyncInfoSnapReader(workspace, this);
while (true)
reader.readSyncInfo(input);
} catch (EOFException eof) {
// ignore end of file -- proceed with what we successfully read
} finally {
input.close();
}
} catch (Exception e) {
// only log the exception, we should not fail restoring the snapshot
String msg= NLS.bind(Messages.resources_readMeta, sourceLocation);
Policy.log(new ResourceStatus(IResourceStatus.FAILED_READ_METADATA, sourceLocation, msg, e));
}
}
/**
* @see ISynchronizer#remove(QualifiedName)
*/
public void remove(QualifiedName partner) {
Assert.isLegal(partner != null);
if (isRegistered(partner)) {
// remove all sync info for this partner
try {
flushSyncInfo(partner, workspace.getRoot(), IResource.DEPTH_INFINITE);
registry.remove(partner);
} catch (CoreException e) {
// XXX: flush needs to be more resilient and not throw exceptions all the time
Policy.log(e);
}
}
}
public void savePartners(DataOutputStream output) throws IOException {
writer.savePartners(output);
}
public void saveSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output, List writtenPartners) throws IOException {
writer.saveSyncInfo(info, requestor, output, writtenPartners);
}
protected void setRegistry(Set registry) {
this.registry= registry;
}
/**
* @see ISynchronizer#setSyncInfo(QualifiedName, IResource, byte[])
*/
public void setSyncInfo(QualifiedName partner, IResource resource, byte[] info) throws CoreException {
Assert.isLegal(partner != null);
Assert.isLegal(resource != null);
try {
workspace.prepareOperation(resource, null);
workspace.beginOperation(true);
if (!isRegistered(partner)) {
String message= NLS.bind(Messages.synchronizer_partnerNotRegistered, partner);
throw new ResourceException(new ResourceStatus(IResourceStatus.PARTNER_NOT_REGISTERED, message));
}
// we do not store sync info on the workspace root
if (resource.getType() == IResource.ROOT)
return;
// if the resource doesn't yet exist then create a phantom so we can set the sync info on it
Resource target= (Resource)resource;
ResourceInfo resourceInfo= workspace.getResourceInfo(target.getFullPath(), true, false);
int flags= target.getFlags(resourceInfo);
if (!target.exists(flags, false)) {
if (info == null)
return;
//ensure it is possible to create this resource
target.checkValidPath(target.getFullPath(), target.getType(), false);
Container parent= (Container)target.getParent();
parent.checkAccessible(parent.getFlags(parent.getResourceInfo(true, false)));
workspace.createResource(target, true);
}
resourceInfo= target.getResourceInfo(true, true);
resourceInfo.setSyncInfo(partner, info);
resourceInfo.incrementSyncInfoGenerationCount();
resourceInfo.set(ICoreConstants.M_SYNCINFO_SNAP_DIRTY);
flags= target.getFlags(resourceInfo);
if (target.isPhantom(flags) && resourceInfo.getSyncInfo(false) == null) {
MultiStatus status= new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INTERNAL_ERROR, Messages.resources_deleteProblem, null);
((Resource)resource).deleteResource(false, status);
if (!status.isOK())
throw new ResourceException(status);
}
} finally {
workspace.endOperation(resource, false, null);
}
}
public void snapSyncInfo(ResourceInfo info, IPathRequestor requestor, DataOutputStream output) throws IOException {
writer.snapSyncInfo(info, requestor, output);
}
}