RefList.java

  1. /*
  2.  * Copyright (C) 2010, 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.Arrays;
  12. import java.util.Collections;
  13. import java.util.Iterator;
  14. import java.util.List;
  15. import java.util.NoSuchElementException;
  16. import java.util.function.BinaryOperator;
  17. import java.util.stream.Collector;

  18. import org.eclipse.jgit.annotations.Nullable;
  19. import org.eclipse.jgit.lib.Ref;
  20. import org.eclipse.jgit.lib.RefComparator;

  21. /**
  22.  * Specialized variant of an ArrayList to support a {@code RefDatabase}.
  23.  * <p>
  24.  * This list is a hybrid of a Map&lt;String,Ref&gt; and of a List&lt;Ref&gt;. It
  25.  * tracks reference instances by name by keeping them sorted and performing
  26.  * binary search to locate an entry. Lookup time is O(log N), but addition and
  27.  * removal is O(N + log N) due to the list expansion or contraction costs.
  28.  * <p>
  29.  * This list type is copy-on-write. Mutation methods return a new copy of the
  30.  * list, leaving {@code this} unmodified. As a result we cannot easily implement
  31.  * the {@link java.util.List} interface contract.
  32.  *
  33.  * @param <T>
  34.  *            the type of reference being stored in the collection.
  35.  */
  36. public class RefList<T extends Ref> implements Iterable<Ref> {
  37.     private static final RefList<Ref> EMPTY = new RefList<>(new Ref[0], 0);

  38.     /**
  39.      * Create an empty unmodifiable reference list.
  40.      *
  41.      * @return an empty unmodifiable reference list.
  42.      */
  43.     @SuppressWarnings("unchecked")
  44.     public static <T extends Ref> RefList<T> emptyList() {
  45.         return (RefList<T>) EMPTY;
  46.     }

  47.     final Ref[] list;

  48.     final int cnt;

  49.     RefList(Ref[] list, int cnt) {
  50.         this.list = list;
  51.         this.cnt = cnt;
  52.     }

  53.     /**
  54.      * Initialize this list to use the same backing array as another list.
  55.      *
  56.      * @param src
  57.      *            the source list.
  58.      */
  59.     protected RefList(RefList<T> src) {
  60.         this.list = src.list;
  61.         this.cnt = src.cnt;
  62.     }

  63.     /** {@inheritDoc} */
  64.     @Override
  65.     public Iterator<Ref> iterator() {
  66.         return new Iterator<>() {
  67.             private int idx;

  68.             @Override
  69.             public boolean hasNext() {
  70.                 return idx < cnt;
  71.             }

  72.             @Override
  73.             public Ref next() {
  74.                 if (idx < cnt)
  75.                     return list[idx++];
  76.                 throw new NoSuchElementException();
  77.             }

  78.             @Override
  79.             public void remove() {
  80.                 throw new UnsupportedOperationException();
  81.             }
  82.         };
  83.     }

  84.     /**
  85.      * Cast {@code this} as an immutable, standard {@link java.util.List}.
  86.      *
  87.      * @return {@code this} as an immutable, standard {@link java.util.List}.
  88.      */
  89.     public final List<Ref> asList() {
  90.         final List<Ref> r = Arrays.asList(list).subList(0, cnt);
  91.         return Collections.unmodifiableList(r);
  92.     }

  93.     /**
  94.      * Get number of items in this list.
  95.      *
  96.      * @return number of items in this list.
  97.      */
  98.     public final int size() {
  99.         return cnt;
  100.     }

  101.     /**
  102.      * Get if this list is empty.
  103.      *
  104.      * @return true if the size of this list is 0.
  105.      */
  106.     public final boolean isEmpty() {
  107.         return cnt == 0;
  108.     }

  109.     /**
  110.      * Locate an entry by name.
  111.      *
  112.      * @param name
  113.      *            the name of the reference to find.
  114.      * @return the index the reference is at. If the entry is not present
  115.      *         returns a negative value. The insertion position for the given
  116.      *         name can be computed from {@code -(index + 1)}.
  117.      */
  118.     public final int find(String name) {
  119.         int high = cnt;
  120.         if (high == 0)
  121.             return -1;
  122.         int low = 0;
  123.         do {
  124.             final int mid = (low + high) >>> 1;
  125.             final int cmp = RefComparator.compareTo(list[mid], name);
  126.             if (cmp < 0)
  127.                 low = mid + 1;
  128.             else if (cmp == 0)
  129.                 return mid;
  130.             else
  131.                 high = mid;
  132.         } while (low < high);
  133.         return -(low + 1);
  134.     }

  135.     /**
  136.      * Determine if a reference is present.
  137.      *
  138.      * @param name
  139.      *            name of the reference to find.
  140.      * @return true if the reference is present; false if it is not.
  141.      */
  142.     public final boolean contains(String name) {
  143.         return 0 <= find(name);
  144.     }

  145.     /**
  146.      * Get a reference object by name.
  147.      *
  148.      * @param name
  149.      *            the name of the reference.
  150.      * @return the reference object; null if it does not exist in this list.
  151.      */
  152.     public final T get(String name) {
  153.         int idx = find(name);
  154.         return 0 <= idx ? get(idx) : null;
  155.     }

  156.     /**
  157.      * Get the reference at a particular index.
  158.      *
  159.      * @param idx
  160.      *            the index to obtain. Must be {@code 0 <= idx < size()}.
  161.      * @return the reference value, never null.
  162.      */
  163.     @SuppressWarnings("unchecked")
  164.     public final T get(int idx) {
  165.         return (T) list[idx];
  166.     }

  167.     /**
  168.      * Obtain a builder initialized with the first {@code n} elements.
  169.      * <p>
  170.      * Copies the first {@code n} elements from this list into a new builder,
  171.      * which can be used by the caller to add additional elements.
  172.      *
  173.      * @param n
  174.      *            the number of elements to copy.
  175.      * @return a new builder with the first {@code n} elements already added.
  176.      */
  177.     public final Builder<T> copy(int n) {
  178.         Builder<T> r = new Builder<>(Math.max(16, n));
  179.         r.addAll(list, 0, n);
  180.         return r;
  181.     }

  182.     /**
  183.      * Obtain a new copy of the list after changing one element.
  184.      * <p>
  185.      * This list instance is not affected by the replacement. Because this
  186.      * method copies the entire list, it runs in O(N) time.
  187.      *
  188.      * @param idx
  189.      *            index of the element to change.
  190.      * @param ref
  191.      *            the new value, must not be null.
  192.      * @return copy of this list, after replacing {@code idx} with {@code ref} .
  193.      */
  194.     public final RefList<T> set(int idx, T ref) {
  195.         Ref[] newList = new Ref[cnt];
  196.         System.arraycopy(list, 0, newList, 0, cnt);
  197.         newList[idx] = ref;
  198.         return new RefList<>(newList, cnt);
  199.     }

  200.     /**
  201.      * Add an item at a specific index.
  202.      * <p>
  203.      * This list instance is not affected by the addition. Because this method
  204.      * copies the entire list, it runs in O(N) time.
  205.      *
  206.      * @param idx
  207.      *            position to add the item at. If negative the method assumes it
  208.      *            was a direct return value from {@link #find(String)} and will
  209.      *            adjust it to the correct position.
  210.      * @param ref
  211.      *            the new reference to insert.
  212.      * @return copy of this list, after making space for and adding {@code ref}.
  213.      */
  214.     public final RefList<T> add(int idx, T ref) {
  215.         if (idx < 0)
  216.             idx = -(idx + 1);

  217.         Ref[] newList = new Ref[cnt + 1];
  218.         if (0 < idx)
  219.             System.arraycopy(list, 0, newList, 0, idx);
  220.         newList[idx] = ref;
  221.         if (idx < cnt)
  222.             System.arraycopy(list, idx, newList, idx + 1, cnt - idx);
  223.         return new RefList<>(newList, cnt + 1);
  224.     }

  225.     /**
  226.      * Remove an item at a specific index.
  227.      * <p>
  228.      * This list instance is not affected by the addition. Because this method
  229.      * copies the entire list, it runs in O(N) time.
  230.      *
  231.      * @param idx
  232.      *            position to remove the item from.
  233.      * @return copy of this list, after making removing the item at {@code idx}.
  234.      */
  235.     public final RefList<T> remove(int idx) {
  236.         if (cnt == 1)
  237.             return emptyList();
  238.         Ref[] newList = new Ref[cnt - 1];
  239.         if (0 < idx)
  240.             System.arraycopy(list, 0, newList, 0, idx);
  241.         if (idx + 1 < cnt)
  242.             System.arraycopy(list, idx + 1, newList, idx, cnt - (idx + 1));
  243.         return new RefList<>(newList, cnt - 1);
  244.     }

  245.     /**
  246.      * Store a reference, adding or replacing as necessary.
  247.      * <p>
  248.      * This list instance is not affected by the store. The correct position is
  249.      * determined, and the item is added if missing, or replaced if existing.
  250.      * Because this method copies the entire list, it runs in O(N + log N) time.
  251.      *
  252.      * @param ref
  253.      *            the reference to store.
  254.      * @return copy of this list, after performing the addition or replacement.
  255.      */
  256.     public final RefList<T> put(T ref) {
  257.         int idx = find(ref.getName());
  258.         if (0 <= idx)
  259.             return set(idx, ref);
  260.         return add(idx, ref);
  261.     }

  262.     /** {@inheritDoc} */
  263.     @Override
  264.     public String toString() {
  265.         StringBuilder r = new StringBuilder();
  266.         r.append('[');
  267.         if (cnt > 0) {
  268.             r.append(list[0]);
  269.             for (int i = 1; i < cnt; i++) {
  270.                 r.append(", "); //$NON-NLS-1$
  271.                 r.append(list[i]);
  272.             }
  273.         }
  274.         r.append(']');
  275.         return r.toString();
  276.     }

  277.     /**
  278.      * Create a {@link Collector} for {@link Ref}.
  279.      *
  280.      * @param mergeFunction
  281.      *            if specified the result will be sorted and deduped.
  282.      * @return {@link Collector} for {@link Ref}
  283.      * @since 5.4
  284.      */
  285.     public static <T extends Ref> Collector<T, ?, RefList<T>> toRefList(
  286.             @Nullable BinaryOperator<T> mergeFunction) {
  287.         return Collector.of(
  288.                 () -> new Builder<>(),
  289.                 Builder<T>::add, (b1, b2) -> {
  290.                     Builder<T> b = new Builder<>();
  291.                     b.addAll(b1);
  292.                     b.addAll(b2);
  293.                     return b;
  294.                 }, (b) -> {
  295.                     if (mergeFunction != null) {
  296.                         b.sort();
  297.                         b.dedupe(mergeFunction);
  298.                     }
  299.                     return b.toRefList();
  300.                 });
  301.     }

  302.     /**
  303.      * Builder to facilitate fast construction of an immutable RefList.
  304.      *
  305.      * @param <T>
  306.      *            type of reference being stored.
  307.      */
  308.     public static class Builder<T extends Ref> {
  309.         private Ref[] list;

  310.         private int size;

  311.         /** Create an empty list ready for items to be added. */
  312.         public Builder() {
  313.             this(16);
  314.         }

  315.         /**
  316.          * Create an empty list with at least the specified capacity.
  317.          *
  318.          * @param capacity
  319.          *            the new capacity; if zero or negative, behavior is the same as
  320.          *            {@link #Builder()}.
  321.          */
  322.         public Builder(int capacity) {
  323.             list = new Ref[Math.max(capacity, 16)];
  324.         }

  325.         /** @return number of items in this builder's internal collection. */
  326.         public int size() {
  327.             return size;
  328.         }

  329.         /**
  330.          * Get the reference at a particular index.
  331.          *
  332.          * @param idx
  333.          *            the index to obtain. Must be {@code 0 <= idx < size()}.
  334.          * @return the reference value, never null.
  335.          */
  336.         @SuppressWarnings("unchecked")
  337.         public T get(int idx) {
  338.             return (T) list[idx];
  339.         }

  340.         /**
  341.          * Remove an item at a specific index.
  342.          *
  343.          * @param idx
  344.          *            position to remove the item from.
  345.          */
  346.         public void remove(int idx) {
  347.             System.arraycopy(list, idx + 1, list, idx, size - (idx + 1));
  348.             size--;
  349.         }

  350.         /**
  351.          * Add the reference to the end of the array.
  352.          * <p>
  353.          * References must be added in sort order, or the array must be sorted
  354.          * after additions are complete using {@link #sort()}.
  355.          *
  356.          * @param ref
  357.          */
  358.         public void add(T ref) {
  359.             if (list.length == size) {
  360.                 Ref[] n = new Ref[size * 2];
  361.                 System.arraycopy(list, 0, n, 0, size);
  362.                 list = n;
  363.             }
  364.             list[size++] = ref;
  365.         }

  366.         /**
  367.          * Add all items from another builder.
  368.          *
  369.          * @param other
  370.          * @since 5.4
  371.          */
  372.         public void addAll(Builder other) {
  373.             addAll(other.list, 0, other.size);
  374.         }

  375.         /**
  376.          * Add all items from a source array.
  377.          * <p>
  378.          * References must be added in sort order, or the array must be sorted
  379.          * after additions are complete using {@link #sort()}.
  380.          *
  381.          * @param src
  382.          *            the source array.
  383.          * @param off
  384.          *            position within {@code src} to start copying from.
  385.          * @param cnt
  386.          *            number of items to copy from {@code src}.
  387.          */
  388.         public void addAll(Ref[] src, int off, int cnt) {
  389.             if (list.length < size + cnt) {
  390.                 Ref[] n = new Ref[Math.max(size * 2, size + cnt)];
  391.                 System.arraycopy(list, 0, n, 0, size);
  392.                 list = n;
  393.             }
  394.             System.arraycopy(src, off, list, size, cnt);
  395.             size += cnt;
  396.         }

  397.         /**
  398.          * Replace a single existing element.
  399.          *
  400.          * @param idx
  401.          *            index, must have already been added previously.
  402.          * @param ref
  403.          *            the new reference.
  404.          */
  405.         public void set(int idx, T ref) {
  406.             list[idx] = ref;
  407.         }

  408.         /** Sort the list's backing array in-place. */
  409.         public void sort() {
  410.             Arrays.sort(list, 0, size, RefComparator.INSTANCE);
  411.         }

  412.         /**
  413.          * Dedupe the refs in place. Must be called after {@link #sort}.
  414.          *
  415.          * @param mergeFunction
  416.          */
  417.         @SuppressWarnings("unchecked")
  418.         void dedupe(BinaryOperator<T> mergeFunction) {
  419.             if (size == 0) {
  420.                 return;
  421.             }
  422.             int lastElement = 0;
  423.             for (int i = 1; i < size; i++) {
  424.                 if (RefComparator.INSTANCE.compare(list[lastElement],
  425.                         list[i]) == 0) {
  426.                     list[lastElement] = mergeFunction
  427.                             .apply((T) list[lastElement], (T) list[i]);
  428.                 } else {
  429.                     list[lastElement + 1] = list[i];
  430.                     lastElement++;
  431.                 }
  432.             }
  433.             size = lastElement + 1;
  434.             Arrays.fill(list, size, list.length, null);
  435.         }

  436.         /** @return an unmodifiable list using this collection's backing array. */
  437.         public RefList<T> toRefList() {
  438.             return new RefList<>(list, size);
  439.         }

  440.         @Override
  441.         public String toString() {
  442.             return toRefList().toString();
  443.         }
  444.     }
  445. }