ObjectIdOwnerMap.java

  1. /*
  2.  * Copyright (C) 2011, Google Inc. and others
  3.  *
  4.  * This program and the accompanying materials are made available under the
  5.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  6.  * https://www.eclipse.org/org/documents/edl-v10.php.
  7.  *
  8.  * SPDX-License-Identifier: BSD-3-Clause
  9.  */

  10. package org.eclipse.jgit.lib;

  11. import java.util.Arrays;
  12. import java.util.Iterator;
  13. import java.util.NoSuchElementException;

  14. /**
  15.  * Fast, efficient map for {@link org.eclipse.jgit.lib.ObjectId} subclasses in
  16.  * only one map.
  17.  * <p>
  18.  * To use this map type, applications must have their entry value type extend
  19.  * from {@link org.eclipse.jgit.lib.ObjectIdOwnerMap.Entry}, which itself
  20.  * extends from ObjectId.
  21.  * <p>
  22.  * Object instances may only be stored in <b>ONE</b> ObjectIdOwnerMap. This
  23.  * restriction exists because the map stores internal map state within each
  24.  * object instance. If an instance is be placed in another ObjectIdOwnerMap it
  25.  * could corrupt one or both map's internal state.
  26.  * <p>
  27.  * If an object instance must be in more than one map, applications may use
  28.  * ObjectIdOwnerMap for one of the maps, and
  29.  * {@link org.eclipse.jgit.lib.ObjectIdSubclassMap} for the other map(s). It is
  30.  * encouraged to use ObjectIdOwnerMap for the map that is accessed most often,
  31.  * as this implementation runs faster than the more general ObjectIdSubclassMap
  32.  * implementation.
  33.  *
  34.  * @param <V>
  35.  *            type of subclass of ObjectId that will be stored in the map.
  36.  */
  37. public class ObjectIdOwnerMap<V extends ObjectIdOwnerMap.Entry>
  38.         implements Iterable<V>, ObjectIdSet {
  39.     /** Size of the initial directory, will grow as necessary. */
  40.     private static final int INITIAL_DIRECTORY = 1024;

  41.     /** Number of bits in a segment's index. Segments are 2^11 in size. */
  42.     private static final int SEGMENT_BITS = 11;

  43.     private static final int SEGMENT_SHIFT = 32 - SEGMENT_BITS;

  44.     /**
  45.      * Top level directory of the segments.
  46.      * <p>
  47.      * The low {@link #bits} of the SHA-1 are used to select the segment from
  48.      * this directory. Each segment is constant sized at 2^SEGMENT_BITS.
  49.      */
  50.     V[][] directory;

  51.     /** Total number of objects in this map. */
  52.     int size;

  53.     /** The map doubles in capacity when {@link #size} reaches this target. */
  54.     private int grow;

  55.     /** Number of low bits used to form the index into {@link #directory}. */
  56.     int bits;

  57.     /** Low bit mask to index into {@link #directory}, {@code 2^bits-1}. */
  58.     private int mask;

  59.     /**
  60.      * Create an empty map.
  61.      */
  62.     @SuppressWarnings("unchecked")
  63.     public ObjectIdOwnerMap() {
  64.         bits = 0;
  65.         mask = 0;
  66.         grow = computeGrowAt(bits);

  67.         directory = (V[][]) new Entry[INITIAL_DIRECTORY][];
  68.         directory[0] = newSegment();
  69.     }

  70.     /**
  71.      * Remove all entries from this map.
  72.      */
  73.     public void clear() {
  74.         size = 0;

  75.         for (V[] tbl : directory) {
  76.             if (tbl == null)
  77.                 break;
  78.             Arrays.fill(tbl, null);
  79.         }
  80.     }

  81.     /**
  82.      * Lookup an existing mapping.
  83.      *
  84.      * @param toFind
  85.      *            the object identifier to find.
  86.      * @return the instance mapped to toFind, or null if no mapping exists.
  87.      */
  88.     @SuppressWarnings("unchecked")
  89.     public V get(AnyObjectId toFind) {
  90.         if (toFind == null) {
  91.             return null;
  92.         }
  93.         int h = toFind.w1;
  94.         V obj = directory[h & mask][h >>> SEGMENT_SHIFT];
  95.         for (; obj != null; obj = (V) obj.next)
  96.             if (equals(obj, toFind))
  97.                 return obj;
  98.         return null;
  99.     }

  100.     /**
  101.      * {@inheritDoc}
  102.      * <p>
  103.      * Returns true if this map contains the specified object.
  104.      */
  105.     @Override
  106.     public boolean contains(AnyObjectId toFind) {
  107.         return get(toFind) != null;
  108.     }

  109.     /**
  110.      * Store an object for future lookup.
  111.      * <p>
  112.      * An existing mapping for <b>must not</b> be in this map. Callers must
  113.      * first call {@link #get(AnyObjectId)} to verify there is no current
  114.      * mapping prior to adding a new mapping, or use {@link #addIfAbsent(Entry)}.
  115.      *
  116.      * @param newValue
  117.      *            the object to store.
  118.      */
  119.     public <Q extends V> void add(Q newValue) {
  120.         if (++size == grow)
  121.             grow();

  122.         int h = newValue.w1;
  123.         V[] table = directory[h & mask];
  124.         h >>>= SEGMENT_SHIFT;

  125.         newValue.next = table[h];
  126.         table[h] = newValue;
  127.     }

  128.     /**
  129.      * Store an object for future lookup.
  130.      * <p>
  131.      * Stores {@code newValue}, but only if there is not already an object for
  132.      * the same object name. Callers can tell if the value is new by checking
  133.      * the return value with reference equality:
  134.      *
  135.      * <pre>
  136.      * V obj = ...;
  137.      * boolean wasNew = map.addIfAbsent(obj) == obj;
  138.      * </pre>
  139.      *
  140.      * @param newValue
  141.      *            the object to store.
  142.      * @return {@code newValue} if stored, or the prior value already stored and
  143.      *         that would have been returned had the caller used
  144.      *         {@code get(newValue)} first.
  145.      */
  146.     @SuppressWarnings("unchecked")
  147.     public <Q extends V> V addIfAbsent(Q newValue) {
  148.         int h = newValue.w1;
  149.         V[] table = directory[h & mask];
  150.         h >>>= SEGMENT_SHIFT;

  151.         for (V obj = table[h]; obj != null; obj = (V) obj.next)
  152.             if (equals(obj, newValue))
  153.                 return obj;

  154.         newValue.next = table[h];
  155.         table[h] = newValue;

  156.         if (++size == grow)
  157.             grow();
  158.         return newValue;
  159.     }

  160.     /**
  161.      * Get number of objects in this map.
  162.      *
  163.      * @return number of objects in this map.
  164.      */
  165.     public int size() {
  166.         return size;
  167.     }

  168.     /**
  169.      * Whether this map is empty
  170.      *
  171.      * @return true if {@link #size()} is 0.
  172.      */
  173.     public boolean isEmpty() {
  174.         return size == 0;
  175.     }

  176.     /** {@inheritDoc} */
  177.     @Override
  178.     public Iterator<V> iterator() {
  179.         return new Iterator<V>() {
  180.             private int found;
  181.             private int dirIdx;
  182.             private int tblIdx;
  183.             private V next;

  184.             @Override
  185.             public boolean hasNext() {
  186.                 return found < size;
  187.             }

  188.             @Override
  189.             public V next() {
  190.                 if (next != null)
  191.                     return found(next);

  192.                 for (;;) {
  193.                     V[] table = directory[dirIdx];
  194.                     if (tblIdx == table.length) {
  195.                         if (++dirIdx >= (1 << bits))
  196.                             throw new NoSuchElementException();
  197.                         table = directory[dirIdx];
  198.                         tblIdx = 0;
  199.                     }

  200.                     while (tblIdx < table.length) {
  201.                         V v = table[tblIdx++];
  202.                         if (v != null)
  203.                             return found(v);
  204.                     }
  205.                 }
  206.             }

  207.             @SuppressWarnings("unchecked")
  208.             private V found(V v) {
  209.                 found++;
  210.                 next = (V) v.next;
  211.                 return v;
  212.             }

  213.             @Override
  214.             public void remove() {
  215.                 throw new UnsupportedOperationException();
  216.             }
  217.         };
  218.     }

  219.     @SuppressWarnings("unchecked")
  220.     private void grow() {
  221.         final int oldDirLen = 1 << bits;
  222.         final int s = 1 << bits;

  223.         bits++;
  224.         mask = (1 << bits) - 1;
  225.         grow = computeGrowAt(bits);

  226.         // Quadruple the directory if it needs to expand. Expanding the
  227.         // directory is expensive because it generates garbage, so try
  228.         // to avoid doing it often.
  229.         //
  230.         final int newDirLen = 1 << bits;
  231.         if (directory.length < newDirLen) {
  232.             V[][] newDir = (V[][]) new Entry[newDirLen << 1][];
  233.             System.arraycopy(directory, 0, newDir, 0, oldDirLen);
  234.             directory = newDir;
  235.         }

  236.         // For every bucket of every old segment, split the chain between
  237.         // the old segment and the new segment's corresponding bucket. To
  238.         // select between them use the lowest bit that was just added into
  239.         // the mask above. This causes the table to double in capacity.
  240.         //
  241.         for (int dirIdx = 0; dirIdx < oldDirLen; dirIdx++) {
  242.             final V[] oldTable = directory[dirIdx];
  243.             final V[] newTable = newSegment();

  244.             for (int i = 0; i < oldTable.length; i++) {
  245.                 V chain0 = null;
  246.                 V chain1 = null;
  247.                 V next;

  248.                 for (V obj = oldTable[i]; obj != null; obj = next) {
  249.                     next = (V) obj.next;

  250.                     if ((obj.w1 & s) == 0) {
  251.                         obj.next = chain0;
  252.                         chain0 = obj;
  253.                     } else {
  254.                         obj.next = chain1;
  255.                         chain1 = obj;
  256.                     }
  257.                 }

  258.                 oldTable[i] = chain0;
  259.                 newTable[i] = chain1;
  260.             }

  261.             directory[oldDirLen + dirIdx] = newTable;
  262.         }
  263.     }

  264.     @SuppressWarnings("unchecked")
  265.     private final V[] newSegment() {
  266.         return (V[]) new Entry[1 << SEGMENT_BITS];
  267.     }

  268.     private static final int computeGrowAt(int bits) {
  269.         return 1 << (bits + SEGMENT_BITS);
  270.     }

  271.     private static final boolean equals(AnyObjectId firstObjectId,
  272.             AnyObjectId secondObjectId) {
  273.         return firstObjectId.w2 == secondObjectId.w2
  274.                 && firstObjectId.w3 == secondObjectId.w3
  275.                 && firstObjectId.w4 == secondObjectId.w4
  276.                 && firstObjectId.w5 == secondObjectId.w5
  277.                 && firstObjectId.w1 == secondObjectId.w1;
  278.     }

  279.     /** Type of entry stored in the {@link ObjectIdOwnerMap}. */
  280.     public abstract static class Entry extends ObjectId {
  281.         transient Entry next;

  282.         /**
  283.          * Initialize this entry with a specific ObjectId.
  284.          *
  285.          * @param id
  286.          *            the id the entry represents.
  287.          */
  288.         public Entry(AnyObjectId id) {
  289.             super(id);
  290.         }
  291.     }
  292. }