net.openhft.chronicle.wire.domestic.reduction.ConcurrentCollectors Maven / Gradle / Ivy
/*
* Copyright 2016-2022 chronicle.software
*
* https://chronicle.software
*
* 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 net.openhft.chronicle.wire.domestic.reduction;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collector;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toConcurrentMap;
import static net.openhft.chronicle.core.util.ObjectUtils.requireNonNull;
/**
* Provides utility methods to obtain concurrent {@code Collector}s that can be used
* in conjunction with the Java Stream API. These collectors allow for thread-safe
* accumulation of elements into collections or other aggregate results.
*/
public final class ConcurrentCollectors {
// Suppresses default constructor, ensuring non-instantiability.
private ConcurrentCollectors() {
}
/**
* Returns a concurrent {@code Collector} that reduces the input elements into a
* new {@code List}.
*
* @param the type of the input elements
* @return a {@code Collector} which collects all the input elements into a
* {@code List}, in encounter order
*/
@NotNull
public static
Collector> toConcurrentList() {
// Creates a synchronized list for concurrent access and sets up accumulation and combination operations.
return Collector.of(
() -> Collections.synchronizedList(new ArrayList<>()),
List::add,
(left, right) -> {
left.addAll(right);
return left;
},
Collector.Characteristics.CONCURRENT
);
}
/**
* Returns a concurrent {@code Collector} that reduces the input elements into a
* new {@code Set}.
*
* This is an {@link java.util.stream.Collector.Characteristics#UNORDERED unordered}
* Collector.
*
* @param the type of the input elements
* @return a {@code Collector} which collects all the input elements into a
* {@code Set}
*/
@NotNull
public static
Collector> toConcurrentSet() {
// Collects elements into a synchronized map and then retrieves the key set.
return collectingAndThen(
toConcurrentMap(Function.identity(),
t -> Boolean.TRUE,
retainingMerger()),
Map::keySet);
}
/**
* Returns a {@link java.util.stream.Collector} which performs a concurrent reduction of its
* input elements under a specified {@code BinaryOperator} using the
* provided identity.
*
* Note: The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or
* {@code partitioningBy}.
*
* @param element type for the input and output of the reduction
* @param identity the identity value for the reduction (also, the value
* that is returned when there are no input elements)
* @param op a {@code BinaryOperator} used to reduce the input elements
* @return a {@code Collector} which implements the reduction operation
* @see Collectors#reducing(Object, BinaryOperator)
*/
public static
Collector reducingConcurrent(final T identity,
@NotNull final BinaryOperator op) {
// Ensure the binary operator is not null.
requireNonNull(op);
// Set up a concurrent reduction using an AtomicReference as the accumulator.
return Collector.of(
() -> new AtomicReference<>(identity),
(AtomicReference ar, T e) -> ar.accumulateAndGet(e, op),
(AtomicReference t1, AtomicReference t2) -> {
t1.accumulateAndGet(t2.get(), op);
return t1;
},
AtomicReference::get,
Collector.Characteristics.CONCURRENT);
}
/**
* Returns a {@code Collector} which performs a concurrent reduction of its
* input elements under a specified {@code BinaryOperator}. The result
* is described as an {@code Optional}.
*
* For example, given a stream of {@code Person}, to calculate tallest
* person in each city:
*
{@code
* Comparator byHeight = Comparator.comparing(Person::getHeight);
* Map tallestByCity
* = people.stream().collect(groupingBy(Person::getCity, reducing(BinaryOperator.maxBy(byHeight))));
* }
*
* @param element type for the input and output of the reduction
* @param op a {@code BinaryOperator} used to reduce the input elements
* @return a {@code Collector} which implements the reduction operation
* @see Collectors#reducing(BinaryOperator)
*/
@NotNull
public static
Collector> reducingConcurrent(@NotNull final BinaryOperator op) {
requireNonNull(op);
final BinaryOperator internalAccumulator = (a, b) -> {
if (a == null) {
return b;
}
return op.apply(a, b);
};
return Collector.of(
AtomicReference::new,
(AtomicReference ar, T e) -> ar.accumulateAndGet(e, internalAccumulator),
(AtomicReference t1, AtomicReference t2) -> {
t1.accumulateAndGet(t2.get(), internalAccumulator);
return t1;
},
(AtomicReference ar) -> Optional.ofNullable(ar.get()),
Collector.Characteristics.CONCURRENT);
}
/**
* Returns a {@code Collector} which performs a concurrent reduction of its
* input elements under a specified mapping function and
* {@code BinaryOperator}. This is a generalization of
* {@link Collectors#reducing(Object, BinaryOperator)} which allows a transformation
* of the elements before reduction.
*
* Note: The {@code reducing()} collectors are most useful when used in a
* multi-level reduction, downstream of {@code groupingBy} or
* {@code partitioningBy}. To perform a simple map-reduce on a stream,
* use {@link Stream#map(Function)} and {@link Stream#reduce(Object, BinaryOperator)}
* instead.
*
* @param the type of the input elements
* @param the type of the mapped values
* @param identity the identity value for the reduction (also, the value
* that is returned when there are no input elements)
* @param mapper a mapping function to apply to each input value
* @param op a {@code BinaryOperator} used to reduce the mapped values
* @return a {@code Collector} implementing the map-reduce operation
*
* For example, given a stream of {@code Person}, to calculate the longest
* last name of residents in each city:
*
{@code
* Comparator byLength = Comparator.comparing(String::length);
* Map longestLastNameByCity
* = people.stream().collect(groupingBy(Person::getCity,
* reducing(Person::getLastName, BinaryOperator.maxBy(byLength))));
* }
* @see Collectors#reducing(Object, Function, BinaryOperator)
*/
@NotNull
public static
Collector reducingConcurrent(@Nullable R identity,
@NotNull final Function super T, ? extends R> mapper,
@NotNull final BinaryOperator op) {
requireNonNull(mapper);
requireNonNull(op);
return Collector.of(
() -> new AtomicReference<>(identity),
(AtomicReference ar, T t) -> ar.accumulateAndGet(mapper.apply(t), op),
(AtomicReference t1, AtomicReference t2) -> {
t1.accumulateAndGet(t2.get(), op);
return t1;
},
AtomicReference::get,
Collector.Characteristics.CONCURRENT
);
}
/**
* Returns a merger that will replace an existing value with the latest value.
*
* @param value type
* @return a merger that will replace values
*/
public static BinaryOperator replacingMerger() {
return (u, v) -> v;
}
/**
* Returns a merger that will retain an existing value and discard the latest value.
*
* @param value type
* @return a merger that will retain values
*/
public static BinaryOperator retainingMerger() {
return (u, v) -> u;
}
/**
* Returns a merger that will throw an Exception if duplicate keys are detected.
*
* @param value type
* @return a merger that will throw an Exception if duplicate keys are detected
*/
public static BinaryOperator throwingMerger() {
return (u, v) -> {
throw new IllegalStateException(String.format("Duplicate value for %s", u));
};
}
}