record Can_Multiple()

in commons/src/main/java/org/apache/causeway/commons/collections/Can_Multiple.java [57:532]


record Can_Multiple<T>(List<T> elements) implements Can<T> {

    // package private, but wrapped just in case
    public List<T> elements() {
        return Collections.unmodifiableList(elements);
    }

    @Override
    public Optional<T> getFirst() {
        return Optional.of(elements.get(0));
    }

    @Override
    public Optional<T> getLast() {
        return Optional.of(elements.get(size()-1));
    }

    @Override
    public Cardinality getCardinality() {
        return Cardinality.MULTIPLE;
    }

    @Override
    public Stream<T> stream() {
        return elements.stream();
    }

    @Override
    public Stream<T> parallelStream() {
        return elements.parallelStream();
    }

    @Override
    public Optional<T> getSingleton() {
        return Optional.empty();
    }

    @Override
    public int size() {
        return elements.size();
    }

    @Override
    public boolean contains(final @Nullable T element) {
        if(element==null) {
            return false; // Can's dont't contain null
        }
        return elements.contains(element);
    }

    @Override
    public Optional<T> get(final int elementIndex) {
        // we do an index out of bounds check ourselves, in order to prevent any stack-traces,
        // that pollute the heap
        var size = size();
        if(size==0) {
            return Optional.empty();
        }
        var minIndex = 0;
        var maxIndex = size - 1;
        if(elementIndex < minIndex ||  elementIndex > maxIndex) {
            return Optional.empty();
        }
        return Optional.of(elements.get(elementIndex));
    }

    @Override
    public Can<T> sorted(final @NonNull Comparator<? super T> c) {
        var newElements = _Lists.<T>newArrayList(elements);
        newElements.sort(c);
        return new Can_Multiple<>(newElements);
    }

    @Override
    public Can<T> distinct() {
        var set = new LinkedHashSet<T>(); // preserve order
        set.addAll(elements);
        return Can.ofCollection(set);
    }

    @Override
    public Can<T> distinct(final @NonNull BiPredicate<T, T> equality) {
        final int initialSize = Math.min(1024, elements.size());
        var uniqueElements = _Lists.<T>newArrayList(initialSize);
        elements
        .forEach(element->{
            if(!uniqueElements.stream().anyMatch(x->equality.test(x, element))) {
                uniqueElements.add(element);
            }
        });
        return _CanFactory.ofNonNullElements(uniqueElements);
    }

    @Override
    public Iterator<T> iterator(final int skip, final int limit) {
        return Collections.unmodifiableList(elements).stream()
            .skip(skip)
            .limit(limit)
            .iterator();
    }

    @Override
    public Iterator<T> iterator() {
        return Collections.unmodifiableList(elements).iterator();
    }

    @Override
    public Iterator<T> reverseIterator() {
        return new Iterator<T>() {
            private int remainingCount = size();
            @Override public boolean hasNext() { return remainingCount>0; }
            @Override public T next() {
                if(!hasNext()) { throw _Exceptions.noSuchElement(); }
                return elements.get(--remainingCount);
            }
        };
    }

    @Override
    public Can<T> reverse() {
        var reverse = new ArrayList<T>(elements.size());
        for(int i=elements.size()-1; i>=0; --i) {
            reverse.add(elements.get(i));
        }
        return new Can_Multiple<>(reverse);
    }

    @Override
    public Can<T> reduce(final @NonNull BinaryOperator<T> accumulator) {
        return this.stream().reduce(accumulator)
                .<Can<T>>map(Can_Singleton::new)
                .orElseGet(Can::empty);
    }

    @Override
    public void forEach(final @NonNull Consumer<? super T> action) {
        elements.forEach(action);
    }

    @Override
    public Can<T> filter(final @Nullable Predicate<? super T> predicate) {
        if(predicate==null) {
            return this; // identity
        }
        var filteredElements =
                stream()
                .filter(predicate)
                .collect(Collectors.toCollection(ArrayList::new));

        // optimization for the case when the filter accepted all
        if(filteredElements.size()==size()) {
            return this; // identity
        }
        return Can.ofCollection(filteredElements);
    }

    @Override
    public <R> void zip(final @NonNull Iterable<R> zippedIn, final @NonNull BiConsumer<? super T, ? super R> action) {
        var zippedInIterator = zippedIn.iterator();
        stream().forEach(t->{
            action.accept(t, zippedInIterator.next());
        });
    }

    @Override
    public <R, Z> Can<R> zipMap(final @NonNull Iterable<Z> zippedIn, final @NonNull BiFunction<? super T, ? super Z, R> mapper) {
        var zippedInIterator = zippedIn.iterator();
        return map(t->mapper.apply(t, zippedInIterator.next()));
    }

    @Override
    public <R, Z> Stream<R> zipStream(final @NonNull Iterable<Z> zippedIn, final BiFunction<? super T, ? super Z, R> mapper) {
        var zippedInIterator = zippedIn.iterator();
        return stream()
                .map(t->mapper.apply(t, zippedInIterator.next()))
                .filter(_NullSafe::isPresent);
    }

    @Override
    public Can<T> add(final @Nullable T element) {
        return element!=null
                ? Can.ofStream(Stream.concat(elements.stream(), Stream.of(element))) // append
                : this;
    }

    @Override
    public Can<T> addAll(final @Nullable Can<T> other) {
        if(other==null
                || other.isEmpty()) {
            return this;
        }
        var newElements = new ArrayList<T>(this.size() + other.size());
        newElements.addAll(elements);
        other.forEach(newElements::add);
        return new Can_Multiple<>(newElements);
    }

    @Override
    public Can<T> add(final int index, final @Nullable T element) {
        if(element==null) {
            return this; // identity
        }
        var newElements = new ArrayList<T>(elements);
        newElements.add(index, element);
        return Can.ofCollection(newElements);
    }

    @Override
    public Can<T> replace(final int index, final @Nullable T element) {
        if(element==null) {
            return remove(index);
        }
        var newElements = new ArrayList<T>(elements);
        newElements.set(index, element);
        return Can.ofCollection(newElements);
    }

    @Override
    public Can<T> remove(final int index) {
        var newElements = new ArrayList<T>(elements);
        newElements.remove(index);
        return Can.ofCollection(newElements);
    }

    @Override
    public Can<T> remove(final @Nullable T element) {
        if(element==null) {
            return this; // identity
        }
        var newElements = new ArrayList<T>(elements);
        newElements.remove(element);
        return Can.ofCollection(newElements);
    }

    @Override
    public Can<T> pickByIndex(final @Nullable int... indices) {
        if(indices==null
                ||indices.length==0) {
            return Can.empty();
        }
        var newElements = new ArrayList<T>(indices.length);
        final int maxIndex = size()-1;
        for(int index:indices) {
            if(index>=0
                    && index<=maxIndex) {
                newElements.add(elements.get(index));
            }
        }
        return Can.ofCollection(newElements);
    }

    @Override
    public Can<T> pickByIndex(final @Nullable IntStream intStream) {
        if(intStream==null) {
            return Can.empty();
        }
        var newElements = new ArrayList<T>();
        final int maxIndex = size()-1;
        intStream
        .filter(index->index>=0 && index<=maxIndex)
        .forEach(index->{
            newElements.add(elements.get(index));
        });
        return _CanFactory.ofNonNullElements(newElements);
    }

    @Override
    public Can<T> subCan(final int startInclusive) {
        return pickByIndex(IntStream.range(startInclusive, size()));
    }

    @Override
    public Can<T> subCan(final int startInclusive, final int endExclusive) {
        final int upperBoundExclusive = endExclusive < 0
                ? size() + endExclusive
                : endExclusive;
        if (startInclusive >= upperBoundExclusive) {
            return Can.empty();
        }
        return pickByIndex(IntStream.range(startInclusive, upperBoundExclusive));
    }

    @Override
    public Can<Can<T>> partitionInnerBound(final int maxInnerSize) {
        if(maxInnerSize<1) {
            throw _Exceptions.illegalArgument("maxInnerSize %d must be greater or equal to 1", maxInnerSize);
        }
        final int n = size();
        final int subCanCount = (n - 1)/maxInnerSize + 1;
        var newElements = new ArrayList<Can<T>>(subCanCount);
        for(int i=0; i<n; i+=maxInnerSize) {
            newElements.add(subCan(i, i + maxInnerSize)); // index overflow is ignored
        }
        return _CanFactory.ofNonNullElements(newElements);
    }

    @Override
    public Can<Can<T>> partitionOuterBound(final int outerSizeYield) {
        if(outerSizeYield<1) {
            throw _Exceptions.illegalArgument("outerSizeYield %d must be greater or equal to 1", outerSizeYield);
        }
        final int n = size();
        final int maxInnerSize = (n - 1)/outerSizeYield + 1;
        return partitionInnerBound(maxInnerSize);
    }

    @Override
    public int indexOf(final @Nullable T element) {
        return this.elements.indexOf(element);
    }

    @Override
    public boolean anyMatch(final Predicate<? super T> predicate) {
        // equivalent to stream().anyMatch(predicate); hoping the loop is faster..
        for(var el : elements) if(predicate.test(el)) return true;
        return false;
    }
    @Override
    public boolean allMatch(final Predicate<? super T> predicate) {
        // equivalent to stream().allMatch(predicate); hoping the loop is faster..
        for(var el : elements) if(!predicate.test(el)) return false;
        return true;
    }

    @Override
    public String toString() {
        var literal = stream()
                .map(s->""+s)
                .collect(Collectors.joining(", "));
        return "Can["+literal+"]";
    }

    @Override
    public boolean equals(final Object obj) {
        if(obj instanceof Can) {
            return ((Can<?>) obj).isEqualTo(this);
        }
        return false;
    }

    @Override
    public int hashCode() {
        return elements.hashCode();
    }

    @Override
    public int compareTo(final @Nullable Can<T> other) {
        // when returning
        // -1 ... this (multi-can) is before other
        // +1 ... this (multi-can) is after other
        if(other==null
                || other.isEmpty()) {
            return 1; // all empty Cans are same and come first
        }
        if(other.isCardinalityOne()) {
            final int firstElementComparison = _Objects.compareNonNull(
                    this.elements.get(0),
                    other.getSingletonOrFail());
            if(firstElementComparison!=0) {
                return firstElementComparison;
            }
        }
        // at this point firstElementComparison is 0 and other is a multi-can
        // XXX we already compared the first elements, could skip ahead for performance reasons
        if(this.size()>=other.size()) {
            var otherIterator = other.iterator();
            for(T left: this) {
                if(!otherIterator.hasNext()) {
                    return 1; // the other has fewer elements hence comes first
                }
                var right = otherIterator.next();
                int c = _Objects.compareNonNull(left, right);
                if(c!=0) {
                    return c;
                }
            }
        } else {
            var thisIterator = this.iterator();
            for(T right: other) {
                if(!thisIterator.hasNext()) {
                    return -1; // this has fewer elements hence comes first
                }
                var left = thisIterator.next();
                int c = _Objects.compareNonNull(left, right);
                if(c!=0) {
                    return c;
                }
            }
        }
        return 0; // we compared all elements and found no difference
    }

    @Override
    public List<T> toList() {
        return Collections.unmodifiableList(elements); // serializable and immutable
    }

    @Override
    public List<T> toArrayList() {
        return _Lists.newArrayList(elements);
    }

    @Override
    public Set<T> toSet() {
        var set = _Sets.<T>newHashSet(); // serializable
        set.addAll(elements);
        return Collections.unmodifiableSet(set); // serializable and immutable
    }

    @Override
    public Set<T> toSet(final @NonNull Consumer<T> onDuplicated) {
        var set = _Sets.<T>newHashSet(); // serializable
        elements
        .forEach(s->{
            if(!set.add(s)) {
                onDuplicated.accept(s);
            }
        });
        return Collections.unmodifiableSet(set); // serializable and immutable
    }

    @Override
    public T[] toArray(final @NonNull Class<T> elementType) {
        var array = _Casts.<T[]>uncheckedCast(Array.newInstance(elementType, size()));
        return elements.toArray(array);
    }

    @Override
    public <K> Map<K, T> toMap(
            final @NonNull Function<? super T, ? extends K> keyExtractor) {
        Map<K, T> map = collect(Collectors.toMap(keyExtractor, UnaryOperator.identity()));
        return Collections.unmodifiableMap(map);
    }
    @Override
    public <K, M extends Map<K, T>> Map<K, T> toMap(
            final @NonNull Function<? super T, ? extends K> keyExtractor,
            final @NonNull BinaryOperator<T> mergeFunction,
            final @NonNull Supplier<M> mapFactory) {
        Map<K, T> map = collect(Collectors.toMap(
                keyExtractor, UnaryOperator.identity(), mergeFunction, mapFactory));
        return Collections.unmodifiableMap(map);
    }

    @Override
    public <R, A> R collect(final @NonNull Collector<? super T, A, R> collector) {
        return stream().collect(collector);
    }

    @Override
    public <K> Map<K, Can<T>> groupBy(
            final @NonNull Function<? super T, ? extends K> classifier) {
        return groupBy(classifier, HashMap::new);
    }

    @Override
    public <K, M extends Map<K, Can<T>>> Map<K, Can<T>> groupBy(
            final @NonNull Function<? super T, ? extends K> classifier,
            final @NonNull Supplier<M> mapFactory) {
        var map = collect(Collectors.groupingBy(classifier, mapFactory, Can.toCan()));
        return Collections.unmodifiableMap(map);
    }

    @Override
    public String join(final @NonNull String delimiter) {
        return join(Object::toString, delimiter);
    }

    @Override
    public String join(final @NonNull Function<? super T, String> toStringFunction, final @NonNull String delimiter) {
        return stream()
                .map(toStringFunction)
                .filter(_NullSafe::isPresent)
                .collect(Collectors.joining(delimiter));
    }

}