/******************************************************************************* * Copyright (c) 2004, 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.properties; import java.io.*; import java.util.*; import org.eclipse.core.internal.localstore.Bucket; import org.eclipse.core.internal.resources.ResourceException; import org.eclipse.core.internal.utils.Messages; import org.eclipse.core.resources.IResourceStatus; import org.eclipse.core.runtime.*; import org.eclipse.osgi.util.NLS; public class PropertyBucket extends Bucket { public static class PropertyEntry extends Entry { private final static Comparator COMPARATOR= new Comparator() { public int compare(Object o1, Object o2) { int qualifierComparison= ((String[])o1)[0].compareTo(((String[])o2)[0]); return qualifierComparison != 0 ? qualifierComparison : ((String[])o1)[1].compareTo(((String[])o2)[1]); } }; private static final String[][] EMPTY_DATA= new String[0][]; /** * value is a String[][] of {{propertyKey.qualifier, propertyKey.localName, propertyValue}} */ private String[][] value; /** * Deletes the property with the given name, and returns the result array. Returns the * original array if the property to be deleted could not be found. Returns * <code>null</code> if the property was found and the original array had size 1 (instead of * a zero-length array). */ static String[][] delete(String[][] existing, QualifiedName propertyName) { // a size-1 array is a special case if (existing.length == 1) return (existing[0][0].equals(propertyName.getQualifier()) && existing[0][1].equals(propertyName.getLocalName())) ? null : existing; // find the guy to delete int deletePosition= search(existing, propertyName); if (deletePosition < 0) // not found, nothing to delete return existing; String[][] newValue= new String[existing.length - 1][]; if (deletePosition > 0) // copy elements preceding the one to be removed System.arraycopy(existing, 0, newValue, 0, deletePosition); if (deletePosition < existing.length - 1) // copy elements succeeding the one to be removed System.arraycopy(existing, deletePosition + 1, newValue, deletePosition, newValue.length - deletePosition); return newValue; } static String[][] insert(String[][] existing, QualifiedName propertyName, String propertyValue) { // look for the right spot where to insert the new guy int index= search(existing, propertyName); if (index >= 0) { // found existing occurrence - just replace the value existing[index][2]= propertyValue; return existing; } // not found - insert int insertPosition= -index - 1; String[][] newValue= new String[existing.length + 1][]; if (insertPosition > 0) System.arraycopy(existing, 0, newValue, 0, insertPosition); newValue[insertPosition]= new String[] { propertyName.getQualifier(), propertyName.getLocalName(), propertyValue }; if (insertPosition < existing.length) System.arraycopy(existing, insertPosition, newValue, insertPosition + 1, existing.length - insertPosition); return newValue; } /** * Merges two entries (are always sorted). Duplicated additions replace existing ones. */ static Object merge(String[][] base, String[][] additions) { int additionPointer= 0; int basePointer= 0; int added= 0; String[][] result= new String[base.length + additions.length][]; while (basePointer < base.length && additionPointer < additions.length) { int comparison= COMPARATOR.compare(base[basePointer], additions[additionPointer]); if (comparison == 0) { result[added++]= additions[additionPointer++]; // duplicate, override basePointer++; } else if (comparison < 0) result[added++]= base[basePointer++]; else result[added++]= additions[additionPointer++]; } // copy the remaining states from either additions or base arrays String[][] remaining= basePointer == base.length ? additions : base; int remainingPointer= basePointer == base.length ? additionPointer : basePointer; int remainingCount= remaining.length - remainingPointer; System.arraycopy(remaining, remainingPointer, result, added, remainingCount); added+= remainingCount; if (added == base.length + additions.length) // no collisions return result; // there were collisions, need to compact String[][] finalResult= new String[added][]; System.arraycopy(result, 0, finalResult, 0, finalResult.length); return finalResult; } private static int search(String[][] existing, QualifiedName propertyName) { return Arrays.binarySearch(existing, new String[] { propertyName.getQualifier(), propertyName.getLocalName(), null }, COMPARATOR); } public PropertyEntry(IPath path, PropertyEntry base) { super(path); //copy 2-dimensional array [x][y] int xLen= base.value.length; this.value= new String[xLen][]; for (int i= 0; i < xLen; i++) { int yLen= base.value[i].length; this.value[i]= new String[yLen]; System.arraycopy(base.value[i], 0, value[i], 0, yLen); } } /** * @param path * @param value is a String[][] {{propertyKey, propertyValue}} */ protected PropertyEntry(IPath path, String[][] value) { super(path); this.value= value; } /** * Compacts the data array removing any null slots. If non-null slots are found, the entry * is marked for removal. */ private void compact() { if (!isDirty()) return; int occurrences= 0; for (int i= 0; i < value.length; i++) if (value[i] != null) value[occurrences++]= value[i]; if (occurrences == value.length) // no states deleted return; if (occurrences == 0) { // no states remaining value= EMPTY_DATA; delete(); return; } String[][] result= new String[occurrences][]; System.arraycopy(value, 0, result, 0, occurrences); value= result; } public int getOccurrences() { return value == null ? 0 : value.length; } public String getProperty(QualifiedName name) { int index= search(value, name); return index < 0 ? null : value[index][2]; } public Object getPropertyName(int i) { return new QualifiedName(this.value[i][0], this.value[i][1]); } public Object getPropertyValue(int i) { return this.value[i][2]; } public Object getValue() { return value; } public void visited() { compact(); } } public static final byte INDEX= 1; public static final byte QNAME= 2; /** * Version number for the current implementation file's format. * <p> * Version 1: * * <pre> * FILE ::= VERSION_ID ENTRY+ * ENTRY ::= PATH PROPERTY_COUNT PROPERTY+ * PATH ::= string (does not contain project name) * PROPERTY_COUNT ::= int * PROPERTY ::= QUALIFIER LOCAL_NAME VALUE * QUALIFIER ::= INDEX | QNAME * INDEX -> byte int * QNAME -> byte string * UUID ::= byte[16] * LAST_MODIFIED ::= byte[8] * </pre> * * </p> */ private static final byte VERSION= 1; private final List qualifierIndex= new ArrayList(); public PropertyBucket() { super(); } protected Entry createEntry(IPath path, Object value) { return new PropertyEntry(path, (String[][])value); } private PropertyEntry getEntry(IPath path) { String pathAsString= path.toString(); String[][] existing= (String[][])getEntryValue(pathAsString); if (existing == null) return null; return new PropertyEntry(path, existing); } /* (non-Javadoc) * @see org.eclipse.core.internal.localstore.Bucket#getIndexFileName() */ protected String getIndexFileName() { return "properties.index"; //$NON-NLS-1$ } public String getProperty(IPath path, QualifiedName name) { PropertyEntry entry= getEntry(path); if (entry == null) return null; return entry.getProperty(name); } protected byte getVersion() { return VERSION; } /* (non-Javadoc) * @see org.eclipse.core.internal.localstore.Bucket#getVersionFileName() */ protected String getVersionFileName() { return "properties.version"; //$NON-NLS-1$ } public void load(String newProjectName, File baseLocation, boolean force) throws CoreException { qualifierIndex.clear(); super.load(newProjectName, baseLocation, force); } protected Object readEntryValue(DataInputStream source) throws IOException, CoreException { int length= source.readUnsignedShort(); String[][] properties= new String[length][3]; for (int j= 0; j < properties.length; j++) { // qualifier byte constant= source.readByte(); switch (constant) { case QNAME: properties[j][0]= source.readUTF(); qualifierIndex.add(properties[j][0]); break; case INDEX: properties[j][0]= (String)qualifierIndex.get(source.readInt()); break; default: //if we get here the properties file is corrupt IPath resourcePath= projectName == null ? Path.ROOT : Path.ROOT.append(projectName); String msg= NLS.bind(Messages.properties_readProperties, resourcePath.toString()); throw new ResourceException(IResourceStatus.FAILED_READ_METADATA, null, msg, null); } // localName properties[j][1]= source.readUTF(); // propertyValue properties[j][2]= source.readUTF(); } return properties; } public void save() throws CoreException { qualifierIndex.clear(); super.save(); } public void setProperties(PropertyEntry entry) { IPath path= entry.getPath(); String[][] additions= (String[][])entry.getValue(); String pathAsString= path.toString(); String[][] existing= (String[][])getEntryValue(pathAsString); if (existing == null) { setEntryValue(pathAsString, additions); return; } setEntryValue(pathAsString, PropertyEntry.merge(existing, additions)); } public void setProperty(IPath path, QualifiedName name, String value) { String pathAsString= path.toString(); String[][] existing= (String[][])getEntryValue(pathAsString); if (existing == null) { if (value != null) setEntryValue(pathAsString, new String[][] { { name.getQualifier(), name.getLocalName(), value } }); return; } String[][] newValue; if (value != null) newValue= PropertyEntry.insert(existing, name, value); else newValue= PropertyEntry.delete(existing, name); // even if newValue == existing we should mark as dirty (insert may just change the existing array) setEntryValue(pathAsString, newValue); } protected void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException { String[][] properties= (String[][])entryValue; destination.writeShort(properties.length); for (int j= 0; j < properties.length; j++) { // writes the property key qualifier int index= qualifierIndex.indexOf(properties[j][0]); if (index == -1) { destination.writeByte(QNAME); destination.writeUTF(properties[j][0]); qualifierIndex.add(properties[j][0]); } else { destination.writeByte(INDEX); destination.writeInt(index); } // then the local name destination.writeUTF(properties[j][1]); // then the property value destination.writeUTF(properties[j][2]); } } }