blob: 6230dbabd505cb5aed7617d0f61af36b459b014b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2002 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Common Public License v0.5
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/cpl-v05.html
*
* Contributors:
* IBM - Initial API and implementation
******************************************************************************/
package org.eclipse.core.internal.properties;
import java.util.*;
import org.eclipse.core.internal.indexing.IndexCursor;
import org.eclipse.core.internal.indexing.ObjectID;
import org.eclipse.core.internal.resources.ResourceException;
import org.eclipse.core.internal.utils.Policy;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.runtime.*;
/**
*
*/
public class PropertyStore {
// The indexed store will maintain the properties
protected IndexedStoreWrapper store = null;
// Add directives
public static final int CREATE = 0; // must not exist
public static final int UPDATE = 1; // must exist
public static final int SET_UPDATE = 2; // create if doesn't exist, update if exists
public static final int SET_SKIP = 3; // create if doesn't exist, don't update if exists
// Remove directives
public static final int IGNORE_MISSING = 0;
public static final int FAIL_MISSING = 1;
public PropertyStore(IPath location) {
store = new IndexedStoreWrapper(location);
}
protected boolean basicExists(StoreKey searchKey) throws CoreException {
byte[] searchBytes = searchKey.toBytes();
IndexCursor cursor = store.getCursor();
try {
cursor.find(searchBytes);
boolean exists = cursor.keyEquals(searchBytes);
cursor.close();
return exists;
} catch (Exception e) {
String message = Policy.bind("properties.couldNotReadProp", searchKey.getQualifier(), searchKey.getLocalName()); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, searchKey.getResourceName().getPath(), message, e);
}
}
/**
* The caller is responsible for ensuring that this will not produce
* duplicate keys in the index.
*/
protected void basicInsert(StoreKey key, String value) throws CoreException {
try {
ObjectID valueID = store.createObject(value);
store.getIndex().insert(key.toBytes(), valueID);
} catch (Exception e) {
String message = Policy.bind("properties.couldNotWriteProp", key.getQualifier(), key.getLocalName()); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, key.getResourceName().getPath(), message, e);
}
}
protected boolean basicRemove(ResourceName resourceName, QualifiedName propertyName) throws CoreException {
StoreKey key = new StoreKey(resourceName, propertyName);
byte[] keyBytes = key.toBytes();
boolean wasFound = false;
IndexCursor cursor = store.getCursor();
try {
cursor.find(keyBytes);
if (cursor.keyEquals(keyBytes)) {
wasFound = true;
ObjectID valueID = cursor.getValueAsObjectID();
store.removeObject(valueID);
cursor.remove();
}
cursor.close();
} catch (Exception e) {
String message = Policy.bind("properties.couldNotDeleteProp", key.getQualifier(), key.getLocalName()); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.FAILED_DELETE_LOCAL, resourceName.getPath(), message, e);
}
return wasFound;
}
protected void basicUpdate(StoreKey key, String value) throws CoreException {
byte[] keyBytes = key.toBytes();
IndexCursor cursor = store.getCursor();
try {
cursor.find(keyBytes);
if (cursor.keyEquals(keyBytes)) {
ObjectID oldID = cursor.getValueAsObjectID();
store.removeObject(oldID);
ObjectID newValueId = store.createObject(value);
cursor.updateValue(newValueId);
}
cursor.close();
} catch (Exception e) {
String message = Policy.bind("properties.couldNotWriteProp", key.getQualifier(), key.getLocalName()); //$NON-NLS-1$
throw new ResourceException(IResourceStatus.FAILED_WRITE_LOCAL, key.getResourceName().getPath(), message, e);
}
}
protected synchronized void commonSet(ResourceName resourceName, StoredProperty[] properties, int depth, int setMode, QueryResults failures) throws CoreException {
if (depth == IResource.DEPTH_ZERO) {
for (int i = 0; i < properties.length; i++) {
StoredProperty property = properties[i];
StoreKey key = new StoreKey(resourceName, property.getName());
boolean exists = basicExists(key);
if ((exists && (setMode == CREATE)) || (!exists && (setMode == UPDATE)))
failures.add(resourceName, property);
else
if (exists && (setMode != SET_SKIP))
basicUpdate(key, property.getStringValue());
else
basicInsert(key, property.getStringValue());
}
} else {
Enumeration resourceNamesEnum = deepResourceNames(resourceName);
while (resourceNamesEnum.hasMoreElements())
commonSet((ResourceName) resourceNamesEnum.nextElement(), properties, IResource.DEPTH_ZERO, setMode, failures);
}
}
/**
* Returns the names of all resources that are rooted at the given resource.
* <p>
* Answers an <code>Enumeration</code> of <code>IResourceName</code>.
* The enumerator will include (at least) <code>resourceName</code> if it
* exists. If <code>resourceName</code> does not exist returns an empty
* enumerator.
*
* @see IResourceName
* @param resourceName the name of the top most resource to match.
* @return an enumeration of matching resource names.
*/
public Enumeration deepResourceNames(ResourceName resourceName) throws CoreException {
final Set resultHolder = new HashSet(10);
IVisitor visitor = new IVisitor() {
public void visit(ResourceName resourceName, StoredProperty property, IndexCursor cursor) {
resultHolder.add(resourceName);
}
public boolean requiresValue(ResourceName resourceName, QualifiedName propertyName) {
return false;
}
};
recordsDeepMatching(resourceName, visitor);
return Collections.enumeration(resultHolder);
}
/**
* Returns the named property for the given resource.
* <p>
* The retieval is performed to depth zero. Returns <code>null</code>
* if there is no such property defined on the resource.
*
* @param resourceName the resource name to match.
* @param propertyName the property name to match.
* @return the matching property, or <code>null</code> if no such property.
*/
public StoredProperty get(ResourceName resourceName, final QualifiedName propertyName) throws CoreException {
final Object[] resultHolder = new Object[1];
IVisitor simpleVisitor = new IVisitor() {
public void visit(ResourceName resourceName, StoredProperty property, IndexCursor cursor) {
resultHolder[0] = property;
}
public boolean requiresValue(ResourceName resourceName, QualifiedName propertyName) {
return true;
}
};
recordsMatching(resourceName, propertyName, simpleVisitor);
return (StoredProperty) resultHolder[0];
}
/**
* Returns all the properties for a given resource.
* <p>
* Answer a <code>QueryResults</code> containing <code>StoredProperty</code>.
* If there are no matches returns an empty <code>QueryResults</code></p>
* <p>
* The depth parameter allows searching based on resource name path prefix.</p>
*
* @see QueryResults
* @param resourceName the resource name to match.
* @param depth the scope of the query
* @return a <code>QueryResults</code> with the matching properties.
*/
public QueryResults getAll(ResourceName resourceName, int depth) throws CoreException {
final QueryResults result = new QueryResults();
IVisitor visitor = new IVisitor() {
public void visit(ResourceName resourceName, StoredProperty property, IndexCursor cursor) {
result.add(resourceName, property);
}
public boolean requiresValue(ResourceName resourceName, QualifiedName propertyName) {
return true;
}
};
if (depth == IResource.DEPTH_ZERO)
recordsMatching(resourceName, visitor);
else
recordsDeepMatching(resourceName, visitor);
return result;
}
/**
* Returns all the property names for a given resource.
* <p>
* The result is a <code>QueryResults</code> containing <code>QualifiedName</code>.
* If the resource has no defined properties, the method returns
* an empty <code>QueryResults</code>.</p>
* <p>
* The depth parameter allows searching based on resource name path prefix.</p>
*
* @param resourceName the resource name to match.
* @param depth the depth to which the query runs.
* @return a <code>QueryResults</code> containing the property names.
*/
public QueryResults getNames(ResourceName resourceName, int depth) throws CoreException {
QueryResults results = new QueryResults();
if (depth == IResource.DEPTH_ZERO)
recordsMatching(resourceName, propertyNameVisitor(results));
else
recordsDeepMatching(resourceName, propertyNameVisitor(results));
return results;
}
protected IVisitor propertyNameVisitor(final QueryResults results) {
return new IVisitor() {
public void visit(ResourceName resourceName, StoredProperty property, IndexCursor cursor) {
results.add(resourceName, property.getName());
}
public boolean requiresValue(ResourceName resourceName, QualifiedName propertyName) {
return false;
}
};
}
/**
* Matches all properties for a given resource.
*/
protected void recordsDeepMatching(ResourceName resourceName, IVisitor visitor) throws CoreException {
// Build the partial 'search' key
StoreKey searchKey = new StoreKey(resourceName, true);
byte[] searchBytes = searchKey.toBytes();
int probe = searchBytes.length;
// Position a cursor over the first matching key
IndexCursor cursor = store.getCursor();
try {
cursor.find(searchBytes);
// While we have a prefix match
while (cursor.keyMatches(searchBytes)) {
// Must check that the prefix is up to a valid path segment
// note that the matching bytes length is > search key length since
// properties MUST have a local name.
byte[] matchingBytes = cursor.getKey();
if (probe == 1 || //empty path is a valid prefix for all paths
(matchingBytes[probe] == 0) || // a full path match
(matchingBytes[probe] == 47 /*IPath.SEPARATOR*/)) {
// a segment boundary match
visitPropertyAt(cursor, visitor);
}
// else the match is intra-segment and therefore invalid
cursor.next();
}
cursor.close();
} catch (Exception e) {
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, resourceName.getPath(), Policy.bind("properties.storeProblem"), e); //$NON-NLS-1$
}
}
/**
* Matches all properties for a given resource.
*/
protected void recordsMatching(ResourceName resourceName, IVisitor visitor) throws CoreException {
// Build the partial 'search' key
StoreKey searchKey = new StoreKey(resourceName, false);
byte[] searchBytes = searchKey.toBytes();
// Position a cursor over the first matching key
IndexCursor cursor = store.getCursor();
try {
cursor.find(searchBytes);
// While we have a prefix match, evaluate the visitor
while (cursor.keyMatches(searchBytes)) {
visitPropertyAt(cursor, visitor);
cursor.next();
}
cursor.close();
} catch (Exception e) {
store.reset();
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, resourceName.getPath(), Policy.bind("properties.storeProblem"), e); //$NON-NLS-1$
}
}
/**
* Matches the given property for a given resource.
* Note that there should be only one.
*/
protected void recordsMatching(ResourceName resourceName, QualifiedName propertyName, IVisitor visitor) throws CoreException {
// Build the full 'search' key
StoreKey searchKey = new StoreKey(resourceName, propertyName);
byte[] searchBytes = searchKey.toBytes();
// Position a cursor over the first matching key
IndexCursor cursor = store.getCursor();
try {
cursor.find(searchBytes);
// If we have an exact match, evaluate the visitor
if (cursor.keyEquals(searchBytes))
visitPropertyAt(cursor, visitor);
cursor.close();
} catch (Exception e) {
store.reset();
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, resourceName.getPath(), Policy.bind("properties.storeProblem"), e); //$NON-NLS-1$
}
}
/**
* Remove the given collection of named properties from the given resource.
* <p>
* All of the properties being removed must exist already on the
* resource based on the removeRule parameter. If the rule is
* MISSING_IGNORE then attempts to remove properties that do not exist
* are ignored, if the rule is MISSING_FAIL the method will throw
* a <code>PropertyNotFoundException</code> if the property does not exist.
* <p>
* If an exception is thrown, all properties that did previously
* exist will have been removed from the resource. To determine which
* properties caused the exception see the offenders result in the exception.</p>
* <p>
* The depth parameter allows matching based on resource name path prefix.</p>
*
* @param resourceName the resource containing the properties.
* @param propertyNames the property names to remove.
* @param depth the scope for matching the resource name.
* @param removeRule the behavior when removing non-existant properties.
* @exception PropertyNotFoundException
*/
public QueryResults remove(ResourceName resourceName, QualifiedName[] propertyNames, int depth, int removeRule) throws CoreException {
QueryResults failures = new QueryResults();
if (depth == IResource.DEPTH_ZERO) {
for (int i = 0; i < propertyNames.length; i++) {
boolean found = basicRemove(resourceName, propertyNames[i]);
if (!found && (removeRule == FAIL_MISSING))
failures.add(resourceName, propertyNames[i]);
}
} else {
Enumeration resourceNamesEnum = deepResourceNames(resourceName);
while (resourceNamesEnum.hasMoreElements()) {
ResourceName resName = (ResourceName) resourceNamesEnum.nextElement();
for (int i = 0; i < propertyNames.length; i++) {
boolean found = basicRemove(resName, propertyNames[i]);
if (!found && (removeRule == FAIL_MISSING))
failures.add(resName, propertyNames[i]);
}
}
}
return failures;
}
/**
* Remove the named property from the given resource.
* <p>
* If a matching property does not exist on this resource
* the method has no affect on the store. Removal is performed
* to depth zero.</p>
* <p>
* @param resourceName the resource containing the property.
* @param property the property to remove.
*/
public void remove(ResourceName resourceName, QualifiedName propertyName) throws CoreException {
remove(resourceName, new QualifiedName[] {propertyName}, IResource.DEPTH_ZERO, IGNORE_MISSING);
}
/**
* Remove all the properties from a given resource.
* <p>
* The depth parameter allows matching based on resource name path prefix.</p>
*
* @param resourceName the resource containing the properties.
* @param depth the scope for matching the resource name.
*/
public void removeAll(ResourceName resourceName, int depth) throws CoreException {
QueryResults namesSearch = getNames(resourceName, depth);
Enumeration resourceNamesEnum = namesSearch.getResourceNames();
while (resourceNamesEnum.hasMoreElements()) {
ResourceName resName = (ResourceName) resourceNamesEnum.nextElement();
Enumeration propertyNamesEnum = Collections.enumeration(namesSearch.getResults(resName));
while (propertyNamesEnum.hasMoreElements()) {
QualifiedName propertyName = (QualifiedName) propertyNamesEnum.nextElement();
basicRemove(resName, propertyName);
}
}
}
/**
* Sets the given collection of properties on the given resource.
* <p>
* The addRule determines whether the properties must already exist
* or not, and if they do whether they are updated by subsequent addition.
* Valid addRule values are defined in <code>IPropertyCollectionConstants</code>.
* <p>
* The depth parameter allows matching based on resource name path prefix.</p>
* <p>
* The <code>PropertyExistsException</code> is thrown if the matching resource
* already has a property of the same name, and the rule requires that
* it must not. If the exception is thrown, all successfull properties
* will have been set, and the failures are listed in the exception.</p>
*
* @param resourceName the resource to receive the properties.
* @param properties the properties to add.
* @param depth the depth at which to apply the add opertion.
* @param addRule the behavior of the add operation.
* @exception PropertyExistsException
*/
public QueryResults set(ResourceName resourceName, StoredProperty[] properties, int depth, int mode) throws CoreException {
QueryResults failures = new QueryResults();
commonSet(resourceName, properties, depth, mode, failures);
return failures;
}
/**
* Sets the given property to the given resource.
* <p>
* The property is added to depth zero. If the resource already has
* a proprety with the same name, it's value is updated to the given
* value (i.e. SET_UPDATE add rule equivalent.)</p>
*
* @param resourceName the resource to receive the property.
* @param property the property to add.
*/
public void set(ResourceName resourceName, StoredProperty property) throws CoreException {
commonSet(resourceName, new StoredProperty[] {property}, IResource.DEPTH_ZERO, SET_UPDATE, null);
}
public void shutdown(IProgressMonitor monitor) {
if (store == null)
return;
store.close();
}
public void startup(IProgressMonitor monitor) {
}
protected void visitPropertyAt(IndexCursor cursor, IVisitor visitor) throws CoreException {
try {
StoreKey key = new StoreKey(cursor.getKey());
ResourceName resourceName = key.getResourceName();
QualifiedName propertyName = key.getPropertyName();
String propertyValue = null;
if (visitor.requiresValue(resourceName, propertyName))
propertyValue = store.getObjectAsString(cursor.getValueAsObjectID());
visitor.visit(resourceName, new StoredProperty(propertyName, propertyValue), cursor);
} catch (Exception e) {
throw new ResourceException(IResourceStatus.FAILED_READ_LOCAL, null, Policy.bind("properties.storeProblem"), e); //$NON-NLS-1$
}
}
public void commit() throws CoreException {
store.commit();
}
}