Copyright (c) 2004, 2015 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 - Bug 473427
/******************************************************************************* * Copyright (c) 2004, 2015 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 *******************************************************************************/
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<String[]> COMPARATOR = (o1, o2) -> { int qualifierComparison = o1[0].compareTo(o2[0]); return qualifierComparison != 0 ? qualifierComparison : o1[1].compareTo(o2[1]); }; private static final String[][] EMPTY_DATA = new String[0][];
value is a String[][] of {{propertyKey.qualifier, propertyKey.localName, propertyValue}}
/** * 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 null if the property was found and the original array had size 1 (instead of a zero-length array).
/** * 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.
/** * 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); } }
Params:
  • path –
  • value – is a String[][] {{propertyKey, propertyValue}}
/** * @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.
/** * 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 (String[] s : value) { if (s != null) { value[occurrences++] = s; } } 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; } @Override 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 QualifiedName getPropertyName(int i) { return new QualifiedName(this.value[i][0], this.value[i][1]); } public String getPropertyValue(int i) { return this.value[i][2]; } @Override public Object getValue() { return value; } @Override 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.

Version 1:

 
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]
/** Version number for the current implementation file's format. * <p> * Version 1: * </p> * <pre> {@code * 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> */
private static final byte VERSION = 1; private final List<String> qualifierIndex = new ArrayList<>(); public PropertyBucket() { super(); } @Override 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); } @Override 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); } @Override protected byte getVersion() { return VERSION; } @Override protected String getVersionFileName() { return "properties.version"; //$NON-NLS-1$ } @Override public void load(String newProjectName, File baseLocation, boolean force) throws CoreException { qualifierIndex.clear(); super.load(newProjectName, baseLocation, force); } @Override protected Object readEntryValue(DataInputStream source) throws IOException, CoreException { int length = source.readUnsignedShort(); String[][] properties = new String[length][3]; for (String[] propertie : properties) { // qualifier byte constant = source.readByte(); switch (constant) { case QNAME: propertie[0] = source.readUTF(); qualifierIndex.add(propertie[0]); break; case INDEX: propertie[0] = 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 propertie[1] = source.readUTF(); // propertyValue propertie[2] = source.readUTF(); } return properties; } @Override 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); } @Override protected void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException { String[][] properties = (String[][]) entryValue; destination.writeShort(properties.length); for (String[] propertie : properties) { // writes the property key qualifier int index = qualifierIndex.indexOf(propertie[0]); if (index == -1) { destination.writeByte(QNAME); destination.writeUTF(propertie[0]); qualifierIndex.add(propertie[0]); } else { destination.writeByte(INDEX); destination.writeInt(index); } // then the local name destination.writeUTF(propertie[1]); // then the property value destination.writeUTF(propertie[2]); } } }