All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jgit.lib.ObjectIdOwnerMap Maven / Gradle / Ivy

/*
 * Copyright (C) 2011, Google Inc.
 * and other copyright owners as documented in the project's IP log.
 *
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Distribution License v1.0 which
 * accompanies this distribution, is reproduced below, and is
 * available at http://www.eclipse.org/org/documents/edl-v10.php
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * - Redistributions of source code must retain the above copyright
 *   notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above
 *   copyright notice, this list of conditions and the following
 *   disclaimer in the documentation and/or other materials provided
 *   with the distribution.
 *
 * - Neither the name of the Eclipse Foundation, Inc. nor the
 *   names of its contributors may be used to endorse or promote
 *   products derived from this software without specific prior
 *   written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.eclipse.jgit.lib;

import java.util.Arrays;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 * Fast, efficient map for {@link ObjectId} subclasses in only one map.
 * 

* To use this map type, applications must have their entry value type extend * from {@link ObjectIdOwnerMap.Entry}, which itself extends from ObjectId. *

* Object instances may only be stored in ONE ObjectIdOwnerMap. This * restriction exists because the map stores internal map state within each * object instance. If an instance is be placed in another ObjectIdOwnerMap it * could corrupt one or both map's internal state. *

* If an object instance must be in more than one map, applications may use * ObjectIdOwnerMap for one of the maps, and {@link ObjectIdSubclassMap} for the * other map(s). It is encouraged to use ObjectIdOwnerMap for the map that is * accessed most often, as this implementation runs faster than the more general * ObjectIdSubclassMap implementation. * * @param * type of subclass of ObjectId that will be stored in the map. */ public class ObjectIdOwnerMap implements Iterable { /** Size of the initial directory, will grow as necessary. */ private static final int INITIAL_DIRECTORY = 1024; /** Number of bits in a segment's index. Segments are 2^11 in size. */ private static final int SEGMENT_BITS = 11; private static final int SEGMENT_SHIFT = 32 - SEGMENT_BITS; /** * Top level directory of the segments. *

* The low {@link #bits} of the SHA-1 are used to select the segment from * this directory. Each segment is constant sized at 2^SEGMENT_BITS. */ private V[][] directory; /** Total number of objects in this map. */ private int size; /** The map doubles in capacity when {@link #size} reaches this target. */ private int grow; /** Number of low bits used to form the index into {@link #directory}. */ private int bits; /** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */ private int mask; /** Create an empty map. */ @SuppressWarnings("unchecked") public ObjectIdOwnerMap() { bits = 0; mask = 0; grow = computeGrowAt(bits); directory = (V[][]) new Entry[INITIAL_DIRECTORY][]; directory[0] = newSegment(); } /** Remove all entries from this map. */ public void clear() { size = 0; for (V[] tbl : directory) { if (tbl == null) break; Arrays.fill(tbl, null); } } /** * Lookup an existing mapping. * * @param toFind * the object identifier to find. * @return the instance mapped to toFind, or null if no mapping exists. */ @SuppressWarnings("unchecked") public V get(final AnyObjectId toFind) { int h = toFind.w1; V obj = directory[h & mask][h >>> SEGMENT_SHIFT]; for (; obj != null; obj = (V) obj.next) if (equals(obj, toFind)) return obj; return null; } /** * Returns true if this map contains the specified object. * * @param toFind * object to find. * @return true if the mapping exists for this object; false otherwise. */ public boolean contains(final AnyObjectId toFind) { return get(toFind) != null; } /** * Store an object for future lookup. *

* An existing mapping for must not be in this map. Callers must * first call {@link #get(AnyObjectId)} to verify there is no current * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}. * * @param newValue * the object to store. * @param * type of instance to store. */ public void add(final Q newValue) { if (++size == grow) grow(); int h = newValue.w1; V[] table = directory[h & mask]; h >>>= SEGMENT_SHIFT; newValue.next = table[h]; table[h] = newValue; } /** * Store an object for future lookup. *

* Stores {@code newValue}, but only if there is not already an object for * the same object name. Callers can tell if the value is new by checking * the return value with reference equality: * *

	 * V obj = ...;
	 * boolean wasNew = map.addIfAbsent(obj) == obj;
	 * 
* * @param newValue * the object to store. * @return {@code newValue} if stored, or the prior value already stored and * that would have been returned had the caller used * {@code get(newValue)} first. * @param * type of instance to store. */ @SuppressWarnings("unchecked") public V addIfAbsent(final Q newValue) { int h = newValue.w1; V[] table = directory[h & mask]; h >>>= SEGMENT_SHIFT; for (V obj = table[h]; obj != null; obj = (V) obj.next) if (equals(obj, newValue)) return obj; newValue.next = table[h]; table[h] = newValue; if (++size == grow) grow(); return newValue; } /** @return number of objects in this map. */ public int size() { return size; } /** @return true if {@link #size()} is 0. */ public boolean isEmpty() { return size == 0; } public Iterator iterator() { return new Iterator() { private int found; private int dirIdx; private int tblIdx; private V next; public boolean hasNext() { return found < size; } public V next() { if (next != null) return found(next); for (;;) { V[] table = directory[dirIdx]; if (tblIdx == table.length) { if (++dirIdx >= (1 << bits)) throw new NoSuchElementException(); table = directory[dirIdx]; tblIdx = 0; } while (tblIdx < table.length) { V v = table[tblIdx++]; if (v != null) return found(v); } } } @SuppressWarnings("unchecked") private V found(V v) { found++; next = (V) v.next; return v; } public void remove() { throw new UnsupportedOperationException(); } }; } @SuppressWarnings("unchecked") private void grow() { final int oldDirLen = 1 << bits; final int s = 1 << bits; bits++; mask = (1 << bits) - 1; grow = computeGrowAt(bits); // Quadruple the directory if it needs to expand. Expanding the // directory is expensive because it generates garbage, so try // to avoid doing it often. // final int newDirLen = 1 << bits; if (directory.length < newDirLen) { V[][] newDir = (V[][]) new Entry[newDirLen << 1][]; System.arraycopy(directory, 0, newDir, 0, oldDirLen); directory = newDir; } // For every bucket of every old segment, split the chain between // the old segment and the new segment's corresponding bucket. To // select between them use the lowest bit that was just added into // the mask above. This causes the table to double in capacity. // for (int dirIdx = 0; dirIdx < oldDirLen; dirIdx++) { final V[] oldTable = directory[dirIdx]; final V[] newTable = newSegment(); for (int i = 0; i < oldTable.length; i++) { V chain0 = null; V chain1 = null; V next; for (V obj = oldTable[i]; obj != null; obj = next) { next = (V) obj.next; if ((obj.w1 & s) == 0) { obj.next = chain0; chain0 = obj; } else { obj.next = chain1; chain1 = obj; } } oldTable[i] = chain0; newTable[i] = chain1; } directory[oldDirLen + dirIdx] = newTable; } } @SuppressWarnings("unchecked") private final V[] newSegment() { return (V[]) new Entry[1 << SEGMENT_BITS]; } private static final int computeGrowAt(int bits) { return 1 << (bits + SEGMENT_BITS); } private static final boolean equals(AnyObjectId firstObjectId, AnyObjectId secondObjectId) { return firstObjectId.w2 == secondObjectId.w2 && firstObjectId.w3 == secondObjectId.w3 && firstObjectId.w4 == secondObjectId.w4 && firstObjectId.w5 == secondObjectId.w5 && firstObjectId.w1 == secondObjectId.w1; } /** Type of entry stored in the {@link ObjectIdOwnerMap}. */ public static abstract class Entry extends ObjectId { Entry next; /** * Initialize this entry with a specific ObjectId. * * @param id * the id the entry represents. */ public Entry(AnyObjectId id) { super(id); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy