/* * Copyright 2015 Goldman Sachs. * * 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 com.gs.collections.impl.bag.sorted.immutable; import java.io.Serializable; import java.lang.reflect.Array; import java.util.Arrays; import java.util.Comparator; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.RandomAccess; import com.gs.collections.api.bag.Bag; import com.gs.collections.api.bag.sorted.ImmutableSortedBag; import com.gs.collections.api.bag.sorted.MutableSortedBag; import com.gs.collections.api.bag.sorted.SortedBag; import com.gs.collections.api.block.function.Function; import com.gs.collections.api.block.predicate.Predicate; import com.gs.collections.api.block.predicate.Predicate2; import com.gs.collections.api.block.procedure.Procedure; import com.gs.collections.api.block.procedure.primitive.ObjectIntProcedure; import com.gs.collections.api.list.MutableList; import com.gs.collections.api.map.sorted.MutableSortedMap; import com.gs.collections.api.ordered.OrderedIterable; import com.gs.collections.api.partition.bag.sorted.PartitionImmutableSortedBag; import com.gs.collections.api.set.sorted.ImmutableSortedSet; import com.gs.collections.api.set.sorted.MutableSortedSet; import com.gs.collections.impl.Counter; import com.gs.collections.impl.bag.sorted.mutable.TreeBag; import com.gs.collections.impl.block.factory.Comparators; import com.gs.collections.impl.factory.SortedBags; import com.gs.collections.impl.factory.SortedSets; import com.gs.collections.impl.map.sorted.mutable.TreeSortedMap; import com.gs.collections.impl.partition.bag.sorted.PartitionImmutableSortedBagImpl; import com.gs.collections.impl.partition.bag.sorted.PartitionTreeBag; import com.gs.collections.impl.utility.ArrayIterate; import com.gs.collections.impl.utility.ListIterate; import com.gs.collections.impl.utility.internal.SortedBagIterables; import net.jcip.annotations.Immutable; @Immutable class ImmutableSortedBagImpl<T> extends AbstractImmutableSortedBag<T> implements Serializable { private static final long serialVersionUID = 1L; private final T[] elements; private final int[] occurrences; private final Comparator<? super T> comparator; private final int size; ImmutableSortedBagImpl(SortedBag<T> sortedBag) { if (sortedBag.isEmpty()) { throw new IllegalArgumentException(); } this.comparator = sortedBag.comparator(); this.elements = (T[]) new Object[sortedBag.sizeDistinct()]; this.occurrences = new int[sortedBag.sizeDistinct()]; this.size = sortedBag.size(); sortedBag.forEachWithOccurrences(new ObjectIntProcedure<T>() { private int i; public void value(T each, int occurrencesOfEach) { ImmutableSortedBagImpl.this.elements[this.i] = each; ImmutableSortedBagImpl.this.occurrences[this.i] = occurrencesOfEach; this.i++; } }); } private ImmutableSortedBagImpl(T[] elements, int[] occurrences, Comparator<? super T> comparator) { if (elements.length != occurrences.length) { throw new IllegalArgumentException(); } this.comparator = comparator; this.elements = elements; this.occurrences = occurrences; int size = 0; for (int occurrence : occurrences) { size += occurrence; } this.size = size; } public ImmutableSortedBag<T> newWith(T element) { int index = Arrays.binarySearch(this.elements, element, this.comparator); if (index >= 0) { int[] occurrences = this.occurrences.clone(); occurrences[index] += 1; return new ImmutableSortedBagImpl<T>(this.elements.clone(), occurrences, this.comparator); } int insertionPoint = (index + 1) * -1; T[] elements = (T[]) new Object[this.elements.length + 1]; int[] occurrences = new int[this.occurrences.length + 1]; System.arraycopy(this.elements, 0, elements, 0, insertionPoint); System.arraycopy(this.occurrences, 0, occurrences, 0, insertionPoint); elements[insertionPoint] = element; occurrences[insertionPoint] = 1; System.arraycopy(this.elements, insertionPoint, elements, insertionPoint + 1, this.elements.length - insertionPoint); System.arraycopy(this.occurrences, insertionPoint, occurrences, insertionPoint + 1, this.occurrences.length - insertionPoint); return new ImmutableSortedBagImpl<T>(elements, occurrences, this.comparator); } public ImmutableSortedBag<T> newWithout(T element) { int index = Arrays.binarySearch(this.elements, element, this.comparator); if (index < 0) { return this; } if (this.occurrences[index] > 1) { int[] occurrences = this.occurrences.clone(); occurrences[index] -= 1; return new ImmutableSortedBagImpl<T>(this.elements.clone(), occurrences, this.comparator); } T[] elements = (T[]) new Object[this.elements.length - 1]; int[] occurrences = new int[this.occurrences.length - 1]; System.arraycopy(this.elements, 0, elements, 0, index); System.arraycopy(this.occurrences, 0, occurrences, 0, index); System.arraycopy(this.elements, index + 1, elements, index, elements.length - index); System.arraycopy(this.occurrences, index + 1, occurrences, index, occurrences.length - index); return new ImmutableSortedBagImpl<T>(elements, occurrences, this.comparator); } public ImmutableSortedBag<T> newWithAll(Iterable<? extends T> elements) { MutableSortedBag<T> result = TreeBag.newBag(this); result.addAllIterable(elements); return result.toImmutable(); } public Comparator<? super T> comparator() { return this.comparator; } @Override public T min() { return ArrayIterate.min(this.elements); } @Override public T min(Comparator<? super T> comparator) { return ArrayIterate.min(this.elements, comparator); } @Override public <V extends Comparable<? super V>> T minBy(Function<? super T, ? extends V> function) { return ArrayIterate.minBy(this.elements, function); } @Override public T max() { return ArrayIterate.max(this.elements); } @Override public T max(Comparator<? super T> comparator) { return ArrayIterate.max(this.elements, comparator); } @Override public <V extends Comparable<? super V>> T maxBy(Function<? super T, ? extends V> function) { return ArrayIterate.maxBy(this.elements, function); } public ImmutableSortedBag<T> takeWhile(Predicate<? super T> predicate) { MutableSortedBag<T> bag = TreeBag.newBag(this.comparator); for (int i = 0; i < this.elements.length; i++) { if (predicate.accept(this.elements[i])) { bag.addOccurrences(this.elements[i], this.occurrences[i]); } else { return bag.toImmutable(); } } return bag.toImmutable(); } public ImmutableSortedBag<T> dropWhile(Predicate<? super T> predicate) { MutableSortedBag<T> bag = TreeBag.newBag(this.comparator); int startIndex = this.detectNotIndex(predicate); for (int i = startIndex; i < this.elements.length; i++) { bag.addOccurrences(this.elements[i], this.occurrences[i]); } return bag.toImmutable(); } public int detectIndex(Predicate<? super T> predicate) { int result = 0; for (int i = 0; i < this.elements.length; i++) { if (predicate.accept(this.elements[i])) { return result; } result += this.occurrences[i]; } return -1; } private int detectNotIndex(Predicate<? super T> predicate) { for (int index = 0; index < this.elements.length; index++) { if (!predicate.accept(this.elements[index])) { return index; } } return this.elements.length; } public PartitionImmutableSortedBag<T> partitionWhile(Predicate<? super T> predicate) { PartitionTreeBag<T> result = new PartitionTreeBag<T>(this.comparator()); MutableSortedBag<T> selected = result.getSelected(); MutableSortedBag<T> rejected = result.getRejected(); int partitionIndex = this.detectNotIndex(predicate); for (int i = 0; i < partitionIndex; i++) { selected.addOccurrences(this.elements[i], this.occurrences[i]); } for (int j = partitionIndex; j < this.elements.length; j++) { rejected.addOccurrences(this.elements[j], this.occurrences[j]); } return new PartitionImmutableSortedBagImpl<T>(result); } public void forEachWithOccurrences(ObjectIntProcedure<? super T> procedure) { for (int i = 0; i < this.occurrences.length; i++) { procedure.value(this.elements[i], this.occurrences[i]); } } public int sizeDistinct() { return this.elements.length; } public int size() { return this.size; } public int indexOf(Object object) { int result = 0; for (int i = 0; i < this.elements.length; i++) { if (object.equals(this.elements[i])) { return result; } result += this.occurrences[i]; } return -1; } public T getFirst() { return ArrayIterate.getFirst(this.elements); } public T getLast() { return ArrayIterate.getLast(this.elements); } public void forEach(int fromIndex, int toIndex, Procedure<? super T> procedure) { int index = fromIndex; ListIterate.rangeCheck(index, toIndex, this.size()); if (index > toIndex) { throw new IllegalArgumentException("fromIndex must not be greater than toIndex"); } int i = 0; int beginningIndex = 0; while (beginningIndex <= index) { beginningIndex += this.occurrences[i]; if (beginningIndex <= index) { i++; } } int numberOfIterations = beginningIndex - index; for (int j = 0; j < numberOfIterations && index <= toIndex; j++) { procedure.value(this.elements[i]); index++; } while (index <= toIndex) { i++; for (int j = 0; j < this.occurrences[i] && index <= toIndex; j++) { procedure.value(this.elements[i]); index++; } } } public void each(Procedure<? super T> procedure) { for (int i = 0; i < this.elements.length; i++) { T element = this.elements[i]; int occurrences = this.occurrences[i]; for (int j = 0; j < occurrences; j++) { procedure.value(element); } } } @Override public void forEachWithIndex(ObjectIntProcedure<? super T> objectIntProcedure) { int index = 0; for (int i = 0; i < this.elements.length; i++) { for (int j = 0; j < this.occurrences[i]; j++) { objectIntProcedure.value(this.elements[i], index); index++; } } } public void forEachWithIndex(int fromIndex, int toIndex, ObjectIntProcedure<? super T> objectIntProcedure) { ListIterate.rangeCheck(fromIndex, toIndex, this.size()); if (fromIndex > toIndex) { throw new IllegalArgumentException("fromIndex must not be greater than toIndex"); } int arrayIndex = 0; int index = fromIndex; int i = 0; while (i <= index) { i += this.occurrences[arrayIndex]; if (i <= index) { arrayIndex++; } } int numberOfIterations = i - index; for (int j = 0; j < numberOfIterations && index <= toIndex; j++) { objectIntProcedure.value(this.elements[arrayIndex], index); index++; } while (index <= toIndex) { arrayIndex++; for (int j = 0; j < this.occurrences[arrayIndex] && index <= toIndex; j++) { objectIntProcedure.value(this.elements[arrayIndex], index); index++; } } } public int occurrencesOf(Object item) { int index = Arrays.binarySearch(this.elements, (T) item, this.comparator); if (index > -1) { return this.occurrences[index]; } return 0; } public ImmutableSortedSet<T> distinct() { return SortedSets.immutable.with(this.comparator(), this.elements); } public <S> boolean corresponds(OrderedIterable<S> other, Predicate2<? super T, ? super S> predicate) { if (this.size != other.size()) { return false; } if (other instanceof RandomAccess) { List<S> otherList = (List<S>) other; int otherListIndex = 0; for (int i = 0; i < this.elements.length; i++) { for (int j = 0; j < this.occurrences[i]; j++) { if (!predicate.accept(this.elements[i], otherList.get(otherListIndex))) { return false; } otherListIndex++; } } return true; } Iterator<S> otherIterator = other.iterator(); for (int i = 0; i < this.elements.length; i++) { for (int j = 0; j < this.occurrences[i]; j++) { if (!predicate.accept(this.elements[i], otherIterator.next())) { return false; } } } return true; } @Override public MutableList<T> toSortedList() { return this.toSortedList(Comparators.naturalOrder()); } @Override public MutableSortedSet<T> toSortedSet() { return SortedSets.mutable.with(this.elements); } @Override public MutableSortedSet<T> toSortedSet(Comparator<? super T> comparator) { return SortedSets.mutable.with(comparator, this.elements); } @Override public Object[] toArray() { final Object[] result = new Object[this.size()]; this.each(new Procedure<T>() { private int i; public void value(T each) { result[this.i] = each; this.i++; } }); return result; } @Override public <E> E[] toArray(E[] array) { E[] array1 = array; if (array1.length < this.size) { array1 = (E[]) Array.newInstance(array1.getClass().getComponentType(), this.size); } T[] items = (T[]) this.toArray(); System.arraycopy(items, 0, array1, 0, this.size); if (array1.length > this.size) { array1[this.size] = null; } return array1; } public MutableSortedMap<T, Integer> toMapOfItemToCount() { final MutableSortedMap<T, Integer> map = TreeSortedMap.newMap(this.comparator()); this.forEachWithOccurrences(new ObjectIntProcedure<T>() { public void value(T item, int count) { map.put(item, count); } }); return map; } public int compareTo(SortedBag<T> otherBag) { return SortedBagIterables.compare(this, otherBag); } @Override public boolean allSatisfy(Predicate<? super T> predicate) { return ArrayIterate.allSatisfy(this.elements, predicate); } @Override public <P> boolean allSatisfyWith(Predicate2<? super T, ? super P> predicate, P parameter) { return ArrayIterate.allSatisfyWith(this.elements, predicate, parameter); } @Override public boolean anySatisfy(Predicate<? super T> predicate) { return ArrayIterate.anySatisfy(this.elements, predicate); } @Override public <P> boolean anySatisfyWith(Predicate2<? super T, ? super P> predicate, P parameter) { return ArrayIterate.anySatisfyWith(this.elements, predicate, parameter); } @Override public boolean noneSatisfy(Predicate<? super T> predicate) { return ArrayIterate.noneSatisfy(this.elements, predicate); } @Override public <P> boolean noneSatisfyWith(Predicate2<? super T, ? super P> predicate, P parameter) { return ArrayIterate.noneSatisfyWith(this.elements, predicate, parameter); } @Override public T detect(Predicate<? super T> predicate) { return ArrayIterate.detect(this.elements, predicate); } @Override public boolean contains(Object object) { return Arrays.binarySearch(this.elements, (T) object, this.comparator) >= 0; } public Iterator<T> iterator() { return new InternalIterator(); } public ImmutableSortedBag<T> take(int count) { if (count < 0) { throw new IllegalArgumentException("Count must be greater than zero, but was: " + count); } if (count == 0) { return SortedBags.immutable.empty(this.comparator()); } if (count >= this.size()) { return this; } MutableSortedBag<T> output = TreeBag.newBag(this.comparator()); int index = 0; for (int i = 0; i < this.elements.length; i++) { for (int j = 0; j < this.occurrences[i]; j++) { output.add(this.elements[i]); index++; if (index >= count) { return output.toImmutable(); } } } throw new AssertionError(); } public ImmutableSortedBag<T> drop(int count) { if (count < 0) { throw new IllegalArgumentException("Count must be greater than zero, but was: " + count); } if (count == 0) { return this; } if (count >= this.size()) { return SortedBags.immutable.empty(this.comparator()); } MutableSortedBag<T> output = TreeBag.newBag(this.comparator()); int index = 0; for (int i = 0; i < this.elements.length; i++) { for (int j = 0; j < this.occurrences[i]; j++) { if (index >= count) { output.add(this.elements[i]); } index++; } } return output.toImmutable(); } @Override public boolean equals(Object other) { if (this == other) { return true; } if (!(other instanceof Bag)) { return false; } Bag<?> bag = (Bag<?>) other; if (this.sizeDistinct() != bag.sizeDistinct()) { return false; } for (int i = 0; i < this.elements.length; i++) { if (bag.occurrencesOf(this.elements[i]) != this.occurrences[i]) { return false; } } return true; } @Override public int hashCode() { final Counter counter = new Counter(); this.forEachWithOccurrences(new ObjectIntProcedure<T>() { public void value(T each, int count) { counter.add((each == null ? 0 : each.hashCode()) ^ count); } }); return counter.getCount(); } private class InternalIterator implements Iterator<T> { private int position; private int occurrencesRemaining = ImmutableSortedBagImpl.this.isEmpty() ? 0 : ImmutableSortedBagImpl.this.occurrences[0]; public boolean hasNext() { return this.position < ImmutableSortedBagImpl.this.elements.length - 1 || this.occurrencesRemaining != 0; } public T next() { if (!this.hasNext()) { throw new NoSuchElementException(); } if (this.occurrencesRemaining == 0) { this.position++; this.occurrencesRemaining = ImmutableSortedBagImpl.this.occurrences[this.position]; } this.occurrencesRemaining--; return ImmutableSortedBagImpl.this.elements[this.position]; } public void remove() { throw new UnsupportedOperationException("Cannot call remove() on " + this.getClass().getSimpleName()); } } }