// Copyright 2012 Google Inc. All Rights Reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package org.eclipse.che.ide.util; import org.eclipse.che.ide.collections.ListHelper; import org.eclipse.che.ide.runtime.Assert; import java.util.ArrayList; import java.util.List; /** * List that is sorted based on the given {@link Comparator}. * <p/> * Insertions and removal of an instance are O(N). */ public class SortedList<T> { /** * Compares two items and returns a value following the same contract as * {@link Comparable#compareTo(Object)}. * * @param <T> * the type of the items being compared */ public interface Comparator<T> { int compare(T a, T b); } /** * Compares some (opaque) value to other items' values. This can be used to * efficiently find an item based on a value. * * @param <T> * the type of items being compared */ public interface OneWayComparator<T> { int compareTo(T o); } /** Simple {@link SortedList.OneWayComparator} that has an int-based value. */ public abstract static class OneWayIntComparator<T> implements OneWayComparator<T> { protected int value; public void setValue(int value) { this.value = value; } } /** Simple {@link SortedList.OneWayComparator} that has a double-based value. */ public static abstract class OneWayDoubleComparator<T> implements SortedList.OneWayComparator<T> { protected double value; public void setValue(double value) { this.value = value; } } private final class ComparatorDelegator implements OneWayComparator<T> { T a; @Override public int compareTo(T o) { return comparator.compare(a, o); } } private static final boolean ENSURE_SORTED_PRECONDITIONS_ENABLED = false; private List<T> list; private final Comparator<T> comparator; private final ComparatorDelegator comparatorDelegator = new ComparatorDelegator(); public SortedList(Comparator<T> comparator) { this.list = new ArrayList<>(); this.comparator = comparator; } /** * Adds an item to the list. Performance is O(lg N). * * @param item * item to add * @return index at which item was added into the list */ public int add(T item) { int index = findInsertionIndex(item); ListHelper.splice(list, index, 0, item); ensureSortedIfEnabled(); return index; } /** Clears the list. */ public void clear() { list.clear(); } /** * Returns the first item in the list matched by the comparator, or null if * there were no matches. */ public T find(OneWayComparator<T> comparator) { int index = findInsertionIndex(comparator); if (index < size()) { T item = get(index); if (comparator.compareTo(item) == 0) { return item; } } return null; } /** * Returns the index where the item with the value would be inserted. The * actual value is unknown to this method; it delegates comparison to the * given {@code comparator}. * <p/> * If an item with the same value already exists in the list, the returned * index will be the first appearance of the value in the list. If the value * does not exist in the list, the returned index will be the first value * greater than the value being compared, or the size of the list if there is * no greater value. * <p/> * An alternate approach to the OneWayComparator used here is to have the * SortedList take another type parameter for the type of the value, and have * a findInsertionIndex(V value) method. Unfortunately, it is common to have * primitives for the value, so we would be boxing unnecessarily. * * @see #findInsertionIndex(OneWayComparator, boolean) */ public int findInsertionIndex(OneWayComparator<T> comparator) { /* * Return the greater value, since that will be the insertion index. * Remember that if we get here, lower > upper (look at the while loop * above). */ return findInsertionIndex(comparator, true); } /** * Returns the index where the item with the value would be inserted. The * actual value is unknown to this method; it delegates comparison to the * given {@code comparator}. * <p/> * If an item with the same value already exists in the list, the returned * index will be the first appearance of the value in the list. If the value * does not exist in the list, see {@code greaterItemIfNoMatch}. * * @param greaterItemIfNoMatch * in the case that the given comparator's value * does not match an item in the list exactly, this parameter defines * which of the items to return. If true, the item that is greater than * the comparator's value will be returned. If false, the item that is * less than the comparator's value will be returned. */ public int findInsertionIndex(OneWayComparator<T> comparator, boolean greaterItemIfNoMatch) { int insertionPoint = binarySearch(list, comparator); if (insertionPoint >= 0) { return insertionPoint; } insertionPoint = -insertionPoint - 1; return greaterItemIfNoMatch ? insertionPoint : insertionPoint - 1; } /** @see #findInsertionIndex(OneWayComparator) */ public int findInsertionIndex(final T item) { comparatorDelegator.a = item; return findInsertionIndex(comparatorDelegator); } /** * Finds index of a given item in the list, or {@code -1}, if not found. * * @param item * the item to search for * @return index of item in the list, or {@code -1} if not found */ public int findIndex(T item) { for (int i = findInsertionIndex(item); i < list.size() && comparator.compare(item, list.get(i)) == 0; i++) { if (list.get(i).equals(item)) { return i; } } return -1; } /** Returns the item at the given {@code index}. */ public T get(int index) { return list.get(index); } /** Removes the item at the given {@code index}. */ public T remove(int index) { T item = list.remove(index); ensureSortedIfEnabled(); return item; } /** Removes the items starting at {@code index} through to the end. */ public List<T> removeThisAndFollowing(int index) { List<T> items = removeSublist(index, list.size() - index); ensureSortedIfEnabled(); return items; } /** Removes the items starting at {@code index} through to the end. */ public List<T> removeSublist(int index, int sublistSize) { ListHelper.splice(list, index, sublistSize); ensureSortedIfEnabled(); return list; } /** Removes the given {@code item}. Performance is O(lg N). */ public boolean remove(T item) { int index = findIndex(item); if (index >= 0) { list.remove(index); ensureSortedIfEnabled(); return true; } return false; } /** @return the size of this list */ public int size() { return list.size(); } /** @return copy of this list as an list */ public List<T> toArray() { List<T> copy = new ArrayList<>(); java.util.Collections.copy(copy, list); return copy; } /** * Ensures that the item that is currently at index {@code index} is * positioned properly in the list. * <p/> * If an item was changed and its position could now be stale, you should call * this method. */ public void repositionItem(int index) { // TODO: naive impl T item = remove(index); ensureSortedIfEnabled(); add(item); ensureSortedIfEnabled(); } public static <T> int binarySearch(List<T> list, OneWayComparator<T> comparator) { int lower = 0; int upper = list.size() - 1; while (lower <= upper) { int middle = lower + (upper - lower) / 2; int c = comparator.compareTo(list.get(middle)); if (c < 0) { upper = middle - 1; } else if (c > 0) { lower = middle + 1; } else { /* * We have an exact match at middle, but there may be more exact matches * before us, so we want to find the first exact match. */ // Move backward to the first non-equal value int i = middle; while (i >= 0 && comparator.compareTo(list.get(i)) == 0) { i--; } // Forward one to the first equal value return i + 1; } } return -lower - 1; } public final void ensureSortedIfEnabled() { if (ENSURE_SORTED_PRECONDITIONS_ENABLED) { for (int i = 1; i < list.size(); i++) { Assert.isTrue(comparator.compare(list.get(i - 1), list.get(i)) <= 0); } } } }