BlockList.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.util;

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

  15. /**
  16.  * Random access list that allocates entries in blocks.
  17.  * <p>
  18.  * Unlike {@link java.util.ArrayList}, this type does not need to reallocate the
  19.  * internal array in order to expand the capacity of the list. Access to any
  20.  * element is constant time, but requires two array lookups instead of one.
  21.  * <p>
  22.  * To handle common usages, {@link #add(Object)} and {@link #iterator()} use
  23.  * internal code paths to amortize out the second array lookup, making addition
  24.  * and simple iteration closer to one array operation per element processed.
  25.  * <p>
  26.  * Similar to {@code ArrayList}, adding or removing from any position except the
  27.  * end of the list requires O(N) time to copy all elements between the
  28.  * modification point and the end of the list. Applications are strongly
  29.  * encouraged to not use this access pattern with this list implementation.
  30.  *
  31.  * @param <T>
  32.  *            type of list element.
  33.  */
  34. public class BlockList<T> extends AbstractList<T> {
  35.     private static final int BLOCK_BITS = 10;

  36.     static final int BLOCK_SIZE = 1 << BLOCK_BITS;

  37.     private static final int BLOCK_MASK = BLOCK_SIZE - 1;

  38.     T[][] directory;

  39.     int size;

  40.     private int tailDirIdx;

  41.     private int tailBlkIdx;

  42.     private T[] tailBlock;

  43.     /**
  44.      * Initialize an empty list.
  45.      */
  46.     public BlockList() {
  47.         directory = BlockList.<T> newDirectory(256);
  48.         directory[0] = BlockList.<T> newBlock();
  49.         tailBlock = directory[0];
  50.     }

  51.     /**
  52.      * Initialize an empty list with an expected capacity.
  53.      *
  54.      * @param capacity
  55.      *            number of elements expected to be in the list.
  56.      */
  57.     public BlockList(int capacity) {
  58.         int dirSize = toDirectoryIndex(capacity);
  59.         if ((capacity & BLOCK_MASK) != 0 || dirSize == 0)
  60.             dirSize++;
  61.         directory = BlockList.<T> newDirectory(dirSize);
  62.         directory[0] = BlockList.<T> newBlock();
  63.         tailBlock = directory[0];
  64.     }

  65.     /** {@inheritDoc} */
  66.     @Override
  67.     public int size() {
  68.         return size;
  69.     }

  70.     /** {@inheritDoc} */
  71.     @Override
  72.     public void clear() {
  73.         for (T[] block : directory) {
  74.             if (block != null)
  75.                 Arrays.fill(block, null);
  76.         }
  77.         size = 0;
  78.         tailDirIdx = 0;
  79.         tailBlkIdx = 0;
  80.         tailBlock = directory[0];
  81.     }

  82.     /** {@inheritDoc} */
  83.     @Override
  84.     public T get(int index) {
  85.         if (index < 0 || size <= index)
  86.             throw new IndexOutOfBoundsException(String.valueOf(index));
  87.         return directory[toDirectoryIndex(index)][toBlockIndex(index)];
  88.     }

  89.     /** {@inheritDoc} */
  90.     @Override
  91.     public T set(int index, T element) {
  92.         if (index < 0 || size <= index)
  93.             throw new IndexOutOfBoundsException(String.valueOf(index));
  94.         T[] blockRef = directory[toDirectoryIndex(index)];
  95.         int blockIdx = toBlockIndex(index);
  96.         T old = blockRef[blockIdx];
  97.         blockRef[blockIdx] = element;
  98.         return old;
  99.     }

  100.     /**
  101.      * Quickly append all elements of another BlockList.
  102.      *
  103.      * @param src
  104.      *            the list to copy elements from.
  105.      */
  106.     public void addAll(BlockList<T> src) {
  107.         if (src.size == 0)
  108.             return;

  109.         int srcDirIdx = 0;
  110.         for (; srcDirIdx < src.tailDirIdx; srcDirIdx++)
  111.             addAll(src.directory[srcDirIdx], 0, BLOCK_SIZE);
  112.         if (src.tailBlkIdx != 0)
  113.             addAll(src.tailBlock, 0, src.tailBlkIdx);
  114.     }

  115.     /**
  116.      * Quickly append all elements from an array.
  117.      *
  118.      * @param src
  119.      *            the source array.
  120.      * @param srcIdx
  121.      *            first index to copy.
  122.      * @param srcCnt
  123.      *            number of elements to copy.
  124.      */
  125.     public void addAll(T[] src, int srcIdx, int srcCnt) {
  126.         while (0 < srcCnt) {
  127.             int i = tailBlkIdx;
  128.             int n = Math.min(srcCnt, BLOCK_SIZE - i);
  129.             if (n == 0) {
  130.                 // Our tail is full, expand by one.
  131.                 add(src[srcIdx++]);
  132.                 srcCnt--;
  133.                 continue;
  134.             }

  135.             System.arraycopy(src, srcIdx, tailBlock, i, n);
  136.             tailBlkIdx += n;
  137.             size += n;
  138.             srcIdx += n;
  139.             srcCnt -= n;
  140.         }
  141.     }

  142.     /** {@inheritDoc} */
  143.     @Override
  144.     public boolean add(T element) {
  145.         int i = tailBlkIdx;
  146.         if (i < BLOCK_SIZE) {
  147.             // Fast-path: Append to current tail block.
  148.             tailBlock[i] = element;
  149.             tailBlkIdx = i + 1;
  150.             size++;
  151.             return true;
  152.         }

  153.         // Slow path: Move to the next block, expanding if necessary.
  154.         if (++tailDirIdx == directory.length) {
  155.             T[][] newDir = BlockList.<T> newDirectory(directory.length << 1);
  156.             System.arraycopy(directory, 0, newDir, 0, directory.length);
  157.             directory = newDir;
  158.         }

  159.         T[] blockRef = directory[tailDirIdx];
  160.         if (blockRef == null) {
  161.             blockRef = BlockList.<T> newBlock();
  162.             directory[tailDirIdx] = blockRef;
  163.         }
  164.         blockRef[0] = element;
  165.         tailBlock = blockRef;
  166.         tailBlkIdx = 1;
  167.         size++;
  168.         return true;
  169.     }

  170.     /** {@inheritDoc} */
  171.     @Override
  172.     public void add(int index, T element) {
  173.         if (index == size) {
  174.             // Fast-path: append onto the end of the list.
  175.             add(element);

  176.         } else if (index < 0 || size < index) {
  177.             throw new IndexOutOfBoundsException(String.valueOf(index));

  178.         } else {
  179.             // Slow-path: the list needs to expand and insert.
  180.             // Do this the naive way, callers shouldn't abuse
  181.             // this class by entering this code path.
  182.             //
  183.             add(null); // expand the list by one
  184.             for (int oldIdx = size - 2; index <= oldIdx; oldIdx--)
  185.                 set(oldIdx + 1, get(oldIdx));
  186.             set(index, element);
  187.         }
  188.     }

  189.     /** {@inheritDoc} */
  190.     @Override
  191.     public T remove(int index) {
  192.         if (index == size - 1) {
  193.             // Fast-path: remove the last element.
  194.             T[] blockRef = directory[toDirectoryIndex(index)];
  195.             int blockIdx = toBlockIndex(index);
  196.             T old = blockRef[blockIdx];
  197.             blockRef[blockIdx] = null;
  198.             size--;
  199.             if (0 < tailBlkIdx)
  200.                 tailBlkIdx--;
  201.             else
  202.                 resetTailBlock();
  203.             return old;

  204.         } else if (index < 0 || size <= index) {
  205.             throw new IndexOutOfBoundsException(String.valueOf(index));

  206.         } else {
  207.             // Slow-path: the list needs to contract and remove.
  208.             // Do this the naive way, callers shouldn't abuse
  209.             // this class by entering this code path.
  210.             //
  211.             T old = get(index);
  212.             for (; index < size - 1; index++)
  213.                 set(index, get(index + 1));
  214.             set(size - 1, null);
  215.             size--;
  216.             resetTailBlock();
  217.             return old;
  218.         }
  219.     }

  220.     private void resetTailBlock() {
  221.         tailDirIdx = toDirectoryIndex(size);
  222.         tailBlkIdx = toBlockIndex(size);
  223.         tailBlock = directory[tailDirIdx];
  224.     }

  225.     /** {@inheritDoc} */
  226.     @Override
  227.     public Iterator<T> iterator() {
  228.         return new MyIterator();
  229.     }

  230.     static final int toDirectoryIndex(int index) {
  231.         return index >>> BLOCK_BITS;
  232.     }

  233.     static final int toBlockIndex(int index) {
  234.         return index & BLOCK_MASK;
  235.     }

  236.     @SuppressWarnings("unchecked")
  237.     private static <T> T[][] newDirectory(int size) {
  238.         return (T[][]) new Object[size][];
  239.     }

  240.     @SuppressWarnings("unchecked")
  241.     private static <T> T[] newBlock() {
  242.         return (T[]) new Object[BLOCK_SIZE];
  243.     }

  244.     private class MyIterator implements Iterator<T> {
  245.         private int index;

  246.         private int dirIdx;

  247.         private int blkIdx;

  248.         private T[] block = directory[0];

  249.         @Override
  250.         public boolean hasNext() {
  251.             return index < size;
  252.         }

  253.         @Override
  254.         public T next() {
  255.             if (size <= index)
  256.                 throw new NoSuchElementException();

  257.             T res = block[blkIdx];
  258.             if (++blkIdx == BLOCK_SIZE) {
  259.                 if (++dirIdx < directory.length)
  260.                     block = directory[dirIdx];
  261.                 else
  262.                     block = null;
  263.                 blkIdx = 0;
  264.             }
  265.             index++;
  266.             return res;
  267.         }

  268.         @Override
  269.         public void remove() {
  270.             if (index == 0)
  271.                 throw new IllegalStateException();

  272.             BlockList.this.remove(--index);

  273.             dirIdx = toDirectoryIndex(index);
  274.             blkIdx = toBlockIndex(index);
  275.             block = directory[dirIdx];
  276.         }
  277.     }
  278. }