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 James Blackburn (Broadcom Corp.) - ongoing development
/******************************************************************************* * 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 * James Blackburn (Broadcom Corp.) - ongoing development *******************************************************************************/
package org.eclipse.core.internal.localstore; import java.io.*; import java.util.Arrays; import java.util.Comparator; import org.eclipse.core.internal.utils.UniversalUniqueIdentifier; import org.eclipse.core.runtime.IPath; public class HistoryBucket extends Bucket {
A entry in the bucket index. Each entry has one path and a collection of states, which by their turn contain a (UUID, timestamp) pair.

This class is intended as a lightweight way of hiding the internal data structure. Objects of this class are supposed to be short-lived. No instances of this class are kept stored anywhere. The real stuff (the internal data structure) is.

/** * A entry in the bucket index. Each entry has one path and a collection * of states, which by their turn contain a (UUID, timestamp) pair. * <p> * This class is intended as a lightweight way of hiding the internal data structure. * Objects of this class are supposed to be short-lived. No instances * of this class are kept stored anywhere. The real stuff (the internal data structure) * is. * </p> */
public static final class HistoryEntry extends Bucket.Entry { final static Comparator<byte[]> COMPARATOR = (state1, state2) -> compareStates(state1, state2); // the length of each component of the data array private final static byte[][] EMPTY_DATA = new byte[0][]; // the length of a long in bytes private final static int LONG_LENGTH = 8; // the length of a UUID in bytes private final static int UUID_LENGTH = UniversalUniqueIdentifier.BYTES_SIZE; public final static int DATA_LENGTH = UUID_LENGTH + LONG_LENGTH;
The history states. The first array dimension is the number of states. The second dimension is an encoding of the {UUID,timestamp} pair for that entry.
/** * The history states. The first array dimension is the number of states. The * second dimension is an encoding of the {UUID,timestamp} pair for that entry. */
private byte[][] data;
Comparison logic for states in byte[] form.
See Also:
  • compare.compare(Object, Object)
/** * Comparison logic for states in byte[] form. * * @see Comparator#compare(java.lang.Object, java.lang.Object) */
static int compareStates(byte[] state1, byte[] state2) { long timestamp1 = getTimestamp(state1); long timestamp2 = getTimestamp(state2); if (timestamp1 == timestamp2) return -UniversalUniqueIdentifier.compareTime(state1, state2); return timestamp1 < timestamp2 ? +1 : -1; }
Returns the byte array representation of a (UUID, timestamp) pair.
/** * Returns the byte array representation of a (UUID, timestamp) pair. */
static byte[] getState(UniversalUniqueIdentifier uuid, long timestamp) { byte[] uuidBytes = uuid.toBytes(); byte[] state = new byte[DATA_LENGTH]; System.arraycopy(uuidBytes, 0, state, 0, uuidBytes.length); for (int j = 0; j < LONG_LENGTH; j++) { state[UUID_LENGTH + j] = (byte) (0xFF & timestamp); timestamp >>>= 8; } return state; } private static long getTimestamp(byte[] state) { long timestamp = 0; for (int j = 0; j < LONG_LENGTH; j++) timestamp += (state[UUID_LENGTH + j] & 0xFFL) << j * 8; return timestamp; }
Inserts the given item into the given array at the right position. Returns the resulting array. Returns null if the item already exists.
/** * Inserts the given item into the given array at the right position. * Returns the resulting array. Returns null if the item already exists. */
static byte[][] insert(byte[][] existing, byte[] toAdd) { // look for the right spot where to insert the new guy int index = search(existing, toAdd); if (index >= 0) // already there - nothing else to be done return null; // not found - insert int insertPosition = -index - 1; byte[][] newValue = new byte[existing.length + 1][]; if (insertPosition > 0) System.arraycopy(existing, 0, newValue, 0, insertPosition); newValue[insertPosition] = toAdd; if (insertPosition < existing.length) System.arraycopy(existing, insertPosition, newValue, insertPosition + 1, existing.length - insertPosition); return newValue; }
Merges two entries (are always sorted). Duplicates are discarded.
/** * Merges two entries (are always sorted). Duplicates are discarded. */
static byte[][] merge(byte[][] base, byte[][] additions) { int additionPointer = 0; int basePointer = 0; int added = 0; byte[][] result = new byte[base.length + additions.length][]; while (basePointer < base.length && additionPointer < additions.length) { int comparison = compareStates(base[basePointer], additions[additionPointer]); if (comparison == 0) { result[added++] = base[basePointer++]; // duplicate, ignore additionPointer++; } else if (comparison < 0) result[added++] = base[basePointer++]; else result[added++] = additions[additionPointer++]; } // copy the remaining states from either additions or base arrays byte[][] 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 byte[][] finalResult = new byte[added][]; System.arraycopy(result, 0, finalResult, 0, finalResult.length); return finalResult; } private static int search(byte[][] existing, byte[] element) { return Arrays.binarySearch(existing, element, COMPARATOR); } public HistoryEntry(IPath path, byte[][] data) { super(path); this.data = data; } public HistoryEntry(IPath path, HistoryEntry base) { super(path); this.data = new byte[base.data.length][]; System.arraycopy(base.data, 0, this.data, 0, this.data.length); }
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 (byte[] d : data) { if (d != null) { data[occurrences++] = d; } } if (occurrences == data.length) // no states deleted return; if (occurrences == 0) { // no states remaining data = EMPTY_DATA; delete(); return; } byte[][] result = new byte[occurrences][]; System.arraycopy(data, 0, result, 0, occurrences); data = result; } public void deleteOccurrence(int i) { markDirty(); data[i] = null; } byte[][] getData() { return data; } @Override public int getOccurrences() { return data.length; } public long getTimestamp(int i) { return getTimestamp(data[i]); } public UniversalUniqueIdentifier getUUID(int i) { return new UniversalUniqueIdentifier(data[i]); } @Override public Object getValue() { return data; } @Override public boolean isEmpty() { return data.length == 0; } @Override public void visited() { compact(); } }
Version number for the current implementation file's format.

Version 2 (3.1 M5):

FILE ::= VERSION_ID ENTRY+
ENTRY ::= PATH STATE_COUNT STATE+
PATH ::= string (does not include project name)
STATE_COUNT ::= int
STATE ::= UUID LAST_MODIFIED
UUID	 ::= byte[16]
LAST_MODIFIED ::= byte[8]

Version 1 (3.1 M4):

FILE ::= VERSION_ID ENTRY+
ENTRY ::= PATH STATE_COUNT STATE+
PATH ::= string
STATE_COUNT ::= int
STATE ::= UUID LAST_MODIFIED
UUID	 ::= byte[16]
LAST_MODIFIED ::= byte[8]
/** * Version number for the current implementation file's format. * <p> * Version 2 (3.1 M5): * </p> * <pre> * FILE ::= VERSION_ID ENTRY+ * ENTRY ::= PATH STATE_COUNT STATE+ * PATH ::= string (does not include project name) * STATE_COUNT ::= int * STATE ::= UUID LAST_MODIFIED * UUID ::= byte[16] * LAST_MODIFIED ::= byte[8] * </pre> * <p> * Version 1 (3.1 M4): * </p> * <pre> * FILE ::= VERSION_ID ENTRY+ * ENTRY ::= PATH STATE_COUNT STATE+ * PATH ::= string * STATE_COUNT ::= int * STATE ::= UUID LAST_MODIFIED * UUID ::= byte[16] * LAST_MODIFIED ::= byte[8] * </pre> */
public final static byte VERSION = 2; public HistoryBucket() { super(); } public void addBlob(IPath path, UniversalUniqueIdentifier uuid, long lastModified) { byte[] state = HistoryEntry.getState(uuid, lastModified); String pathAsString = path.toString(); byte[][] existing = (byte[][]) getEntryValue(pathAsString); if (existing == null) { setEntryValue(pathAsString, new byte[][] {state}); return; } byte[][] newValue = HistoryEntry.insert(existing, state); if (newValue == null) return; setEntryValue(pathAsString, newValue); } public void addBlobs(HistoryEntry fileEntry) { IPath path = fileEntry.getPath(); byte[][] additions = fileEntry.getData(); String pathAsString = path.toString(); byte[][] existing = (byte[][]) getEntryValue(pathAsString); if (existing == null) { setEntryValue(pathAsString, additions); return; } setEntryValue(pathAsString, HistoryEntry.merge(existing, additions)); } @Override protected Bucket.Entry createEntry(IPath path, Object value) { return new HistoryEntry(path, (byte[][]) value); } public HistoryEntry getEntry(IPath path) { String pathAsString = path.toString(); byte[][] existing = (byte[][]) getEntryValue(pathAsString); if (existing == null) return null; return new HistoryEntry(path, existing); } @Override protected String getIndexFileName() { return "history.index"; //$NON-NLS-1$ } @Override protected byte getVersion() { return VERSION; } @Override protected String getVersionFileName() { return "history.version"; //$NON-NLS-1$ } @Override protected Object readEntryValue(DataInputStream source) throws IOException { int length = source.readUnsignedShort(); byte[][] uuids = new byte[length][HistoryEntry.DATA_LENGTH]; for (byte[] uuid : uuids) source.read(uuid); return uuids; } @Override protected void writeEntryValue(DataOutputStream destination, Object entryValue) throws IOException { byte[][] uuids = (byte[][]) entryValue; destination.writeShort(uuids.length); for (byte[] uuid : uuids) destination.write(uuid); } }