Merge "OpenJDK11: Required files for java.net merging."
diff --git a/api/current.txt b/api/current.txt
index 237d049c..fa86b34 100755
--- a/api/current.txt
+++ b/api/current.txt
@@ -17016,6 +17016,8 @@
method public static <T> java.util.stream.Collector<T,?,java.lang.Double> averagingLong(java.util.function.ToLongFunction<? super T>);
method public static <T, A, R, RR> java.util.stream.Collector<T,A,RR> collectingAndThen(java.util.stream.Collector<T,A,R>, java.util.function.Function<R,RR>);
method public static <T> java.util.stream.Collector<T,?,java.lang.Long> counting();
+ method public static <T, A, R> java.util.stream.Collector<T,?,R> filtering(java.util.function.Predicate<? super T>, java.util.stream.Collector<? super T,A,R>);
+ method public static <T, U, A, R> java.util.stream.Collector<T,?,R> flatMapping(java.util.function.Function<? super T,? extends java.util.stream.Stream<? extends U>>, java.util.stream.Collector<? super U,A,R>);
method public static <T, K> java.util.stream.Collector<T,?,java.util.Map<K,java.util.List<T>>> groupingBy(java.util.function.Function<? super T,? extends K>);
method public static <T, K, A, D> java.util.stream.Collector<T,?,java.util.Map<K,D>> groupingBy(java.util.function.Function<? super T,? extends K>, java.util.stream.Collector<? super T,A,D>);
method public static <T, K, D, A, M extends java.util.Map<K, D>> java.util.stream.Collector<T,?,M> groupingBy(java.util.function.Function<? super T,? extends K>, java.util.function.Supplier<M>, java.util.stream.Collector<? super T,A,D>);
@@ -17048,6 +17050,10 @@
method public static <T, K, U> java.util.stream.Collector<T,?,java.util.Map<K,U>> toMap(java.util.function.Function<? super T,? extends K>, java.util.function.Function<? super T,? extends U>, java.util.function.BinaryOperator<U>);
method public static <T, K, U, M extends java.util.Map<K, U>> java.util.stream.Collector<T,?,M> toMap(java.util.function.Function<? super T,? extends K>, java.util.function.Function<? super T,? extends U>, java.util.function.BinaryOperator<U>, java.util.function.Supplier<M>);
method public static <T> java.util.stream.Collector<T,?,java.util.Set<T>> toSet();
+ method public static <T> java.util.stream.Collector<T,?,java.util.List<T>> toUnmodifiableList();
+ method public static <T, K, U> java.util.stream.Collector<T,?,java.util.Map<K,U>> toUnmodifiableMap(java.util.function.Function<? super T,? extends K>, java.util.function.Function<? super T,? extends U>);
+ method public static <T, K, U> java.util.stream.Collector<T,?,java.util.Map<K,U>> toUnmodifiableMap(java.util.function.Function<? super T,? extends K>, java.util.function.Function<? super T,? extends U>, java.util.function.BinaryOperator<U>);
+ method public static <T> java.util.stream.Collector<T,?,java.util.Set<T>> toUnmodifiableSet();
}
public interface DoubleStream extends java.util.stream.BaseStream<java.lang.Double,java.util.stream.DoubleStream> {
diff --git a/ojluni/src/main/java/java/util/stream/Collectors.java b/ojluni/src/main/java/java/util/stream/Collectors.java
index a338ec2..26d98bf 100644
--- a/ojluni/src/main/java/java/util/stream/Collectors.java
+++ b/ojluni/src/main/java/java/util/stream/Collectors.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2017, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,7 +27,6 @@
import java.util.AbstractMap;
import java.util.AbstractSet;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -66,36 +65,37 @@
* common mutable reduction tasks:
*
* <pre>{@code
- * // Accumulate names into a List
- * List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
+ * // Accumulate names into a List
+ * List<String> list = people.stream()
+ * .map(Person::getName)
+ * .collect(Collectors.toList());
*
- * // Accumulate names into a TreeSet
- * Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
+ * // Accumulate names into a TreeSet
+ * Set<String> set = people.stream()
+ * .map(Person::getName)
+ * .collect(Collectors.toCollection(TreeSet::new));
*
- * // Convert elements to strings and concatenate them, separated by commas
- * String joined = things.stream()
- * .map(Object::toString)
- * .collect(Collectors.joining(", "));
+ * // Convert elements to strings and concatenate them, separated by commas
+ * String joined = things.stream()
+ * .map(Object::toString)
+ * .collect(Collectors.joining(", "));
*
- * // Compute sum of salaries of employee
- * int total = employees.stream()
- * .collect(Collectors.summingInt(Employee::getSalary)));
+ * // Compute sum of salaries of employee
+ * int total = employees.stream()
+ * .collect(Collectors.summingInt(Employee::getSalary));
*
- * // Group employees by department
- * Map<Department, List<Employee>> byDept
- * = employees.stream()
- * .collect(Collectors.groupingBy(Employee::getDepartment));
+ * // Group employees by department
+ * Map<Department, List<Employee>> byDept = employees.stream()
+ * .collect(Collectors.groupingBy(Employee::getDepartment));
*
- * // Compute sum of salaries by department
- * Map<Department, Integer> totalByDept
- * = employees.stream()
- * .collect(Collectors.groupingBy(Employee::getDepartment,
- * Collectors.summingInt(Employee::getSalary)));
+ * // Compute sum of salaries by department
+ * Map<Department, Integer> totalByDept = employees.stream()
+ * .collect(Collectors.groupingBy(Employee::getDepartment,
+ * Collectors.summingInt(Employee::getSalary)));
*
- * // Partition students into passing and failing
- * Map<Boolean, List<Student>> passingFailing =
- * students.stream()
- * .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
+ * // Partition students into passing and failing
+ * Map<Boolean, List<Student>> passingFailing = students.stream()
+ * .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));
*
* }</pre>
*
@@ -116,21 +116,69 @@
= Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED,
Collector.Characteristics.IDENTITY_FINISH));
static final Set<Collector.Characteristics> CH_NOID = Collections.emptySet();
+ static final Set<Collector.Characteristics> CH_UNORDERED_NOID
+ = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.UNORDERED));
private Collectors() { }
/**
- * Returns a merge function, suitable for use in
- * {@link Map#merge(Object, Object, BiFunction) Map.merge()} or
- * {@link #toMap(Function, Function, BinaryOperator) toMap()}, which always
- * throws {@code IllegalStateException}. This can be used to enforce the
- * assumption that the elements being collected are distinct.
+ * Construct an {@code IllegalStateException} with appropriate message.
*
- * @param <T> the type of input arguments to the merge function
- * @return a merge function which always throw {@code IllegalStateException}
+ * @param k the duplicate key
+ * @param u 1st value to be accumulated/merged
+ * @param v 2nd value to be accumulated/merged
*/
- private static <T> BinaryOperator<T> throwingMerger() {
- return (u,v) -> { throw new IllegalStateException(String.format("Duplicate key %s", u)); };
+ private static IllegalStateException duplicateKeyException(
+ Object k, Object u, Object v) {
+ return new IllegalStateException(String.format(
+ "Duplicate key %s (attempted merging values %s and %s)",
+ k, u, v));
+ }
+
+ /**
+ * {@code BinaryOperator<Map>} that merges the contents of its right
+ * argument into its left argument, throwing {@code IllegalStateException}
+ * if duplicate keys are encountered.
+ *
+ * @param <K> type of the map keys
+ * @param <V> type of the map values
+ * @param <M> type of the map
+ * @return a merge function for two maps
+ */
+ private static <K, V, M extends Map<K,V>>
+ BinaryOperator<M> uniqKeysMapMerger() {
+ return (m1, m2) -> {
+ for (Map.Entry<K,V> e : m2.entrySet()) {
+ K k = e.getKey();
+ V v = Objects.requireNonNull(e.getValue());
+ V u = m1.putIfAbsent(k, v);
+ if (u != null) throw duplicateKeyException(k, u, v);
+ }
+ return m1;
+ };
+ }
+
+ /**
+ * {@code BiConsumer<Map, T>} that accumulates (key, value) pairs
+ * extracted from elements into the map, throwing {@code IllegalStateException}
+ * if duplicate keys are encountered.
+ *
+ * @param keyMapper a function that maps an element into a key
+ * @param valueMapper a function that maps an element into a value
+ * @param <T> type of elements
+ * @param <K> type of map keys
+ * @param <V> type of map values
+ * @return an accumulating consumer
+ */
+ private static <T, K, V>
+ BiConsumer<Map<K, V>, T> uniqKeysMapAccumulator(Function<? super T, ? extends K> keyMapper,
+ Function<? super T, ? extends V> valueMapper) {
+ return (map, element) -> {
+ K k = keyMapper.apply(element);
+ V v = Objects.requireNonNull(valueMapper.apply(element));
+ V u = map.putIfAbsent(k, v);
+ if (u != null) throw duplicateKeyException(k, u, v);
+ };
}
@SuppressWarnings("unchecked")
@@ -203,8 +251,8 @@
*
* @param <T> the type of the input elements
* @param <C> the type of the resulting {@code Collection}
- * @param collectionFactory a {@code Supplier} which returns a new, empty
- * {@code Collection} of the appropriate type
+ * @param collectionFactory a supplier providing a new empty {@code Collection}
+ * into which the results will be inserted
* @return a {@code Collector} which collects all the input elements into a
* {@code Collection}, in encounter order
*/
@@ -233,6 +281,26 @@
}
/**
+ * Returns a {@code Collector} that accumulates the input elements into an
+ * <a href="../List.html#unmodifiable">unmodifiable List</a> in encounter
+ * order. The returned Collector disallows null values and will throw
+ * {@code NullPointerException} if it is presented with a null value.
+ *
+ * @param <T> the type of the input elements
+ * @return a {@code Collector} that accumulates the input elements into an
+ * <a href="../List.html#unmodifiable">unmodifiable List</a> in encounter order
+ * @since 10
+ */
+ @SuppressWarnings("unchecked")
+ public static <T>
+ Collector<T, ?, List<T>> toUnmodifiableList() {
+ return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
+ (left, right) -> { left.addAll(right); return left; },
+ list -> (List<T>)List.of(list.toArray()),
+ CH_NOID);
+ }
+
+ /**
* Returns a {@code Collector} that accumulates the input elements into a
* new {@code Set}. There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code Set} returned; if more
@@ -249,11 +317,47 @@
public static <T>
Collector<T, ?, Set<T>> toSet() {
return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
- (left, right) -> { left.addAll(right); return left; },
+ (left, right) -> {
+ if (left.size() < right.size()) {
+ right.addAll(left); return right;
+ } else {
+ left.addAll(right); return left;
+ }
+ },
CH_UNORDERED_ID);
}
/**
+ * Returns a {@code Collector} that accumulates the input elements into an
+ * <a href="../Set.html#unmodifiable">unmodifiable Set</a>. The returned
+ * Collector disallows null values and will throw {@code NullPointerException}
+ * if it is presented with a null value. If the input contains duplicate elements,
+ * an arbitrary element of the duplicates is preserved.
+ *
+ * <p>This is an {@link Collector.Characteristics#UNORDERED unordered}
+ * Collector.
+ *
+ * @param <T> the type of the input elements
+ * @return a {@code Collector} that accumulates the input elements into an
+ * <a href="../Set.html#unmodifiable">unmodifiable Set</a>
+ * @since 10
+ */
+ @SuppressWarnings("unchecked")
+ public static <T>
+ Collector<T, ?, Set<T>> toUnmodifiableSet() {
+ return new CollectorImpl<>((Supplier<Set<T>>) HashSet::new, Set::add,
+ (left, right) -> {
+ if (left.size() < right.size()) {
+ right.addAll(left); return right;
+ } else {
+ left.addAll(right); return left;
+ }
+ },
+ set -> (Set<T>)Set.of(set.toArray()),
+ CH_UNORDERED_NOID);
+ }
+
+ /**
* Returns a {@code Collector} that concatenates the input elements into a
* {@code String}, in encounter order.
*
@@ -333,9 +437,11 @@
* {@code partitioningBy}. For example, given a stream of
* {@code Person}, to accumulate the set of last names in each city:
* <pre>{@code
- * Map<City, Set<String>> lastNamesByCity
- * = people.stream().collect(groupingBy(Person::getCity,
- * mapping(Person::getLastName, toSet())));
+ * Map<City, Set<String>> lastNamesByCity
+ * = people.stream().collect(
+ * groupingBy(Person::getCity,
+ * mapping(Person::getLastName,
+ * toSet())));
* }</pre>
*
* @param <T> the type of the input elements
@@ -358,12 +464,112 @@
}
/**
+ * Adapts a {@code Collector} accepting elements of type {@code U} to one
+ * accepting elements of type {@code T} by applying a flat mapping function
+ * to each input element before accumulation. The flat mapping function
+ * maps an input element to a {@link Stream stream} covering zero or more
+ * output elements that are then accumulated downstream. Each mapped stream
+ * is {@link java.util.stream.BaseStream#close() closed} after its contents
+ * have been placed downstream. (If a mapped stream is {@code null}
+ * an empty stream is used, instead.)
+ *
+ * @apiNote
+ * The {@code flatMapping()} collectors are most useful when used in a
+ * multi-level reduction, such as downstream of a {@code groupingBy} or
+ * {@code partitioningBy}. For example, given a stream of
+ * {@code Order}, to accumulate the set of line items for each customer:
+ * <pre>{@code
+ * Map<String, Set<LineItem>> itemsByCustomerName
+ * = orders.stream().collect(
+ * groupingBy(Order::getCustomerName,
+ * flatMapping(order -> order.getLineItems().stream(),
+ * toSet())));
+ * }</pre>
+ *
+ * @param <T> the type of the input elements
+ * @param <U> type of elements accepted by downstream collector
+ * @param <A> intermediate accumulation type of the downstream collector
+ * @param <R> result type of collector
+ * @param mapper a function to be applied to the input elements, which
+ * returns a stream of results
+ * @param downstream a collector which will receive the elements of the
+ * stream returned by mapper
+ * @return a collector which applies the mapping function to the input
+ * elements and provides the flat mapped results to the downstream collector
+ * @since 9
+ */
+ public static <T, U, A, R>
+ Collector<T, ?, R> flatMapping(Function<? super T, ? extends Stream<? extends U>> mapper,
+ Collector<? super U, A, R> downstream) {
+ BiConsumer<A, ? super U> downstreamAccumulator = downstream.accumulator();
+ return new CollectorImpl<>(downstream.supplier(),
+ (r, t) -> {
+ try (Stream<? extends U> result = mapper.apply(t)) {
+ if (result != null)
+ result.sequential().forEach(u -> downstreamAccumulator.accept(r, u));
+ }
+ },
+ downstream.combiner(), downstream.finisher(),
+ downstream.characteristics());
+ }
+
+ /**
+ * Adapts a {@code Collector} to one accepting elements of the same type
+ * {@code T} by applying the predicate to each input element and only
+ * accumulating if the predicate returns {@code true}.
+ *
+ * @apiNote
+ * The {@code filtering()} collectors are most useful when used in a
+ * multi-level reduction, such as downstream of a {@code groupingBy} or
+ * {@code partitioningBy}. For example, given a stream of
+ * {@code Employee}, to accumulate the employees in each department that have a
+ * salary above a certain threshold:
+ * <pre>{@code
+ * Map<Department, Set<Employee>> wellPaidEmployeesByDepartment
+ * = employees.stream().collect(
+ * groupingBy(Employee::getDepartment,
+ * filtering(e -> e.getSalary() > 2000,
+ * toSet())));
+ * }</pre>
+ * A filtering collector differs from a stream's {@code filter()} operation.
+ * In this example, suppose there are no employees whose salary is above the
+ * threshold in some department. Using a filtering collector as shown above
+ * would result in a mapping from that department to an empty {@code Set}.
+ * If a stream {@code filter()} operation were done instead, there would be
+ * no mapping for that department at all.
+ *
+ * @param <T> the type of the input elements
+ * @param <A> intermediate accumulation type of the downstream collector
+ * @param <R> result type of collector
+ * @param predicate a predicate to be applied to the input elements
+ * @param downstream a collector which will accept values that match the
+ * predicate
+ * @return a collector which applies the predicate to the input elements
+ * and provides matching elements to the downstream collector
+ * @since 9
+ */
+ public static <T, A, R>
+ Collector<T, ?, R> filtering(Predicate<? super T> predicate,
+ Collector<? super T, A, R> downstream) {
+ BiConsumer<A, ? super T> downstreamAccumulator = downstream.accumulator();
+ return new CollectorImpl<>(downstream.supplier(),
+ (r, t) -> {
+ if (predicate.test(t)) {
+ downstreamAccumulator.accept(r, t);
+ }
+ },
+ downstream.combiner(), downstream.finisher(),
+ downstream.characteristics());
+ }
+
+ /**
* Adapts a {@code Collector} to perform an additional finishing
* transformation. For example, one could adapt the {@link #toList()}
* collector to always produce an immutable list with:
* <pre>{@code
- * List<String> people
- * = people.stream().collect(collectingAndThen(toList(), Collections::unmodifiableList));
+ * List<String> list = people.stream().collect(
+ * collectingAndThen(toList(),
+ * Collections::unmodifiableList));
* }</pre>
*
* @param <T> the type of the input elements
@@ -410,7 +616,7 @@
*/
public static <T> Collector<T, ?, Long>
counting() {
- return reducing(0L, e -> 1L, Long::sum);
+ return summingLong(e -> 1L);
}
/**
@@ -515,8 +721,9 @@
*/
return new CollectorImpl<>(
() -> new double[3],
- (a, t) -> { sumWithCompensation(a, mapper.applyAsDouble(t));
- a[2] += mapper.applyAsDouble(t);},
+ (a, t) -> { double val = mapper.applyAsDouble(t);
+ sumWithCompensation(a, val);
+ a[2] += val;},
(a, b) -> { sumWithCompensation(a, b[0]);
a[2] += b[2];
return sumWithCompensation(a, b[1]); },
@@ -565,8 +772,9 @@
* the result is 0.
*
* @param <T> the type of the input elements
- * @param mapper a function extracting the property to be summed
- * @return a {@code Collector} that produces the sum of a derived property
+ * @param mapper a function extracting the property to be averaged
+ * @return a {@code Collector} that produces the arithmetic mean of a
+ * derived property
*/
public static <T> Collector<T, ?, Double>
averagingInt(ToIntFunction<? super T> mapper) {
@@ -583,8 +791,9 @@
* the result is 0.
*
* @param <T> the type of the input elements
- * @param mapper a function extracting the property to be summed
- * @return a {@code Collector} that produces the sum of a derived property
+ * @param mapper a function extracting the property to be averaged
+ * @return a {@code Collector} that produces the arithmetic mean of a
+ * derived property
*/
public static <T> Collector<T, ?, Double>
averagingLong(ToLongFunction<? super T> mapper) {
@@ -614,8 +823,9 @@
* 2<sup>53</sup>, leading to additional numerical errors.
*
* @param <T> the type of the input elements
- * @param mapper a function extracting the property to be summed
- * @return a {@code Collector} that produces the sum of a derived property
+ * @param mapper a function extracting the property to be averaged
+ * @return a {@code Collector} that produces the arithmetic mean of a
+ * derived property
*/
public static <T> Collector<T, ?, Double>
averagingDouble(ToDoubleFunction<? super T> mapper) {
@@ -627,7 +837,7 @@
*/
return new CollectorImpl<>(
() -> new double[4],
- (a, t) -> { sumWithCompensation(a, mapper.applyAsDouble(t)); a[2]++; a[3]+= mapper.applyAsDouble(t);},
+ (a, t) -> { double val = mapper.applyAsDouble(t); sumWithCompensation(a, val); a[2]++; a[3]+= val;},
(a, b) -> { sumWithCompensation(a, b[0]); sumWithCompensation(a, b[1]); a[2] += b[2]; a[3] += b[3]; return a; },
a -> (a[2] == 0) ? 0.0d : (computeFinalSum(a) / a[2]),
CH_NOID);
@@ -682,9 +892,11 @@
* <p>For example, given a stream of {@code Person}, to calculate tallest
* person in each city:
* <pre>{@code
- * Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
- * Map<City, Person> tallestByCity
- * = people.stream().collect(groupingBy(Person::getCity, reducing(BinaryOperator.maxBy(byHeight))));
+ * Comparator<Person> byHeight = Comparator.comparing(Person::getHeight);
+ * Map<City, Optional<Person>> tallestByCity
+ * = people.stream().collect(
+ * groupingBy(Person::getCity,
+ * reducing(BinaryOperator.maxBy(byHeight))));
* }</pre>
*
* @param <T> element type for the input and output of the reduction
@@ -735,10 +947,13 @@
* <p>For example, given a stream of {@code Person}, to calculate the longest
* last name of residents in each city:
* <pre>{@code
- * Comparator<String> byLength = Comparator.comparing(String::length);
- * Map<City, String> longestLastNameByCity
- * = people.stream().collect(groupingBy(Person::getCity,
- * reducing(Person::getLastName, BinaryOperator.maxBy(byLength))));
+ * Comparator<String> byLength = Comparator.comparing(String::length);
+ * Map<City, String> longestLastNameByCity
+ * = people.stream().collect(
+ * groupingBy(Person::getCity,
+ * reducing("",
+ * Person::getLastName,
+ * BinaryOperator.maxBy(byLength))));
* }</pre>
*
* @param <T> the type of the input elements
@@ -822,9 +1037,11 @@
*
* <p>For example, to compute the set of last names of people in each city:
* <pre>{@code
- * Map<City, Set<String>> namesByCity
- * = people.stream().collect(groupingBy(Person::getCity,
- * mapping(Person::getLastName, toSet())));
+ * Map<City, Set<String>> namesByCity
+ * = people.stream().collect(
+ * groupingBy(Person::getCity,
+ * mapping(Person::getLastName,
+ * toSet())));
* }</pre>
*
* @implNote
@@ -869,9 +1086,12 @@
* <p>For example, to compute the set of last names of people in each city,
* where the city names are sorted:
* <pre>{@code
- * Map<City, Set<String>> namesByCity
- * = people.stream().collect(groupingBy(Person::getCity, TreeMap::new,
- * mapping(Person::getLastName, toSet())));
+ * Map<City, Set<String>> namesByCity
+ * = people.stream().collect(
+ * groupingBy(Person::getCity,
+ * TreeMap::new,
+ * mapping(Person::getLastName,
+ * toSet())));
* }</pre>
*
* @implNote
@@ -889,8 +1109,8 @@
* @param <M> the type of the resulting {@code Map}
* @param classifier a classifier function mapping input elements to keys
* @param downstream a {@code Collector} implementing the downstream reduction
- * @param mapFactory a function which, when called, produces a new empty
- * {@code Map} of the desired type
+ * @param mapFactory a supplier providing a new empty {@code Map}
+ * into which the results will be inserted
* @return a {@code Collector} implementing the cascaded group-by operation
*
* @see #groupingBy(Function, Collector)
@@ -944,7 +1164,7 @@
* function.
*
* <p>There are no guarantees on the type, mutability, or serializability
- * of the {@code Map} or {@code List} objects returned, or of the
+ * of the {@code ConcurrentMap} or {@code List} objects returned, or of the
* thread-safety of the {@code List} objects returned.
* @implSpec
* This produces a result similar to:
@@ -980,14 +1200,19 @@
* <p>The classification function maps elements to some key type {@code K}.
* The downstream collector operates on elements of type {@code T} and
* produces a result of type {@code D}. The resulting collector produces a
- * {@code Map<K, D>}.
+ * {@code ConcurrentMap<K, D>}.
+ *
+ * <p>There are no guarantees on the type, mutability, or serializability
+ * of the {@code ConcurrentMap} returned.
*
* <p>For example, to compute the set of last names of people in each city,
* where the city names are sorted:
* <pre>{@code
- * ConcurrentMap<City, Set<String>> namesByCity
- * = people.stream().collect(groupingByConcurrent(Person::getCity,
- * mapping(Person::getLastName, toSet())));
+ * ConcurrentMap<City, Set<String>> namesByCity
+ * = people.stream().collect(
+ * groupingByConcurrent(Person::getCity,
+ * mapping(Person::getLastName,
+ * toSet())));
* }</pre>
*
* @param <T> the type of the input elements
@@ -1022,17 +1247,19 @@
* <p>The classification function maps elements to some key type {@code K}.
* The downstream collector operates on elements of type {@code T} and
* produces a result of type {@code D}. The resulting collector produces a
- * {@code Map<K, D>}.
+ * {@code ConcurrentMap<K, D>}.
*
* <p>For example, to compute the set of last names of people in each city,
* where the city names are sorted:
* <pre>{@code
- * ConcurrentMap<City, Set<String>> namesByCity
- * = people.stream().collect(groupingBy(Person::getCity, ConcurrentSkipListMap::new,
- * mapping(Person::getLastName, toSet())));
+ * ConcurrentMap<City, Set<String>> namesByCity
+ * = people.stream().collect(
+ * groupingByConcurrent(Person::getCity,
+ * ConcurrentSkipListMap::new,
+ * mapping(Person::getLastName,
+ * toSet())));
* }</pre>
*
- *
* @param <T> the type of the input elements
* @param <K> the type of the keys
* @param <A> the intermediate accumulation type of the downstream collector
@@ -1040,8 +1267,8 @@
* @param <M> the type of the resulting {@code ConcurrentMap}
* @param classifier a classifier function mapping input elements to keys
* @param downstream a {@code Collector} implementing the downstream reduction
- * @param mapFactory a function which, when called, produces a new empty
- * {@code ConcurrentMap} of the desired type
+ * @param mapFactory a supplier providing a new empty {@code ConcurrentMap}
+ * into which the results will be inserted
* @return a concurrent, unordered {@code Collector} implementing the cascaded group-by operation
*
* @see #groupingByConcurrent(Function)
@@ -1096,8 +1323,15 @@
* to a {@code Predicate}, and organizes them into a
* {@code Map<Boolean, List<T>>}.
*
+ * The returned {@code Map} always contains mappings for both
+ * {@code false} and {@code true} keys.
* There are no guarantees on the type, mutability,
- * serializability, or thread-safety of the {@code Map} returned.
+ * serializability, or thread-safety of the {@code Map} or {@code List}
+ * returned.
+ *
+ * @apiNote
+ * If a partition has no elements, its value in the result Map will be
+ * an empty List.
*
* @param <T> the type of the input elements
* @param predicate a predicate used for classifying input elements
@@ -1117,9 +1351,17 @@
* {@code Map<Boolean, D>} whose values are the result of the downstream
* reduction.
*
- * <p>There are no guarantees on the type, mutability,
+ * <p>
+ * The returned {@code Map} always contains mappings for both
+ * {@code false} and {@code true} keys.
+ * There are no guarantees on the type, mutability,
* serializability, or thread-safety of the {@code Map} returned.
*
+ * @apiNote
+ * If a partition has no elements, its value in the result Map will be
+ * obtained by calling the downstream collector's supplier function and then
+ * applying the finisher function.
+ *
* @param <T> the type of the input elements
* @param <A> the intermediate accumulation type of the downstream collector
* @param <D> the result type of the downstream reduction
@@ -1160,12 +1402,15 @@
* {@code Map} whose keys and values are the result of applying the provided
* mapping functions to the input elements.
*
- * <p>If the mapped keys contains duplicates (according to
+ * <p>If the mapped keys contain duplicates (according to
* {@link Object#equals(Object)}), an {@code IllegalStateException} is
* thrown when the collection operation is performed. If the mapped keys
- * may have duplicates, use {@link #toMap(Function, Function, BinaryOperator)}
+ * might have duplicates, use {@link #toMap(Function, Function, BinaryOperator)}
* instead.
*
+ * <p>There are no guarantees on the type, mutability, serializability,
+ * or thread-safety of the {@code Map} returned.
+ *
* @apiNote
* It is common for either the key or the value to be the input elements.
* In this case, the utility method
@@ -1173,16 +1418,18 @@
* For example, the following produces a {@code Map} mapping
* students to their grade point average:
* <pre>{@code
- * Map<Student, Double> studentToGPA
- * students.stream().collect(toMap(Functions.identity(),
- * student -> computeGPA(student)));
+ * Map<Student, Double> studentToGPA
+ * = students.stream().collect(
+ * toMap(Function.identity(),
+ * student -> computeGPA(student)));
* }</pre>
* And the following produces a {@code Map} mapping a unique identifier to
* students:
* <pre>{@code
- * Map<String, Student> studentIdToStudent
- * students.stream().collect(toMap(Student::getId,
- * Functions.identity());
+ * Map<String, Student> studentIdToStudent
+ * = students.stream().collect(
+ * toMap(Student::getId,
+ * Function.identity()));
* }</pre>
*
* @implNote
@@ -1209,7 +1456,49 @@
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
- return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
+ return new CollectorImpl<>(HashMap::new,
+ uniqKeysMapAccumulator(keyMapper, valueMapper),
+ uniqKeysMapMerger(),
+ CH_ID);
+ }
+
+ /**
+ * Returns a {@code Collector} that accumulates the input elements into an
+ * <a href="../Map.html#unmodifiable">unmodifiable Map</a>,
+ * whose keys and values are the result of applying the provided
+ * mapping functions to the input elements.
+ *
+ * <p>If the mapped keys contain duplicates (according to
+ * {@link Object#equals(Object)}), an {@code IllegalStateException} is
+ * thrown when the collection operation is performed. If the mapped keys
+ * might have duplicates, use {@link #toUnmodifiableMap(Function, Function, BinaryOperator)}
+ * to handle merging of the values.
+ *
+ * <p>The returned Collector disallows null keys and values. If either mapping function
+ * returns null, {@code NullPointerException} will be thrown.
+ *
+ * @param <T> the type of the input elements
+ * @param <K> the output type of the key mapping function
+ * @param <U> the output type of the value mapping function
+ * @param keyMapper a mapping function to produce keys, must be non-null
+ * @param valueMapper a mapping function to produce values, must be non-null
+ * @return a {@code Collector} that accumulates the input elements into an
+ * <a href="../Map.html#unmodifiable">unmodifiable Map</a>, whose keys and values
+ * are the result of applying the provided mapping functions to the input elements
+ * @throws NullPointerException if either keyMapper or valueMapper is null
+ *
+ * @see #toUnmodifiableMap(Function, Function, BinaryOperator)
+ * @since 10
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static <T, K, U>
+ Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper,
+ Function<? super T, ? extends U> valueMapper) {
+ Objects.requireNonNull(keyMapper, "keyMapper");
+ Objects.requireNonNull(valueMapper, "valueMapper");
+ return collectingAndThen(
+ toMap(keyMapper, valueMapper),
+ map -> (Map<K,U>)Map.ofEntries(map.entrySet().toArray(new Map.Entry[0])));
}
/**
@@ -1218,10 +1507,13 @@
* mapping functions to the input elements.
*
* <p>If the mapped
- * keys contains duplicates (according to {@link Object#equals(Object)}),
+ * keys contain duplicates (according to {@link Object#equals(Object)}),
* the value mapping function is applied to each equal element, and the
* results are merged using the provided merging function.
*
+ * <p>There are no guarantees on the type, mutability, serializability,
+ * or thread-safety of the {@code Map} returned.
+ *
* @apiNote
* There are multiple ways to deal with collisions between multiple elements
* mapping to the same key. The other forms of {@code toMap} simply use
@@ -1229,13 +1521,14 @@
* more flexible merge policies. For example, if you have a stream
* of {@code Person}, and you want to produce a "phone book" mapping name to
* address, but it is possible that two persons have the same name, you can
- * do as follows to gracefully deals with these collisions, and produce a
+ * do as follows to gracefully deal with these collisions, and produce a
* {@code Map} mapping names to a concatenated list of addresses:
* <pre>{@code
- * Map<String, String> phoneBook
- * people.stream().collect(toMap(Person::getName,
- * Person::getAddress,
- * (s, a) -> s + ", " + a));
+ * Map<String, String> phoneBook
+ * = people.stream().collect(
+ * toMap(Person::getName,
+ * Person::getAddress,
+ * (s, a) -> s + ", " + a));
* }</pre>
*
* @implNote
@@ -1271,13 +1564,58 @@
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
+
+ /**
+ * Returns a {@code Collector} that accumulates the input elements into an
+ * <a href="../Map.html#unmodifiable">unmodifiable Map</a>,
+ * whose keys and values are the result of applying the provided
+ * mapping functions to the input elements.
+ *
+ * <p>If the mapped
+ * keys contain duplicates (according to {@link Object#equals(Object)}),
+ * the value mapping function is applied to each equal element, and the
+ * results are merged using the provided merging function.
+ *
+ * <p>The returned Collector disallows null keys and values. If either mapping function
+ * returns null, {@code NullPointerException} will be thrown.
+ *
+ * @param <T> the type of the input elements
+ * @param <K> the output type of the key mapping function
+ * @param <U> the output type of the value mapping function
+ * @param keyMapper a mapping function to produce keys, must be non-null
+ * @param valueMapper a mapping function to produce values, must be non-null
+ * @param mergeFunction a merge function, used to resolve collisions between
+ * values associated with the same key, as supplied
+ * to {@link Map#merge(Object, Object, BiFunction)},
+ * must be non-null
+ * @return a {@code Collector} that accumulates the input elements into an
+ * <a href="../Map.html#unmodifiable">unmodifiable Map</a>, whose keys and values
+ * are the result of applying the provided mapping functions to the input elements
+ * @throws NullPointerException if the keyMapper, valueMapper, or mergeFunction is null
+ *
+ * @see #toUnmodifiableMap(Function, Function)
+ * @since 10
+ */
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public static <T, K, U>
+ Collector<T, ?, Map<K,U>> toUnmodifiableMap(Function<? super T, ? extends K> keyMapper,
+ Function<? super T, ? extends U> valueMapper,
+ BinaryOperator<U> mergeFunction) {
+ Objects.requireNonNull(keyMapper, "keyMapper");
+ Objects.requireNonNull(valueMapper, "valueMapper");
+ Objects.requireNonNull(mergeFunction, "mergeFunction");
+ return collectingAndThen(
+ toMap(keyMapper, valueMapper, mergeFunction, HashMap::new),
+ map -> (Map<K,U>)Map.ofEntries(map.entrySet().toArray(new Map.Entry[0])));
+ }
+
/**
* Returns a {@code Collector} that accumulates elements into a
* {@code Map} whose keys and values are the result of applying the provided
* mapping functions to the input elements.
*
* <p>If the mapped
- * keys contains duplicates (according to {@link Object#equals(Object)}),
+ * keys contain duplicates (according to {@link Object#equals(Object)}),
* the value mapping function is applied to each equal element, and the
* results are merged using the provided merging function. The {@code Map}
* is created by a provided supplier function.
@@ -1299,8 +1637,8 @@
* @param mergeFunction a merge function, used to resolve collisions between
* values associated with the same key, as supplied
* to {@link Map#merge(Object, Object, BiFunction)}
- * @param mapSupplier a function which returns a new, empty {@code Map} into
- * which the results will be inserted
+ * @param mapFactory a supplier providing a new empty {@code Map}
+ * into which the results will be inserted
* @return a {@code Collector} which collects elements into a {@code Map}
* whose keys are the result of applying a key mapping function to the input
* elements, and whose values are the result of applying a value mapping
@@ -1313,13 +1651,13 @@
*/
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
- Function<? super T, ? extends U> valueMapper,
- BinaryOperator<U> mergeFunction,
- Supplier<M> mapSupplier) {
+ Function<? super T, ? extends U> valueMapper,
+ BinaryOperator<U> mergeFunction,
+ Supplier<M> mapFactory) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
- return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID);
+ return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_ID);
}
/**
@@ -1327,29 +1665,34 @@
* {@code ConcurrentMap} whose keys and values are the result of applying
* the provided mapping functions to the input elements.
*
- * <p>If the mapped keys contains duplicates (according to
+ * <p>If the mapped keys contain duplicates (according to
* {@link Object#equals(Object)}), an {@code IllegalStateException} is
* thrown when the collection operation is performed. If the mapped keys
* may have duplicates, use
* {@link #toConcurrentMap(Function, Function, BinaryOperator)} instead.
*
+ * <p>There are no guarantees on the type, mutability, or serializability
+ * of the {@code ConcurrentMap} returned.
+ *
* @apiNote
* It is common for either the key or the value to be the input elements.
* In this case, the utility method
* {@link java.util.function.Function#identity()} may be helpful.
- * For example, the following produces a {@code Map} mapping
+ * For example, the following produces a {@code ConcurrentMap} mapping
* students to their grade point average:
* <pre>{@code
- * Map<Student, Double> studentToGPA
- * students.stream().collect(toMap(Functions.identity(),
- * student -> computeGPA(student)));
+ * ConcurrentMap<Student, Double> studentToGPA
+ * = students.stream().collect(
+ * toConcurrentMap(Function.identity(),
+ * student -> computeGPA(student)));
* }</pre>
- * And the following produces a {@code Map} mapping a unique identifier to
- * students:
+ * And the following produces a {@code ConcurrentMap} mapping a
+ * unique identifier to students:
* <pre>{@code
- * Map<String, Student> studentIdToStudent
- * students.stream().collect(toConcurrentMap(Student::getId,
- * Functions.identity());
+ * ConcurrentMap<String, Student> studentIdToStudent
+ * = students.stream().collect(
+ * toConcurrentMap(Student::getId,
+ * Function.identity()));
* }</pre>
*
* <p>This is a {@link Collector.Characteristics#CONCURRENT concurrent} and
@@ -1372,7 +1715,10 @@
public static <T, K, U>
Collector<T, ?, ConcurrentMap<K,U>> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
- return toConcurrentMap(keyMapper, valueMapper, throwingMerger(), ConcurrentHashMap::new);
+ return new CollectorImpl<>(ConcurrentHashMap::new,
+ uniqKeysMapAccumulator(keyMapper, valueMapper),
+ uniqKeysMapMerger(),
+ CH_CONCURRENT_ID);
}
/**
@@ -1380,10 +1726,13 @@
* {@code ConcurrentMap} whose keys and values are the result of applying
* the provided mapping functions to the input elements.
*
- * <p>If the mapped keys contains duplicates (according to {@link Object#equals(Object)}),
+ * <p>If the mapped keys contain duplicates (according to {@link Object#equals(Object)}),
* the value mapping function is applied to each equal element, and the
* results are merged using the provided merging function.
*
+ * <p>There are no guarantees on the type, mutability, or serializability
+ * of the {@code ConcurrentMap} returned.
+ *
* @apiNote
* There are multiple ways to deal with collisions between multiple elements
* mapping to the same key. The other forms of {@code toConcurrentMap} simply use
@@ -1391,13 +1740,14 @@
* more flexible merge policies. For example, if you have a stream
* of {@code Person}, and you want to produce a "phone book" mapping name to
* address, but it is possible that two persons have the same name, you can
- * do as follows to gracefully deals with these collisions, and produce a
- * {@code Map} mapping names to a concatenated list of addresses:
+ * do as follows to gracefully deal with these collisions, and produce a
+ * {@code ConcurrentMap} mapping names to a concatenated list of addresses:
* <pre>{@code
- * Map<String, String> phoneBook
- * people.stream().collect(toConcurrentMap(Person::getName,
- * Person::getAddress,
- * (s, a) -> s + ", " + a));
+ * ConcurrentMap<String, String> phoneBook
+ * = people.stream().collect(
+ * toConcurrentMap(Person::getName,
+ * Person::getAddress,
+ * (s, a) -> s + ", " + a));
* }</pre>
*
* <p>This is a {@link Collector.Characteristics#CONCURRENT concurrent} and
@@ -1434,7 +1784,7 @@
* {@code ConcurrentMap} whose keys and values are the result of applying
* the provided mapping functions to the input elements.
*
- * <p>If the mapped keys contains duplicates (according to {@link Object#equals(Object)}),
+ * <p>If the mapped keys contain duplicates (according to {@link Object#equals(Object)}),
* the value mapping function is applied to each equal element, and the
* results are merged using the provided merging function. The
* {@code ConcurrentMap} is created by a provided supplier function.
@@ -1451,8 +1801,8 @@
* @param mergeFunction a merge function, used to resolve collisions between
* values associated with the same key, as supplied
* to {@link Map#merge(Object, Object, BiFunction)}
- * @param mapSupplier a function which returns a new, empty {@code Map} into
- * which the results will be inserted
+ * @param mapFactory a supplier providing a new empty {@code ConcurrentMap}
+ * into which the results will be inserted
* @return a concurrent, unordered {@code Collector} which collects elements into a
* {@code ConcurrentMap} whose keys are the result of applying a key mapping
* function to the input elements, and whose values are the result of
@@ -1467,11 +1817,11 @@
Collector<T, ?, M> toConcurrentMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
- Supplier<M> mapSupplier) {
+ Supplier<M> mapFactory) {
BiConsumer<M, T> accumulator
= (map, element) -> map.merge(keyMapper.apply(element),
valueMapper.apply(element), mergeFunction);
- return new CollectorImpl<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_CONCURRENT_ID);
+ return new CollectorImpl<>(mapFactory, accumulator, mapMerger(mergeFunction), CH_CONCURRENT_ID);
}
/**
@@ -1550,12 +1900,12 @@
@Override
public Set<Map.Entry<Boolean, T>> entrySet() {
- return new AbstractSet<Map.Entry<Boolean, T>>() {
+ return new AbstractSet<>() {
@Override
public Iterator<Map.Entry<Boolean, T>> iterator() {
Map.Entry<Boolean, T> falseEntry = new SimpleImmutableEntry<>(false, forFalse);
Map.Entry<Boolean, T> trueEntry = new SimpleImmutableEntry<>(true, forTrue);
- return Arrays.asList(falseEntry, trueEntry).iterator();
+ return List.of(falseEntry, trueEntry).iterator();
}
@Override
diff --git a/ojluni/src/main/java/jdk/net/ExtendedSocketOptions.java b/ojluni/src/main/java/jdk/net/ExtendedSocketOptions.java
index 644fc4d..74a7bc8 100644
--- a/ojluni/src/main/java/jdk/net/ExtendedSocketOptions.java
+++ b/ojluni/src/main/java/jdk/net/ExtendedSocketOptions.java
@@ -1,5 +1,5 @@
/*
- * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@@ -25,12 +25,23 @@
package jdk.net;
+import java.io.FileDescriptor;
+import java.net.SocketException;
import java.net.SocketOption;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import jdk.internal.misc.JavaIOFileDescriptorAccess;
+import jdk.internal.misc.SharedSecrets;
/**
* Defines extended socket options, beyond those defined in
* {@link java.net.StandardSocketOptions}. These options may be platform
* specific.
+ *
+ * @since 1.8
*/
// Android-removed: @jdk.Exported, not present on Android.
// @jdk.Exported
@@ -48,7 +59,7 @@
@Override public String toString() { return name; }
}
- private ExtendedSocketOptions() {}
+ private ExtendedSocketOptions() { }
/**
* Service level properties. When a security manager is installed,
@@ -58,4 +69,347 @@
*/
public static final SocketOption<SocketFlow> SO_FLOW_SLA = new
ExtSocketOption<SocketFlow>("SO_FLOW_SLA", SocketFlow.class);
+
+ /**
+ * Disable Delayed Acknowledgements.
+ *
+ * <p>
+ * This socket option can be used to reduce or disable delayed
+ * acknowledgments (ACKs). When {@code TCP_QUICKACK} is enabled, ACKs are
+ * sent immediately, rather than delayed if needed in accordance to normal
+ * TCP operation. This option is not permanent, it only enables a switch to
+ * or from {@code TCP_QUICKACK} mode. Subsequent operations of the TCP
+ * protocol will once again disable/enable {@code TCP_QUICKACK} mode
+ * depending on internal protocol processing and factors such as delayed ACK
+ * timeouts occurring and data transfer, therefore this option needs to be
+ * set with {@code setOption} after each operation of TCP on a given socket.
+ *
+ * <p>
+ * The value of this socket option is a {@code Boolean} that represents
+ * whether the option is enabled or disabled. The socket option is specific
+ * to stream-oriented sockets using the TCP/IP protocol. The exact semantics
+ * of this socket option are socket type and system dependent.
+ *
+ * @since 10
+ */
+ public static final SocketOption<Boolean> TCP_QUICKACK =
+ new ExtSocketOption<Boolean>("TCP_QUICKACK", Boolean.class);
+
+ /**
+ * Keep-Alive idle time.
+ *
+ * <p>
+ * The value of this socket option is an {@code Integer} that is the number
+ * of seconds of idle time before keep-alive initiates a probe. The socket
+ * option is specific to stream-oriented sockets using the TCP/IP protocol.
+ * The exact semantics of this socket option are system dependent.
+ *
+ * <p>
+ * When the {@link java.net.StandardSocketOptions#SO_KEEPALIVE
+ * SO_KEEPALIVE} option is enabled, TCP probes a connection that has been
+ * idle for some amount of time. The default value for this idle period is
+ * system dependent, but is typically 2 hours. The {@code TCP_KEEPIDLE}
+ * option can be used to affect this value for a given socket.
+ *
+ * @since 11
+ */
+ public static final SocketOption<Integer> TCP_KEEPIDLE
+ = new ExtSocketOption<Integer>("TCP_KEEPIDLE", Integer.class);
+
+ /**
+ * Keep-Alive retransmission interval time.
+ *
+ * <p>
+ * The value of this socket option is an {@code Integer} that is the number
+ * of seconds to wait before retransmitting a keep-alive probe. The socket
+ * option is specific to stream-oriented sockets using the TCP/IP protocol.
+ * The exact semantics of this socket option are system dependent.
+ *
+ * <p>
+ * When the {@link java.net.StandardSocketOptions#SO_KEEPALIVE
+ * SO_KEEPALIVE} option is enabled, TCP probes a connection that has been
+ * idle for some amount of time. If the remote system does not respond to a
+ * keep-alive probe, TCP retransmits the probe after some amount of time.
+ * The default value for this retransmission interval is system dependent,
+ * but is typically 75 seconds. The {@code TCP_KEEPINTERVAL} option can be
+ * used to affect this value for a given socket.
+ *
+ * @since 11
+ */
+ public static final SocketOption<Integer> TCP_KEEPINTERVAL
+ = new ExtSocketOption<Integer>("TCP_KEEPINTERVAL", Integer.class);
+
+ /**
+ * Keep-Alive retransmission maximum limit.
+ *
+ * <p>
+ * The value of this socket option is an {@code Integer} that is the maximum
+ * number of keep-alive probes to be sent. The socket option is specific to
+ * stream-oriented sockets using the TCP/IP protocol. The exact semantics of
+ * this socket option are system dependent.
+ *
+ * <p>
+ * When the {@link java.net.StandardSocketOptions#SO_KEEPALIVE
+ * SO_KEEPALIVE} option is enabled, TCP probes a connection that has been
+ * idle for some amount of time. If the remote system does not respond to a
+ * keep-alive probe, TCP retransmits the probe a certain number of times
+ * before a connection is considered to be broken. The default value for
+ * this keep-alive probe retransmit limit is system dependent, but is
+ * typically 8. The {@code TCP_KEEPCOUNT} option can be used to affect this
+ * value for a given socket.
+ *
+ * @since 11
+ */
+ public static final SocketOption<Integer> TCP_KEEPCOUNT
+ = new ExtSocketOption<Integer>("TCP_KEEPCOUNT", Integer.class);
+
+ private static final PlatformSocketOptions platformSocketOptions =
+ PlatformSocketOptions.get();
+
+ private static final boolean flowSupported =
+ platformSocketOptions.flowSupported();
+ private static final boolean quickAckSupported =
+ platformSocketOptions.quickAckSupported();
+ private static final boolean keepAliveOptSupported =
+ platformSocketOptions.keepAliveOptionsSupported();
+ private static final Set<SocketOption<?>> extendedOptions = options();
+
+ static Set<SocketOption<?>> options() {
+ Set<SocketOption<?>> options = new HashSet<>();
+ if (flowSupported) {
+ options.add(SO_FLOW_SLA);
+ }
+ if (quickAckSupported) {
+ options.add(TCP_QUICKACK);
+ }
+ if (keepAliveOptSupported) {
+ options.addAll(Set.of(TCP_KEEPCOUNT, TCP_KEEPIDLE, TCP_KEEPINTERVAL));
+ }
+ return Collections.unmodifiableSet(options);
+ }
+
+ static {
+ // Registers the extended socket options with the base module.
+ sun.net.ext.ExtendedSocketOptions.register(
+ new sun.net.ext.ExtendedSocketOptions(extendedOptions) {
+
+ @Override
+ public void setOption(FileDescriptor fd,
+ SocketOption<?> option,
+ Object value)
+ throws SocketException
+ {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new NetworkPermission("setOption." + option.name()));
+
+ if (fd == null || !fd.valid())
+ throw new SocketException("socket closed");
+
+ if (option == SO_FLOW_SLA) {
+ assert flowSupported;
+ SocketFlow flow = checkValueType(value, option.type());
+ setFlowOption(fd, flow);
+ } else if (option == TCP_QUICKACK) {
+ setQuickAckOption(fd, (boolean) value);
+ } else if (option == TCP_KEEPCOUNT) {
+ setTcpkeepAliveProbes(fd, (Integer) value);
+ } else if (option == TCP_KEEPIDLE) {
+ setTcpKeepAliveTime(fd, (Integer) value);
+ } else if (option == TCP_KEEPINTERVAL) {
+ setTcpKeepAliveIntvl(fd, (Integer) value);
+ } else {
+ throw new InternalError("Unexpected option " + option);
+ }
+ }
+
+ @Override
+ public Object getOption(FileDescriptor fd,
+ SocketOption<?> option)
+ throws SocketException
+ {
+ SecurityManager sm = System.getSecurityManager();
+ if (sm != null)
+ sm.checkPermission(new NetworkPermission("getOption." + option.name()));
+
+ if (fd == null || !fd.valid())
+ throw new SocketException("socket closed");
+
+ if (option == SO_FLOW_SLA) {
+ assert flowSupported;
+ SocketFlow flow = SocketFlow.create();
+ getFlowOption(fd, flow);
+ return flow;
+ } else if (option == TCP_QUICKACK) {
+ return getQuickAckOption(fd);
+ } else if (option == TCP_KEEPCOUNT) {
+ return getTcpkeepAliveProbes(fd);
+ } else if (option == TCP_KEEPIDLE) {
+ return getTcpKeepAliveTime(fd);
+ } else if (option == TCP_KEEPINTERVAL) {
+ return getTcpKeepAliveIntvl(fd);
+ } else {
+ throw new InternalError("Unexpected option " + option);
+ }
+ }
+ });
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T checkValueType(Object value, Class<?> type) {
+ if (!type.isAssignableFrom(value.getClass())) {
+ String s = "Found: " + value.getClass() + ", Expected: " + type;
+ throw new IllegalArgumentException(s);
+ }
+ return (T) value;
+ }
+
+ private static final JavaIOFileDescriptorAccess fdAccess =
+ SharedSecrets.getJavaIOFileDescriptorAccess();
+
+ private static void setFlowOption(FileDescriptor fd, SocketFlow f)
+ throws SocketException
+ {
+ int status = platformSocketOptions.setFlowOption(fdAccess.get(fd),
+ f.priority(),
+ f.bandwidth());
+ f.status(status); // augment the given flow with the status
+ }
+
+ private static void getFlowOption(FileDescriptor fd, SocketFlow f)
+ throws SocketException {
+ int status = platformSocketOptions.getFlowOption(fdAccess.get(fd), f);
+ f.status(status); // augment the given flow with the status
+ }
+
+ private static void setQuickAckOption(FileDescriptor fd, boolean enable)
+ throws SocketException {
+ platformSocketOptions.setQuickAck(fdAccess.get(fd), enable);
+ }
+
+ private static Object getQuickAckOption(FileDescriptor fd)
+ throws SocketException {
+ return platformSocketOptions.getQuickAck(fdAccess.get(fd));
+ }
+
+ private static void setTcpkeepAliveProbes(FileDescriptor fd, int value)
+ throws SocketException {
+ platformSocketOptions.setTcpkeepAliveProbes(fdAccess.get(fd), value);
+ }
+
+ private static void setTcpKeepAliveTime(FileDescriptor fd, int value)
+ throws SocketException {
+ platformSocketOptions.setTcpKeepAliveTime(fdAccess.get(fd), value);
+ }
+
+ private static void setTcpKeepAliveIntvl(FileDescriptor fd, int value)
+ throws SocketException {
+ platformSocketOptions.setTcpKeepAliveIntvl(fdAccess.get(fd), value);
+ }
+
+ private static int getTcpkeepAliveProbes(FileDescriptor fd) throws SocketException {
+ return platformSocketOptions.getTcpkeepAliveProbes(fdAccess.get(fd));
+ }
+
+ private static int getTcpKeepAliveTime(FileDescriptor fd) throws SocketException {
+ return platformSocketOptions.getTcpKeepAliveTime(fdAccess.get(fd));
+ }
+
+ private static int getTcpKeepAliveIntvl(FileDescriptor fd) throws SocketException {
+ return platformSocketOptions.getTcpKeepAliveIntvl(fdAccess.get(fd));
+ }
+
+ static class PlatformSocketOptions {
+
+ protected PlatformSocketOptions() {}
+
+ @SuppressWarnings("unchecked")
+ private static PlatformSocketOptions newInstance(String cn) {
+ Class<PlatformSocketOptions> c;
+ try {
+ c = (Class<PlatformSocketOptions>)Class.forName(cn);
+ return c.getConstructor(new Class<?>[] { }).newInstance();
+ } catch (ReflectiveOperationException x) {
+ throw new AssertionError(x);
+ }
+ }
+
+ private static PlatformSocketOptions create() {
+ // Android-removed: other platforms are unsupported.
+// String osname = AccessController.doPrivileged(
+// new PrivilegedAction<String>() {
+// public String run() {
+// return System.getProperty("os.name");
+// }
+// });
+// if ("SunOS".equals(osname)) {
+// return newInstance("jdk.net.SolarisSocketOptions");
+// } else if ("Linux".equals(osname)) {
+// return newInstance("jdk.net.LinuxSocketOptions");
+// } else if (osname.startsWith("Mac")) {
+// return newInstance("jdk.net.MacOSXSocketOptions");
+// }
+ return new PlatformSocketOptions();
+ }
+
+ private static final PlatformSocketOptions instance = create();
+
+ static PlatformSocketOptions get() {
+ return instance;
+ }
+
+ int setFlowOption(int fd, int priority, long bandwidth)
+ throws SocketException
+ {
+ throw new UnsupportedOperationException("unsupported socket option");
+ }
+
+ int getFlowOption(int fd, SocketFlow f) throws SocketException {
+ throw new UnsupportedOperationException("unsupported socket option");
+ }
+
+ boolean flowSupported() {
+ return false;
+ }
+
+ void setQuickAck(int fd, boolean on) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_QUICKACK option");
+ }
+
+ boolean getQuickAck(int fd) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_QUICKACK option");
+ }
+
+ boolean quickAckSupported() {
+ return false;
+ }
+
+ boolean keepAliveOptionsSupported() {
+ return false;
+ }
+
+ void setTcpkeepAliveProbes(int fd, final int value) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPCNT option");
+ }
+
+ void setTcpKeepAliveTime(int fd, final int value) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPIDLE option");
+ }
+
+ void setTcpKeepAliveIntvl(int fd, final int value) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPINTVL option");
+ }
+
+ int getTcpkeepAliveProbes(int fd) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPCNT option");
+ }
+
+ int getTcpKeepAliveTime(int fd) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPIDLE option");
+ }
+
+ int getTcpKeepAliveIntvl(int fd) throws SocketException {
+ throw new UnsupportedOperationException("unsupported TCP_KEEPINTVL option");
+ }
+ }
}
diff --git a/ojluni/src/main/java/jdk/net/SocketFlow.java b/ojluni/src/main/java/jdk/net/SocketFlow.java
index 59875a8..8777722 100644
--- a/ojluni/src/main/java/jdk/net/SocketFlow.java
+++ b/ojluni/src/main/java/jdk/net/SocketFlow.java
@@ -42,28 +42,33 @@
* <p>
* When a security manager is installed, a {@link NetworkPermission}
* is required to set or get this option.
+ *
+ * @since 1.8
*/
// Android-removed: @jdk.Exported, not present on Android.
// @jdk.Exported
public class SocketFlow {
- private static final int UNSET = -1;
+ @Native public static final int UNSET = -1;
@Native public static final int NORMAL_PRIORITY = 1;
@Native public static final int HIGH_PRIORITY = 2;
- private int priority = NORMAL_PRIORITY;
-
- private long bandwidth = UNSET;
-
- private Status status = Status.NO_STATUS;
-
- private SocketFlow() {}
+ @Native private static final int NO_STATUS_VALUE = 0;
+ @Native private static final int OK_VALUE = 1;
+ @Native private static final int NO_PERMISSION_VALUE = 2;
+ @Native private static final int NOT_CONNECTED_VALUE = 3;
+ @Native private static final int NOT_SUPPORTED_VALUE = 4;
+ @Native private static final int ALREADY_CREATED_VALUE = 5;
+ @Native private static final int IN_PROGRESS_VALUE = 6;
+ @Native private static final int OTHER_VALUE = 7;
/**
* Enumeration of the return values from the SO_FLOW_SLA
* socket option. Both setting and getting the option return
* one of these statuses, which reflect the state of socket's
* flow.
+ *
+ * @since 1.8
*/
// Android-removed: @jdk.Exported, not present on Android.
// @jdk.Exported
@@ -72,37 +77,56 @@
* Set or get socket option has not been called yet. Status
* values can only be retrieved after calling set or get.
*/
- NO_STATUS,
+ NO_STATUS(NO_STATUS_VALUE),
/**
* Flow successfully created.
*/
- OK,
+ OK(OK_VALUE),
/**
* Caller has no permission to create flow.
*/
- NO_PERMISSION,
+ NO_PERMISSION(NO_PERMISSION_VALUE),
/**
* Flow can not be created because socket is not connected.
*/
- NOT_CONNECTED,
+ NOT_CONNECTED(NOT_CONNECTED_VALUE),
/**
* Flow creation not supported for this socket.
*/
- NOT_SUPPORTED,
+ NOT_SUPPORTED(NOT_SUPPORTED_VALUE),
/**
* A flow already exists with identical attributes.
*/
- ALREADY_CREATED,
+ ALREADY_CREATED(ALREADY_CREATED_VALUE),
/**
* A flow is being created.
*/
- IN_PROGRESS,
+ IN_PROGRESS(IN_PROGRESS_VALUE),
/**
* Some other unspecified error.
*/
- OTHER
+ OTHER(OTHER_VALUE);
+
+ private final int value;
+ Status(int value) { this.value = value; }
+
+ static Status from(int value) {
+ if (value == NO_STATUS.value) return NO_STATUS;
+ else if (value == OK.value) return OK;
+ else if (value == NO_PERMISSION.value) return NO_PERMISSION;
+ else if (value == NOT_CONNECTED.value) return NOT_CONNECTED;
+ else if (value == NOT_SUPPORTED.value) return NOT_SUPPORTED;
+ else if (value == ALREADY_CREATED.value) return ALREADY_CREATED;
+ else if (value == IN_PROGRESS.value) return IN_PROGRESS;
+ else if (value == OTHER.value) return OTHER;
+ else throw new InternalError("Unknown value: " + value);
+ }
}
+ private int priority = NORMAL_PRIORITY;
+ private long bandwidth = UNSET;
+ private Status status = Status.NO_STATUS;
+
/**
* Creates a new SocketFlow that can be used to set the SO_FLOW_SLA
* socket option and create a socket flow.
@@ -111,6 +135,8 @@
return new SocketFlow();
}
+ private SocketFlow() { }
+
/**
* Sets this SocketFlow's priority. Must be either NORMAL_PRIORITY
* HIGH_PRIORITY. If not set, a flow's priority is normal.
@@ -119,9 +145,8 @@
* HIGH_PRIORITY.
*/
public SocketFlow priority(int priority) {
- if (priority != NORMAL_PRIORITY && priority != HIGH_PRIORITY) {
- throw new IllegalArgumentException("invalid priority");
- }
+ if (priority != NORMAL_PRIORITY && priority != HIGH_PRIORITY)
+ throw new IllegalArgumentException("invalid priority :" + priority);
this.priority = priority;
return this;
}
@@ -133,11 +158,9 @@
* @throws IllegalArgumentException if bandwidth is less than zero.
*/
public SocketFlow bandwidth(long bandwidth) {
- if (bandwidth < 0) {
- throw new IllegalArgumentException("invalid bandwidth");
- } else {
- this.bandwidth = bandwidth;
- }
+ if (bandwidth < 0)
+ throw new IllegalArgumentException("invalid bandwidth: " + bandwidth);
+ this.bandwidth = bandwidth;
return this;
}
@@ -164,4 +187,18 @@
public Status status() {
return status;
}
+
+ void status(int status) {
+ this.status = Status.from(status);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(super.toString());
+ sb.append(" [ priority=").append(priority())
+ .append(", bandwidth=").append(bandwidth())
+ .append(", status=").append(status())
+ .append(" ]");
+ return sb.toString();
+ }
}
diff --git a/ojluni/src/main/java/sun/net/ext/ExtendedSocketOptions.java b/ojluni/src/main/java/sun/net/ext/ExtendedSocketOptions.java
new file mode 100644
index 0000000..4036e11
--- /dev/null
+++ b/ojluni/src/main/java/sun/net/ext/ExtendedSocketOptions.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Oracle designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Oracle in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+
+package sun.net.ext;
+
+import java.io.FileDescriptor;
+import java.net.SocketException;
+import java.net.SocketOption;
+import java.util.Collections;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * Defines the infrastructure to support extended socket options, beyond those
+ * defined in {@link java.net.StandardSocketOptions}.
+ *
+ * Extended socket options are accessed through the jdk.net API, which is in
+ * the jdk.net module.
+ */
+public abstract class ExtendedSocketOptions {
+
+ public static final short SOCK_STREAM = 1;
+ public static final short SOCK_DGRAM = 2;
+
+ private final Set<SocketOption<?>> options;
+
+ /** Tells whether or not the option is supported. */
+ public final boolean isOptionSupported(SocketOption<?> option) {
+ return options().contains(option);
+ }
+
+ /** Return the, possibly empty, set of extended socket options available. */
+ public final Set<SocketOption<?>> options() { return options; }
+
+ public static final Set<SocketOption<?>> options(short type) {
+ return getInstance().options0(type);
+ }
+
+ private Set<SocketOption<?>> options0(short type) {
+ Set<SocketOption<?>> extOptions = null;
+ switch (type) {
+ case SOCK_DGRAM:
+ extOptions = options.stream()
+ .filter((option) -> !option.name().startsWith("TCP_"))
+ .collect(Collectors.toUnmodifiableSet());
+ break;
+ case SOCK_STREAM:
+ extOptions = options.stream()
+ .filter((option) -> !option.name().startsWith("UDP_"))
+ .collect(Collectors.toUnmodifiableSet());
+ break;
+ default:
+ //this will never happen
+ throw new IllegalArgumentException("Invalid socket option type");
+ }
+ return extOptions;
+ }
+
+ /** Sets the value of a socket option, for the given socket. */
+ public abstract void setOption(FileDescriptor fd, SocketOption<?> option, Object value)
+ throws SocketException;
+
+ /** Returns the value of a socket option, for the given socket. */
+ public abstract Object getOption(FileDescriptor fd, SocketOption<?> option)
+ throws SocketException;
+
+ protected ExtendedSocketOptions(Set<SocketOption<?>> options) {
+ this.options = options;
+ }
+
+ private static volatile ExtendedSocketOptions instance;
+
+ public static final ExtendedSocketOptions getInstance() { return instance; }
+
+ /** Registers support for extended socket options. Invoked by the jdk.net module. */
+ public static final void register(ExtendedSocketOptions extOptions) {
+ if (instance != null)
+ throw new InternalError("Attempting to reregister extended options");
+
+ instance = extOptions;
+ }
+
+ static {
+ try {
+ // If the class is present, it will be initialized which
+ // triggers registration of the extended socket options.
+ Class<?> c = Class.forName("jdk.net.ExtendedSocketOptions");
+ } catch (ClassNotFoundException e) {
+ // the jdk.net module is not present => no extended socket options
+ instance = new NoExtendedSocketOptions();
+ }
+ }
+
+ static final class NoExtendedSocketOptions extends ExtendedSocketOptions {
+
+ NoExtendedSocketOptions() {
+ super(Collections.<SocketOption<?>>emptySet());
+ }
+
+ @Override
+ public void setOption(FileDescriptor fd, SocketOption<?> option, Object value)
+ throws SocketException
+ {
+ throw new UnsupportedOperationException(
+ "no extended options: " + option.name());
+ }
+
+ @Override
+ public Object getOption(FileDescriptor fd, SocketOption<?> option)
+ throws SocketException
+ {
+ throw new UnsupportedOperationException(
+ "no extended options: " + option.name());
+ }
+ }
+}
diff --git a/ojluni/src/test/java/util/stream/Collectors/CollectorsTest.java b/ojluni/src/test/java/util/stream/Collectors/CollectorsTest.java
new file mode 100644
index 0000000..20e47a2
--- /dev/null
+++ b/ojluni/src/test/java/util/stream/Collectors/CollectorsTest.java
@@ -0,0 +1,752 @@
+/*
+ * Copyright (c) 2012, 2015, Oracle and/or its affiliates. All rights reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
+ * or visit www.oracle.com if you need additional information or have any
+ * questions.
+ */
+package test.java.util.stream.Collectors;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.StringJoiner;
+import java.util.TreeMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentSkipListMap;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.BinaryOperator;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import org.testng.annotations.Test;
+import org.openjdk.testlib.java.util.stream.LambdaTestHelpers;
+import org.openjdk.testlib.java.util.stream.StreamOpFlagTestHelper;
+import org.openjdk.testlib.java.util.stream.StreamTestDataProvider;
+import org.openjdk.testlib.java.util.stream.TestData;
+import org.openjdk.testlib.java.util.stream.OpTestCase;
+
+import static java.util.stream.Collectors.collectingAndThen;
+import static java.util.stream.Collectors.flatMapping;
+import static java.util.stream.Collectors.filtering;
+import static java.util.stream.Collectors.groupingBy;
+import static java.util.stream.Collectors.groupingByConcurrent;
+import static java.util.stream.Collectors.mapping;
+import static java.util.stream.Collectors.partitioningBy;
+import static java.util.stream.Collectors.reducing;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toConcurrentMap;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toMap;
+import static java.util.stream.Collectors.toSet;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContents;
+import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.assertContentsUnordered;
+import static org.openjdk.testlib.java.util.stream.LambdaTestHelpers.mDoubler;
+
+/*
+ * @test
+ * @bug 8071600 8144675
+ * @summary Test for collectors.
+ */
+public class CollectorsTest extends OpTestCase {
+
+ private abstract static class CollectorAssertion<T, U> {
+ abstract void assertValue(U value,
+ Supplier<Stream<T>> source,
+ boolean ordered) throws ReflectiveOperationException;
+ }
+
+ static class MappingAssertion<T, V, R> extends CollectorAssertion<T, R> {
+ private final Function<T, V> mapper;
+ private final CollectorAssertion<V, R> downstream;
+
+ MappingAssertion(Function<T, V> mapper, CollectorAssertion<V, R> downstream) {
+ this.mapper = mapper;
+ this.downstream = downstream;
+ }
+
+ @Override
+ void assertValue(R value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
+ downstream.assertValue(value,
+ () -> source.get().map(mapper::apply),
+ ordered);
+ }
+ }
+
+ static class FlatMappingAssertion<T, V, R> extends CollectorAssertion<T, R> {
+ private final Function<T, Stream<V>> mapper;
+ private final CollectorAssertion<V, R> downstream;
+
+ FlatMappingAssertion(Function<T, Stream<V>> mapper,
+ CollectorAssertion<V, R> downstream) {
+ this.mapper = mapper;
+ this.downstream = downstream;
+ }
+
+ @Override
+ void assertValue(R value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
+ downstream.assertValue(value,
+ () -> source.get().flatMap(mapper::apply),
+ ordered);
+ }
+ }
+
+ static class FilteringAssertion<T, R> extends CollectorAssertion<T, R> {
+ private final Predicate<T> filter;
+ private final CollectorAssertion<T, R> downstream;
+
+ public FilteringAssertion(Predicate<T> filter, CollectorAssertion<T, R> downstream) {
+ this.filter = filter;
+ this.downstream = downstream;
+ }
+
+ @Override
+ void assertValue(R value, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
+ downstream.assertValue(value,
+ () -> source.get().filter(filter),
+ ordered);
+ }
+ }
+
+ static class GroupingByAssertion<T, K, V, M extends Map<K, ? extends V>> extends CollectorAssertion<T, M> {
+ private final Class<? extends Map> clazz;
+ private final Function<T, K> classifier;
+ private final CollectorAssertion<T,V> downstream;
+
+ GroupingByAssertion(Function<T, K> classifier, Class<? extends Map> clazz,
+ CollectorAssertion<T, V> downstream) {
+ this.clazz = clazz;
+ this.classifier = classifier;
+ this.downstream = downstream;
+ }
+
+ @Override
+ void assertValue(M map,
+ Supplier<Stream<T>> source,
+ boolean ordered) throws ReflectiveOperationException {
+ if (!clazz.isAssignableFrom(map.getClass()))
+ fail(String.format("Class mismatch in GroupingByAssertion: %s, %s", clazz, map.getClass()));
+ assertContentsUnordered(map.keySet(), source.get().map(classifier).collect(toSet()));
+ for (Map.Entry<K, ? extends V> entry : map.entrySet()) {
+ K key = entry.getKey();
+ downstream.assertValue(entry.getValue(),
+ () -> source.get().filter(e -> classifier.apply(e).equals(key)),
+ ordered);
+ }
+ }
+ }
+
+ static class ToMapAssertion<T, K, V, M extends Map<K,V>> extends CollectorAssertion<T, M> {
+ private final Class<? extends Map> clazz;
+ private final Function<T, K> keyFn;
+ private final Function<T, V> valueFn;
+ private final BinaryOperator<V> mergeFn;
+
+ ToMapAssertion(Function<T, K> keyFn,
+ Function<T, V> valueFn,
+ BinaryOperator<V> mergeFn,
+ Class<? extends Map> clazz) {
+ this.clazz = clazz;
+ this.keyFn = keyFn;
+ this.valueFn = valueFn;
+ this.mergeFn = mergeFn;
+ }
+
+ @Override
+ void assertValue(M map, Supplier<Stream<T>> source, boolean ordered) throws ReflectiveOperationException {
+ if (!clazz.isAssignableFrom(map.getClass()))
+ fail(String.format("Class mismatch in ToMapAssertion: %s, %s", clazz, map.getClass()));
+ Set<K> uniqueKeys = source.get().map(keyFn).collect(toSet());
+ assertEquals(uniqueKeys, map.keySet());
+ source.get().forEach(t -> {
+ K key = keyFn.apply(t);
+ V v = source.get()
+ .filter(e -> key.equals(keyFn.apply(e)))
+ .map(valueFn)
+ .reduce(mergeFn)
+ .get();
+ assertEquals(map.get(key), v);
+ });
+ }
+ }
+
+ static class PartitioningByAssertion<T, D> extends CollectorAssertion<T, Map<Boolean,D>> {
+ private final Predicate<T> predicate;
+ private final CollectorAssertion<T,D> downstream;
+
+ PartitioningByAssertion(Predicate<T> predicate, CollectorAssertion<T, D> downstream) {
+ this.predicate = predicate;
+ this.downstream = downstream;
+ }
+
+ @Override
+ void assertValue(Map<Boolean, D> map,
+ Supplier<Stream<T>> source,
+ boolean ordered) throws ReflectiveOperationException {
+ if (!Map.class.isAssignableFrom(map.getClass()))
+ fail(String.format("Class mismatch in PartitioningByAssertion: %s", map.getClass()));
+ assertEquals(2, map.size());
+ downstream.assertValue(map.get(true), () -> source.get().filter(predicate), ordered);
+ downstream.assertValue(map.get(false), () -> source.get().filter(predicate.negate()), ordered);
+ }
+ }
+
+ static class ToListAssertion<T> extends CollectorAssertion<T, List<T>> {
+ @Override
+ void assertValue(List<T> value, Supplier<Stream<T>> source, boolean ordered)
+ throws ReflectiveOperationException {
+ if (!List.class.isAssignableFrom(value.getClass()))
+ fail(String.format("Class mismatch in ToListAssertion: %s", value.getClass()));
+ Stream<T> stream = source.get();
+ List<T> result = new ArrayList<>();
+ for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
+ result.add(it.next());
+ if (StreamOpFlagTestHelper.isStreamOrdered(stream) && ordered)
+ assertContents(value, result);
+ else
+ assertContentsUnordered(value, result);
+ }
+ }
+
+ static class ToCollectionAssertion<T> extends CollectorAssertion<T, Collection<T>> {
+ private final Class<? extends Collection> clazz;
+ private final boolean targetOrdered;
+
+ ToCollectionAssertion(Class<? extends Collection> clazz, boolean targetOrdered) {
+ this.clazz = clazz;
+ this.targetOrdered = targetOrdered;
+ }
+
+ @Override
+ void assertValue(Collection<T> value, Supplier<Stream<T>> source, boolean ordered)
+ throws ReflectiveOperationException {
+ if (!clazz.isAssignableFrom(value.getClass()))
+ fail(String.format("Class mismatch in ToCollectionAssertion: %s, %s", clazz, value.getClass()));
+ Stream<T> stream = source.get();
+ Collection<T> result = clazz.newInstance();
+ for (Iterator<T> it = stream.iterator(); it.hasNext(); ) // avoid capturing result::add
+ result.add(it.next());
+ if (StreamOpFlagTestHelper.isStreamOrdered(stream) && targetOrdered && ordered)
+ assertContents(value, result);
+ else
+ assertContentsUnordered(value, result);
+ }
+ }
+
+ static class ReducingAssertion<T, U> extends CollectorAssertion<T, U> {
+ private final U identity;
+ private final Function<T, U> mapper;
+ private final BinaryOperator<U> reducer;
+
+ ReducingAssertion(U identity, Function<T, U> mapper, BinaryOperator<U> reducer) {
+ this.identity = identity;
+ this.mapper = mapper;
+ this.reducer = reducer;
+ }
+
+ @Override
+ void assertValue(U value, Supplier<Stream<T>> source, boolean ordered)
+ throws ReflectiveOperationException {
+ Optional<U> reduced = source.get().map(mapper).reduce(reducer);
+ if (value == null)
+ assertTrue(!reduced.isPresent());
+ else if (!reduced.isPresent()) {
+ assertEquals(value, identity);
+ }
+ else {
+ assertEquals(value, reduced.get());
+ }
+ }
+ }
+
+ private <T> ResultAsserter<T> mapTabulationAsserter(boolean ordered) {
+ return (act, exp, ord, par) -> {
+ if (par && (!ordered || !ord)) {
+ CollectorsTest.nestedMapEqualityAssertion(act, exp);
+ }
+ else {
+ LambdaTestHelpers.assertContentsEqual(act, exp);
+ }
+ };
+ }
+
+ private<T, M extends Map>
+ void exerciseMapCollection(TestData<T, Stream<T>> data,
+ Collector<T, ?, ? extends M> collector,
+ CollectorAssertion<T, M> assertion)
+ throws ReflectiveOperationException {
+ boolean ordered = !collector.characteristics().contains(Collector.Characteristics.UNORDERED);
+
+ M m = withData(data)
+ .terminal(s -> s.collect(collector))
+ .resultAsserter(mapTabulationAsserter(ordered))
+ .exercise();
+ assertion.assertValue(m, () -> data.stream(), ordered);
+
+ m = withData(data)
+ .terminal(s -> s.unordered().collect(collector))
+ .resultAsserter(mapTabulationAsserter(ordered))
+ .exercise();
+ assertion.assertValue(m, () -> data.stream(), false);
+ }
+
+ private static void nestedMapEqualityAssertion(Object o1, Object o2) {
+ if (o1 instanceof Map) {
+ Map m1 = (Map) o1;
+ Map m2 = (Map) o2;
+ assertContentsUnordered(m1.keySet(), m2.keySet());
+ for (Object k : m1.keySet())
+ nestedMapEqualityAssertion(m1.get(k), m2.get(k));
+ }
+ else if (o1 instanceof Collection) {
+ assertContentsUnordered(((Collection) o1), ((Collection) o2));
+ }
+ else
+ assertEquals(o1, o2);
+ }
+
+ private<T, R> void assertCollect(TestData.OfRef<T> data,
+ Collector<T, ?, R> collector,
+ Function<Stream<T>, R> streamReduction) {
+ R check = streamReduction.apply(data.stream());
+ withData(data).terminal(s -> s.collect(collector)).expectedResult(check).exercise();
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testReducing(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ assertCollect(data, Collectors.reducing(0, Integer::sum),
+ s -> s.reduce(0, Integer::sum));
+ assertCollect(data, Collectors.reducing(Integer.MAX_VALUE, Integer::min),
+ s -> s.min(Integer::compare).orElse(Integer.MAX_VALUE));
+ assertCollect(data, Collectors.reducing(Integer.MIN_VALUE, Integer::max),
+ s -> s.max(Integer::compare).orElse(Integer.MIN_VALUE));
+
+ assertCollect(data, Collectors.reducing(Integer::sum),
+ s -> s.reduce(Integer::sum));
+ assertCollect(data, Collectors.minBy(Comparator.naturalOrder()),
+ s -> s.min(Integer::compare));
+ assertCollect(data, Collectors.maxBy(Comparator.naturalOrder()),
+ s -> s.max(Integer::compare));
+
+ assertCollect(data, Collectors.reducing(0, x -> x*2, Integer::sum),
+ s -> s.map(x -> x*2).reduce(0, Integer::sum));
+
+ assertCollect(data, Collectors.summingLong(x -> x * 2L),
+ s -> s.map(x -> x*2L).reduce(0L, Long::sum));
+ assertCollect(data, Collectors.summingInt(x -> x * 2),
+ s -> s.map(x -> x*2).reduce(0, Integer::sum));
+ assertCollect(data, Collectors.summingDouble(x -> x * 2.0d),
+ s -> s.map(x -> x * 2.0d).reduce(0.0d, Double::sum));
+
+ assertCollect(data, Collectors.averagingInt(x -> x * 2),
+ s -> s.mapToInt(x -> x * 2).average().orElse(0));
+ assertCollect(data, Collectors.averagingLong(x -> x * 2),
+ s -> s.mapToLong(x -> x * 2).average().orElse(0));
+ assertCollect(data, Collectors.averagingDouble(x -> x * 2),
+ s -> s.mapToDouble(x -> x * 2).average().orElse(0));
+
+ // Test explicit Collector.of
+ Collector<Integer, long[], Double> avg2xint = Collector.of(() -> new long[2],
+ (a, b) -> {
+ a[0] += b * 2;
+ a[1]++;
+ },
+ (a, b) -> {
+ a[0] += b[0];
+ a[1] += b[1];
+ return a;
+ },
+ a -> a[1] == 0 ? 0.0d : (double) a[0] / a[1]);
+ assertCollect(data, avg2xint,
+ s -> s.mapToInt(x -> x * 2).average().orElse(0));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testJoining(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ withData(data)
+ .terminal(s -> s.map(Object::toString).collect(Collectors.joining()))
+ .expectedResult(join(data, ""))
+ .exercise();
+
+ Collector<String, StringBuilder, String> likeJoining = Collector.of(StringBuilder::new, StringBuilder::append, (sb1, sb2) -> sb1.append(sb2.toString()), StringBuilder::toString);
+ withData(data)
+ .terminal(s -> s.map(Object::toString).collect(likeJoining))
+ .expectedResult(join(data, ""))
+ .exercise();
+
+ withData(data)
+ .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",")))
+ .expectedResult(join(data, ","))
+ .exercise();
+
+ withData(data)
+ .terminal(s -> s.map(Object::toString).collect(Collectors.joining(",", "[", "]")))
+ .expectedResult("[" + join(data, ",") + "]")
+ .exercise();
+
+ withData(data)
+ .terminal(s -> s.map(Object::toString)
+ .collect(StringBuilder::new, StringBuilder::append, StringBuilder::append)
+ .toString())
+ .expectedResult(join(data, ""))
+ .exercise();
+
+ withData(data)
+ .terminal(s -> s.map(Object::toString)
+ .collect(() -> new StringJoiner(","),
+ (sj, cs) -> sj.add(cs),
+ (j1, j2) -> j1.merge(j2))
+ .toString())
+ .expectedResult(join(data, ","))
+ .exercise();
+
+ withData(data)
+ .terminal(s -> s.map(Object::toString)
+ .collect(() -> new StringJoiner(",", "[", "]"),
+ (sj, cs) -> sj.add(cs),
+ (j1, j2) -> j1.merge(j2))
+ .toString())
+ .expectedResult("[" + join(data, ",") + "]")
+ .exercise();
+ }
+
+ private<T> String join(TestData.OfRef<T> data, String delim) {
+ StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (T i : data) {
+ if (!first)
+ sb.append(delim);
+ sb.append(i.toString());
+ first = false;
+ }
+ return sb.toString();
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testSimpleToMap(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> keyFn = i -> i * 2;
+ Function<Integer, Integer> valueFn = i -> i * 4;
+
+ List<Integer> dataAsList = Arrays.asList(data.stream().toArray(Integer[]::new));
+ Set<Integer> dataAsSet = new HashSet<>(dataAsList);
+
+ BinaryOperator<Integer> sum = Integer::sum;
+ for (BinaryOperator<Integer> op : Arrays.asList((u, v) -> u,
+ (u, v) -> v,
+ sum)) {
+ try {
+ exerciseMapCollection(data, toMap(keyFn, valueFn),
+ new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
+ if (dataAsList.size() != dataAsSet.size())
+ fail("Expected ISE on input with duplicates");
+ }
+ catch (IllegalStateException e) {
+ if (dataAsList.size() == dataAsSet.size())
+ fail("Expected no ISE on input without duplicates");
+ }
+
+ exerciseMapCollection(data, toMap(keyFn, valueFn, op),
+ new ToMapAssertion<>(keyFn, valueFn, op, HashMap.class));
+
+ exerciseMapCollection(data, toMap(keyFn, valueFn, op, TreeMap::new),
+ new ToMapAssertion<>(keyFn, valueFn, op, TreeMap.class));
+ }
+
+ // For concurrent maps, only use commutative merge functions
+ try {
+ exerciseMapCollection(data, toConcurrentMap(keyFn, valueFn),
+ new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
+ if (dataAsList.size() != dataAsSet.size())
+ fail("Expected ISE on input with duplicates");
+ }
+ catch (IllegalStateException e) {
+ if (dataAsList.size() == dataAsSet.size())
+ fail("Expected no ISE on input without duplicates");
+ }
+
+ exerciseMapCollection(data, toConcurrentMap(keyFn, valueFn, sum),
+ new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentHashMap.class));
+
+ exerciseMapCollection(data, toConcurrentMap(keyFn, valueFn, sum, ConcurrentSkipListMap::new),
+ new ToMapAssertion<>(keyFn, valueFn, sum, ConcurrentSkipListMap.class));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testSimpleGroupingBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 3;
+
+ // Single-level groupBy
+ exerciseMapCollection(data, groupingBy(classifier),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new ToListAssertion<>()));
+ exerciseMapCollection(data, groupingByConcurrent(classifier),
+ new GroupingByAssertion<>(classifier, ConcurrentHashMap.class,
+ new ToListAssertion<>()));
+
+ // With explicit constructors
+ exerciseMapCollection(data,
+ groupingBy(classifier, TreeMap::new, toCollection(HashSet::new)),
+ new GroupingByAssertion<>(classifier, TreeMap.class,
+ new ToCollectionAssertion<Integer>(HashSet.class, false)));
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, ConcurrentSkipListMap::new,
+ toCollection(HashSet::new)),
+ new GroupingByAssertion<>(classifier, ConcurrentSkipListMap.class,
+ new ToCollectionAssertion<Integer>(HashSet.class, false)));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testGroupingByWithMapping(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 3;
+ Function<Integer, Integer> mapper = i -> i * 2;
+
+ exerciseMapCollection(data,
+ groupingBy(classifier, mapping(mapper, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new MappingAssertion<>(mapper,
+ new ToListAssertion<>())));
+ }
+
+ @Test(groups = { "serialization-hostile" })
+ public void testFlatMappingClose() {
+ Function<Integer, Integer> classifier = i -> i;
+ AtomicInteger ai = new AtomicInteger();
+ Function<Integer, Stream<Integer>> flatMapper = i -> Stream.of(i, i).onClose(ai::getAndIncrement);
+ Map<Integer, List<Integer>> m = Stream.of(1, 2).collect(groupingBy(classifier, flatMapping(flatMapper, toList())));
+ assertEquals(m.size(), ai.get());
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testGroupingByWithFlatMapping(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 3;
+ Function<Integer, Stream<Integer>> flatMapperByNull = i -> null;
+ Function<Integer, Stream<Integer>> flatMapperBy0 = i -> Stream.empty();
+ Function<Integer, Stream<Integer>> flatMapperBy2 = i -> Stream.of(i, i);
+
+ exerciseMapCollection(data,
+ groupingBy(classifier, flatMapping(flatMapperByNull, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FlatMappingAssertion<>(flatMapperBy0,
+ new ToListAssertion<>())));
+ exerciseMapCollection(data,
+ groupingBy(classifier, flatMapping(flatMapperBy0, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FlatMappingAssertion<>(flatMapperBy0,
+ new ToListAssertion<>())));
+ exerciseMapCollection(data,
+ groupingBy(classifier, flatMapping(flatMapperBy2, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FlatMappingAssertion<>(flatMapperBy2,
+ new ToListAssertion<>())));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testGroupingByWithFiltering(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 3;
+ Predicate<Integer> filteringByMod2 = i -> i % 2 == 0;
+ Predicate<Integer> filteringByUnder100 = i -> i % 2 < 100;
+ Predicate<Integer> filteringByTrue = i -> true;
+ Predicate<Integer> filteringByFalse = i -> false;
+
+ exerciseMapCollection(data,
+ groupingBy(classifier, filtering(filteringByMod2, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FilteringAssertion<>(filteringByMod2,
+ new ToListAssertion<>())));
+ exerciseMapCollection(data,
+ groupingBy(classifier, filtering(filteringByUnder100, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FilteringAssertion<>(filteringByUnder100,
+ new ToListAssertion<>())));
+ exerciseMapCollection(data,
+ groupingBy(classifier, filtering(filteringByTrue, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FilteringAssertion<>(filteringByTrue,
+ new ToListAssertion<>())));
+ exerciseMapCollection(data,
+ groupingBy(classifier, filtering(filteringByFalse, toList())),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new FilteringAssertion<>(filteringByFalse,
+ new ToListAssertion<>())));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testTwoLevelGroupingBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 6;
+ Function<Integer, Integer> classifier2 = i -> i % 23;
+
+ // Two-level groupBy
+ exerciseMapCollection(data,
+ groupingBy(classifier, groupingBy(classifier2)),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new GroupingByAssertion<>(classifier2, HashMap.class,
+ new ToListAssertion<>())));
+ // with concurrent as upstream
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, groupingBy(classifier2)),
+ new GroupingByAssertion<>(classifier, ConcurrentHashMap.class,
+ new GroupingByAssertion<>(classifier2, HashMap.class,
+ new ToListAssertion<>())));
+ // with concurrent as downstream
+ exerciseMapCollection(data,
+ groupingBy(classifier, groupingByConcurrent(classifier2)),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new GroupingByAssertion<>(classifier2, ConcurrentHashMap.class,
+ new ToListAssertion<>())));
+ // with concurrent as upstream and downstream
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, groupingByConcurrent(classifier2)),
+ new GroupingByAssertion<>(classifier, ConcurrentHashMap.class,
+ new GroupingByAssertion<>(classifier2, ConcurrentHashMap.class,
+ new ToListAssertion<>())));
+
+ // With explicit constructors
+ exerciseMapCollection(data,
+ groupingBy(classifier, TreeMap::new, groupingBy(classifier2, TreeMap::new, toCollection(HashSet::new))),
+ new GroupingByAssertion<>(classifier, TreeMap.class,
+ new GroupingByAssertion<>(classifier2, TreeMap.class,
+ new ToCollectionAssertion<Integer>(HashSet.class, false))));
+ // with concurrent as upstream
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingBy(classifier2, TreeMap::new, toList())),
+ new GroupingByAssertion<>(classifier, ConcurrentSkipListMap.class,
+ new GroupingByAssertion<>(classifier2, TreeMap.class,
+ new ToListAssertion<>())));
+ // with concurrent as downstream
+ exerciseMapCollection(data,
+ groupingBy(classifier, TreeMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
+ new GroupingByAssertion<>(classifier, TreeMap.class,
+ new GroupingByAssertion<>(classifier2, ConcurrentSkipListMap.class,
+ new ToListAssertion<>())));
+ // with concurrent as upstream and downstream
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, ConcurrentSkipListMap::new, groupingByConcurrent(classifier2, ConcurrentSkipListMap::new, toList())),
+ new GroupingByAssertion<>(classifier, ConcurrentSkipListMap.class,
+ new GroupingByAssertion<>(classifier2, ConcurrentSkipListMap.class,
+ new ToListAssertion<>())));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testGroupubgByWithReducing(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Function<Integer, Integer> classifier = i -> i % 3;
+
+ // Single-level simple reduce
+ exerciseMapCollection(data,
+ groupingBy(classifier, reducing(0, Integer::sum)),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new ReducingAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
+ // with concurrent
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, reducing(0, Integer::sum)),
+ new GroupingByAssertion<>(classifier, ConcurrentHashMap.class,
+ new ReducingAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
+
+ // With explicit constructors
+ exerciseMapCollection(data,
+ groupingBy(classifier, TreeMap::new, reducing(0, Integer::sum)),
+ new GroupingByAssertion<>(classifier, TreeMap.class,
+ new ReducingAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
+ // with concurrent
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, Integer::sum)),
+ new GroupingByAssertion<>(classifier, ConcurrentSkipListMap.class,
+ new ReducingAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
+
+ // Single-level map-reduce
+ exerciseMapCollection(data,
+ groupingBy(classifier, reducing(0, mDoubler, Integer::sum)),
+ new GroupingByAssertion<>(classifier, HashMap.class,
+ new ReducingAssertion<>(0, mDoubler, Integer::sum)));
+ // with concurrent
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, reducing(0, mDoubler, Integer::sum)),
+ new GroupingByAssertion<>(classifier, ConcurrentHashMap.class,
+ new ReducingAssertion<>(0, mDoubler, Integer::sum)));
+
+ // With explicit constructors
+ exerciseMapCollection(data,
+ groupingBy(classifier, TreeMap::new, reducing(0, mDoubler, Integer::sum)),
+ new GroupingByAssertion<>(classifier, TreeMap.class,
+ new ReducingAssertion<>(0, mDoubler, Integer::sum)));
+ // with concurrent
+ exerciseMapCollection(data,
+ groupingByConcurrent(classifier, ConcurrentSkipListMap::new, reducing(0, mDoubler, Integer::sum)),
+ new GroupingByAssertion<>(classifier, ConcurrentSkipListMap.class,
+ new ReducingAssertion<>(0, mDoubler, Integer::sum)));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testSimplePartitioningBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Predicate<Integer> classifier = i -> i % 3 == 0;
+
+ // Single-level partition to downstream List
+ exerciseMapCollection(data,
+ partitioningBy(classifier),
+ new PartitioningByAssertion<>(classifier, new ToListAssertion<>()));
+ exerciseMapCollection(data,
+ partitioningBy(classifier, toList()),
+ new PartitioningByAssertion<>(classifier, new ToListAssertion<>()));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testTwoLevelPartitioningBy(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ Predicate<Integer> classifier = i -> i % 3 == 0;
+ Predicate<Integer> classifier2 = i -> i % 7 == 0;
+
+ // Two level partition
+ exerciseMapCollection(data,
+ partitioningBy(classifier, partitioningBy(classifier2)),
+ new PartitioningByAssertion<>(classifier,
+ new PartitioningByAssertion(classifier2, new ToListAssertion<>())));
+
+ // Two level partition with reduce
+ exerciseMapCollection(data,
+ partitioningBy(classifier, reducing(0, Integer::sum)),
+ new PartitioningByAssertion<>(classifier,
+ new ReducingAssertion<>(0, LambdaTestHelpers.identity(), Integer::sum)));
+ }
+
+ @Test(dataProvider = "StreamTestData<Integer>", dataProviderClass = StreamTestDataProvider.class)
+ public void testComposeFinisher(String name, TestData.OfRef<Integer> data) throws ReflectiveOperationException {
+ List<Integer> asList = exerciseTerminalOps(data, s -> s.collect(toList()));
+ List<Integer> asImmutableList = exerciseTerminalOps(data, s -> s.collect(collectingAndThen(toList(), Collections::unmodifiableList)));
+ assertEquals(asList, asImmutableList);
+ try {
+ asImmutableList.add(0);
+ fail("Expecting immutable result");
+ }
+ catch (UnsupportedOperationException ignored) { }
+ }
+
+}
\ No newline at end of file
diff --git a/openjdk_java_files.bp b/openjdk_java_files.bp
index 4ace742..99971d1e 100644
--- a/openjdk_java_files.bp
+++ b/openjdk_java_files.bp
@@ -1503,6 +1503,7 @@
"ojluni/src/main/java/sun/net/ApplicationProxy.java",
"ojluni/src/main/java/sun/net/ConnectionResetException.java",
"ojluni/src/main/java/sun/net/ExtendedOptionsImpl.java",
+ "ojluni/src/main/java/sun/net/ext/ExtendedSocketOptions.java",
"ojluni/src/main/java/sun/net/ftp/FtpClient.java",
"ojluni/src/main/java/sun/net/ftp/FtpClientProvider.java",
"ojluni/src/main/java/sun/net/ftp/FtpDirEntry.java",