blob: 19e3b43350cb21ccb4a41c07510e90aab015d6a9 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427
* Mickael Istria (Red Hat Inc.) - Bug 488937
*******************************************************************************/
package org.eclipse.core.internal.properties;
import java.io.File;
import java.util.*;
import org.eclipse.core.internal.localstore.Bucket;
import org.eclipse.core.internal.localstore.Bucket.Entry;
import org.eclipse.core.internal.localstore.BucketTree;
import org.eclipse.core.internal.properties.PropertyBucket.PropertyEntry;
import org.eclipse.core.internal.resources.*;
import org.eclipse.core.internal.utils.Messages;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.*;
import org.eclipse.osgi.util.NLS;
/**
* @see org.eclipse.core.internal.properties.IPropertyManager
*/
public class PropertyManager2 implements IPropertyManager {
private static final int MAX_VALUE_SIZE = 2 * 1024;
class PropertyCopyVisitor extends Bucket.Visitor {
private List<PropertyEntry> changes = new ArrayList<>();
private IPath destination;
private IPath source;
public PropertyCopyVisitor(IPath source, IPath destination) {
this.source = source;
this.destination = destination;
}
@Override
public void afterSaving(Bucket bucket) throws CoreException {
saveChanges((PropertyBucket) bucket);
changes.clear();
}
private void saveChanges(PropertyBucket bucket) throws CoreException {
if (changes.isEmpty())
return;
// make effective all changes collected
Iterator<PropertyEntry> i = changes.iterator();
PropertyEntry entry = i.next();
tree.loadBucketFor(entry.getPath());
bucket.setProperties(entry);
while (i.hasNext())
bucket.setProperties(i.next());
bucket.save();
}
@Override
public int visit(Entry entry) {
PropertyEntry sourceEntry = (PropertyEntry) entry;
IPath destinationPath = destination.append(sourceEntry.getPath().removeFirstSegments(source.segmentCount()));
PropertyEntry destinationEntry = new PropertyEntry(destinationPath, sourceEntry);
changes.add(destinationEntry);
return CONTINUE;
}
}
BucketTree tree;
public PropertyManager2(Workspace workspace) {
this.tree = new BucketTree(workspace, new PropertyBucket());
}
@Override
public void closePropertyStore(IResource target) throws CoreException {
// ensure any uncommitted are written to disk
tree.getCurrent().save();
// flush in-memory state to avoid confusion if another project is later
// created with the same name
tree.getCurrent().flush();
}
@Override
public synchronized void copy(IResource source, IResource destination, int depth) throws CoreException {
copyProperties(source.getFullPath(), destination.getFullPath(), depth);
}
/**
* Copies all properties from the source path to the target path, to the given depth.
*/
private void copyProperties(final IPath source, final IPath destination, int depth) throws CoreException {
Assert.isLegal(source.segmentCount() > 0);
Assert.isLegal(destination.segmentCount() > 0);
Assert.isLegal(source.segmentCount() > 1 || destination.segmentCount() == 1);
// copy history by visiting the source tree
PropertyCopyVisitor copyVisitor = new PropertyCopyVisitor(source, destination);
tree.accept(copyVisitor, source, BucketTree.DEPTH_INFINITE);
}
@Override
public synchronized void deleteProperties(IResource target, int depth) throws CoreException {
tree.accept(new PropertyBucket.Visitor() {
@Override
public int visit(Entry entry) {
entry.delete();
return CONTINUE;
}
}, target.getFullPath(), depth == IResource.DEPTH_INFINITE ? BucketTree.DEPTH_INFINITE : depth);
}
@Override
public void deleteResource(IResource target) throws CoreException {
deleteProperties(target, IResource.DEPTH_INFINITE);
}
@Override
public synchronized Map<QualifiedName, String> getProperties(IResource target) throws CoreException {
final Map<QualifiedName, String> result = new HashMap<>();
tree.accept(new PropertyBucket.Visitor() {
@Override
public int visit(Entry entry) {
PropertyEntry propertyEntry = (PropertyEntry) entry;
int propertyCount = propertyEntry.getOccurrences();
for (int i = 0; i < propertyCount; i++)
result.put(propertyEntry.getPropertyName(i), propertyEntry.getPropertyValue(i));
return CONTINUE;
}
}, target.getFullPath(), BucketTree.DEPTH_ZERO);
return result;
}
@Override
public synchronized String getProperty(IResource target, QualifiedName name) throws CoreException {
if (name.getQualifier() == null) {
String message = Messages.properties_qualifierIsNull;
throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, target.getFullPath(), message, null);
}
IPath resourcePath = target.getFullPath();
PropertyBucket current = (PropertyBucket) tree.getCurrent();
tree.loadBucketFor(resourcePath);
return current.getProperty(resourcePath, name);
}
public BucketTree getTree() {
return tree;
}
public File getVersionFile() {
return tree.getVersionFile();
}
@Override
public synchronized void setProperty(IResource target, QualifiedName name, String value) throws CoreException {
//resource may have been deleted concurrently
//must check for existence within synchronized method
Resource resource = (Resource) target;
ResourceInfo info = resource.getResourceInfo(false, false);
int flags = resource.getFlags(info);
resource.checkAccessible(flags);
// enforce the limit stated by the spec
if (value != null && value.length() > MAX_VALUE_SIZE) {
String message = NLS.bind(Messages.properties_valueTooLong, new Object[] {name.getQualifier(), name.getLocalName(), Integer.toString(MAX_VALUE_SIZE)});
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), message, null);
}
if (name.getQualifier() == null) {
String message = Messages.properties_qualifierIsNull;
throw new ResourceException(IResourceStatus.FAILED_WRITE_METADATA, target.getFullPath(), message, null);
}
IPath resourcePath = target.getFullPath();
tree.loadBucketFor(resourcePath);
PropertyBucket current = (PropertyBucket) tree.getCurrent();
current.setProperty(resourcePath, name, value);
current.save();
}
@Override
public void shutdown(IProgressMonitor monitor) throws CoreException {
tree.close();
}
@Override
public void startup(IProgressMonitor monitor) {
// nothing to do
}
}