8178117: Add public state constructors for Int/Long/DoubleSummaryStatistics
authorpsandoz
Tue, 11 Apr 2017 17:25:09 -0400
changeset 47486 9a3682829bbb
parent 47485 8fee80b92e65
child 47487 c15c00e48c3a
8178117: Add public state constructors for Int/Long/DoubleSummaryStatistics Reviewed-by: psandoz, bpb, briangoetz Contributed-by: Chris Dennis <chris.w.dennis@gmail.com>
src/java.base/share/classes/java/util/DoubleSummaryStatistics.java
src/java.base/share/classes/java/util/IntSummaryStatistics.java
src/java.base/share/classes/java/util/LongSummaryStatistics.java
test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java
--- a/src/java.base/share/classes/java/util/DoubleSummaryStatistics.java	Thu Nov 02 11:16:27 2017 +0100
+++ b/src/java.base/share/classes/java/util/DoubleSummaryStatistics.java	Tue Apr 11 17:25:09 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2015, 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
@@ -26,6 +26,7 @@
 
 import java.util.function.DoubleConsumer;
 import java.util.stream.Collector;
+import java.util.stream.DoubleStream;
 
 /**
  * A state object for collecting statistics such as count, min, max, sum, and
@@ -69,13 +70,66 @@
     private double max = Double.NEGATIVE_INFINITY;
 
     /**
-     * Construct an empty instance with zero count, zero sum,
+     * Constructs an empty instance with zero count, zero sum,
      * {@code Double.POSITIVE_INFINITY} min, {@code Double.NEGATIVE_INFINITY}
      * max and zero average.
      */
     public DoubleSummaryStatistics() { }
 
     /**
+     * Constructs a non-empty instance with the specified {@code count},
+     * {@code min}, {@code max}, and {@code sum}.
+     *
+     * <p>If {@code count} is zero then the remaining arguments are ignored and
+     * an empty instance is constructed.
+     *
+     * <p>If the arguments are inconsistent then an {@code IllegalArgumentException}
+     * is thrown.  The necessary consistent argument conditions are:
+     * <ul>
+     *   <li>{@code count >= 0}</li>
+     *   <li>{@code (min <= max && !isNaN(sum)) || (isNaN(min) && isNaN(max) && isNaN(sum))}</li>
+     * </ul>
+     * @apiNote
+     * The enforcement of argument correctness means that the retrieved set of
+     * recorded values obtained from a {@code DoubleSummaryStatistics} source
+     * instance may not be a legal set of arguments for this constructor due to
+     * arithmetic overflow of the source's recorded count of values.
+     * The consistent argument conditions are not sufficient to prevent the
+     * creation of an internally inconsistent instance.  An example of such a
+     * state would be an instance with: {@code count} = 2, {@code min} = 1,
+     * {@code max} = 2, and {@code sum} = 0.
+     *
+     * @param count the count of values
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param sum the sum of all values
+     * @throws IllegalArgumentException if the arguments are inconsistent
+     * @since 10
+     */
+    public DoubleSummaryStatistics(long count, double min, double max, double sum)
+            throws IllegalArgumentException {
+        if (count < 0L) {
+            throw new IllegalArgumentException("Negative count value");
+        } else if (count > 0L) {
+            if (min > max)
+                throw new IllegalArgumentException("Minimum greater than maximum");
+
+            // All NaN or non NaN
+            var ncount = DoubleStream.of(min, max, sum).filter(Double::isNaN).count();
+            if (ncount > 0 && ncount < 3)
+                throw new IllegalArgumentException("Some, not all, of the minimum, maximum, or sum is NaN");
+
+            this.count = count;
+            this.sum = sum;
+            this.simpleSum = sum;
+            this.sumCompensation = 0.0d;
+            this.min = min;
+            this.max = max;
+        }
+        // Use default field values if count == 0
+    }
+
+    /**
      * Records another value into the summary information.
      *
      * @param value the input value
--- a/src/java.base/share/classes/java/util/IntSummaryStatistics.java	Thu Nov 02 11:16:27 2017 +0100
+++ b/src/java.base/share/classes/java/util/IntSummaryStatistics.java	Tue Apr 11 17:25:09 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2015, 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
@@ -69,13 +69,58 @@
     private int max = Integer.MIN_VALUE;
 
     /**
-     * Construct an empty instance with zero count, zero sum,
+     * Constructs an empty instance with zero count, zero sum,
      * {@code Integer.MAX_VALUE} min, {@code Integer.MIN_VALUE} max and zero
      * average.
      */
     public IntSummaryStatistics() { }
 
     /**
+     * Constructs a non-empty instance with the specified {@code count},
+     * {@code min}, {@code max}, and {@code sum}.
+     *
+     * <p>If {@code count} is zero then the remaining arguments are ignored and
+     * an empty instance is constructed.
+     *
+     * <p>If the arguments are inconsistent then an {@code IllegalArgumentException}
+     * is thrown.  The necessary consistent argument conditions are:
+     * <ul>
+     *   <li>{@code count >= 0}</li>
+     *   <li>{@code min <= max}</li>
+     * </ul>
+     * @apiNote
+     * The enforcement of argument correctness means that the retrieved set of
+     * recorded values obtained from a {@code IntSummaryStatistics} source
+     * instance may not be a legal set of arguments for this constructor due to
+     * arithmetic overflow of the source's recorded count of values.
+     * The consistent argument conditions are not sufficient to prevent the
+     * creation of an internally inconsistent instance.  An example of such a
+     * state would be an instance with: {@code count} = 2, {@code min} = 1,
+     * {@code max} = 2, and {@code sum} = 0.
+     *
+     * @param count the count of values
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param sum the sum of all values
+     * @throws IllegalArgumentException if the arguments are inconsistent
+     * @since 10
+     */
+    public IntSummaryStatistics(long count, int min, int max, long sum)
+            throws IllegalArgumentException {
+        if (count < 0L) {
+            throw new IllegalArgumentException("Negative count value");
+        } else if (count > 0L) {
+            if (min > max) throw new IllegalArgumentException("Minimum greater than maximum");
+
+            this.count = count;
+            this.sum = sum;
+            this.min = min;
+            this.max = max;
+        }
+        // Use default field values if count == 0
+    }
+
+    /**
      * Records a new value into the summary information
      *
      * @param value the input value
--- a/src/java.base/share/classes/java/util/LongSummaryStatistics.java	Thu Nov 02 11:16:27 2017 +0100
+++ b/src/java.base/share/classes/java/util/LongSummaryStatistics.java	Tue Apr 11 17:25:09 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2015, 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
@@ -70,13 +70,58 @@
     private long max = Long.MIN_VALUE;
 
     /**
-     * Construct an empty instance with zero count, zero sum,
+     * Constructs an empty instance with zero count, zero sum,
      * {@code Long.MAX_VALUE} min, {@code Long.MIN_VALUE} max and zero
      * average.
      */
     public LongSummaryStatistics() { }
 
     /**
+     * Constructs a non-empty instance with the specified {@code count},
+     * {@code min}, {@code max}, and {@code sum}.
+     *
+     * <p>If {@code count} is zero then the remaining arguments are ignored and
+     * an empty instance is constructed.
+     *
+     * <p>If the arguments are inconsistent then an {@code IllegalArgumentException}
+     * is thrown.  The necessary consistent argument conditions are:
+     * <ul>
+     *   <li>{@code count >= 0}</li>
+     *   <li>{@code min <= max}</li>
+     * </ul>
+     * @apiNote
+     * The enforcement of argument correctness means that the retrieved set of
+     * recorded values obtained from a {@code LongSummaryStatistics} source
+     * instance may not be a legal set of arguments for this constructor due to
+     * arithmetic overflow of the source's recorded count of values.
+     * The consistent argument conditions are not sufficient to prevent the
+     * creation of an internally inconsistent instance.  An example of such a
+     * state would be an instance with: {@code count} = 2, {@code min} = 1,
+     * {@code max} = 2, and {@code sum} = 0.
+     *
+     * @param count the count of values
+     * @param min the minimum value
+     * @param max the maximum value
+     * @param sum the sum of all values
+     * @throws IllegalArgumentException if the arguments are inconsistent
+     * @since 10
+     */
+    public LongSummaryStatistics(long count, long min, long max, long sum)
+            throws IllegalArgumentException {
+        if (count < 0L) {
+            throw new IllegalArgumentException("Negative count value");
+        } else if (count > 0L) {
+            if (min > max) throw new IllegalArgumentException("Minimum greater than maximum");
+
+            this.count = count;
+            this.sum = sum;
+            this.min = min;
+            this.max = max;
+        }
+        // Use default field values if count == 0
+    }
+
+    /**
      * Records a new {@code int} value into the summary information.
      *
      * @param value the input value
--- a/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java	Thu Nov 02 11:16:27 2017 +0100
+++ b/test/jdk/java/util/stream/test/org/openjdk/tests/java/util/stream/CollectAndSummaryStatisticsTest.java	Tue Apr 11 17:25:09 2017 -0400
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2016, 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
@@ -24,7 +24,7 @@
 /*
  * @test
  * @summary primtive stream collection with summary statistics
- * @bug 8044047
+ * @bug 8044047 8178117
  */
 
 package org.openjdk.tests.java.util.stream;
@@ -91,11 +91,19 @@
         instances.add(countTo(1000).stream().mapToInt(i -> i).collect(IntSummaryStatistics::new,
                                                                       IntSummaryStatistics::accept,
                                                                       IntSummaryStatistics::combine));
+        instances.add(countTo(1000).stream().mapToInt(i -> i).collect(() -> new IntSummaryStatistics(0, -1, 1001, 2),
+                                                                      IntSummaryStatistics::accept,
+                                                                      IntSummaryStatistics::combine));
         instances.add(countTo(1000).parallelStream().collect(Collectors.summarizingInt(i -> i)));
         instances.add(countTo(1000).parallelStream().mapToInt(i -> i).summaryStatistics());
         instances.add(countTo(1000).parallelStream().mapToInt(i -> i).collect(IntSummaryStatistics::new,
                                                                               IntSummaryStatistics::accept,
                                                                               IntSummaryStatistics::combine));
+        instances.add(countTo(1000).parallelStream().mapToInt(i -> i).collect(() -> new IntSummaryStatistics(0, -1, 1001, 2),
+                                                                              IntSummaryStatistics::accept,
+                                                                              IntSummaryStatistics::combine));
+        IntSummaryStatistics original = instances.get(0);
+        instances.add(new IntSummaryStatistics(original.getCount(), original.getMin(), original.getMax(), original.getSum()));
 
         for (IntSummaryStatistics stats : instances) {
             assertEquals(stats.getCount(), 1000);
@@ -104,6 +112,9 @@
             assertEquals(stats.getMax(), 1000);
             assertEquals(stats.getMin(), 1);
         }
+
+        expectThrows(IllegalArgumentException.class, () -> new IntSummaryStatistics(-1, 0, 0, 0));
+        expectThrows(IllegalArgumentException.class, () -> new IntSummaryStatistics(1, 3, 2, 0));
     }
 
 
@@ -114,11 +125,19 @@
         instances.add(countTo(1000).stream().mapToLong(i -> i).collect(LongSummaryStatistics::new,
                                                                        LongSummaryStatistics::accept,
                                                                        LongSummaryStatistics::combine));
+        instances.add(countTo(1000).stream().mapToInt(i -> i).collect(() -> new LongSummaryStatistics(0, -1, 1001, 2),
+                                                                      LongSummaryStatistics::accept,
+                                                                      LongSummaryStatistics::combine));
         instances.add(countTo(1000).parallelStream().collect(Collectors.summarizingLong(i -> i)));
         instances.add(countTo(1000).parallelStream().mapToLong(i -> i).summaryStatistics());
         instances.add(countTo(1000).parallelStream().mapToLong(i -> i).collect(LongSummaryStatistics::new,
                                                                                LongSummaryStatistics::accept,
                                                                                LongSummaryStatistics::combine));
+        instances.add(countTo(1000).parallelStream().mapToInt(i -> i).collect(() -> new LongSummaryStatistics(0, -1, 1001, 2),
+                                                                              LongSummaryStatistics::accept,
+                                                                              LongSummaryStatistics::combine));
+        LongSummaryStatistics original = instances.get(0);
+        instances.add(new LongSummaryStatistics(original.getCount(), original.getMin(), original.getMax(), original.getSum()));
 
         for (LongSummaryStatistics stats : instances) {
             assertEquals(stats.getCount(), 1000);
@@ -127,6 +146,9 @@
             assertEquals(stats.getMax(), 1000L);
             assertEquals(stats.getMin(), 1L);
         }
+
+        expectThrows(IllegalArgumentException.class, () -> new LongSummaryStatistics(-1, 0, 0, 0));
+        expectThrows(IllegalArgumentException.class, () -> new LongSummaryStatistics(1, 3, 2, 0));
     }
 
     public void testDoubleStatistics() {
@@ -136,11 +158,19 @@
         instances.add(countTo(1000).stream().mapToDouble(i -> i).collect(DoubleSummaryStatistics::new,
                                                                          DoubleSummaryStatistics::accept,
                                                                          DoubleSummaryStatistics::combine));
+        instances.add(countTo(1000).stream().mapToInt(i -> i).collect(() -> new DoubleSummaryStatistics(0, -1, 1001, 2),
+                                                                      DoubleSummaryStatistics::accept,
+                                                                      DoubleSummaryStatistics::combine));
         instances.add(countTo(1000).parallelStream().collect(Collectors.summarizingDouble(i -> i)));
         instances.add(countTo(1000).parallelStream().mapToDouble(i -> i).summaryStatistics());
         instances.add(countTo(1000).parallelStream().mapToDouble(i -> i).collect(DoubleSummaryStatistics::new,
                                                                                  DoubleSummaryStatistics::accept,
                                                                                  DoubleSummaryStatistics::combine));
+        instances.add(countTo(1000).parallelStream().mapToInt(i -> i).collect(() -> new DoubleSummaryStatistics(0, -1, 1001, 2),
+                                                                              DoubleSummaryStatistics::accept,
+                                                                              DoubleSummaryStatistics::combine));
+        DoubleSummaryStatistics original = instances.get(0);
+        instances.add(new DoubleSummaryStatistics(original.getCount(), original.getMin(), original.getMax(), original.getSum()));
 
         for (DoubleSummaryStatistics stats : instances) {
             assertEquals(stats.getCount(), 1000);
@@ -149,5 +179,18 @@
             assertEquals(stats.getMax(), 1000.0);
             assertEquals(stats.getMin(), 1.0);
         }
+
+        expectThrows(IllegalArgumentException.class, () -> new DoubleSummaryStatistics(-1, 0, 0, 0));
+        expectThrows(IllegalArgumentException.class, () -> new DoubleSummaryStatistics(1, 3, 2, 0));
+        double[] values = {1.0, Double.NaN};
+        for (var min : values) {
+            for (var max : values) {
+                for (var sum : values) {
+                    if (Double.isNaN(min) && Double.isNaN(max) && Double.isNaN(sum)) continue;
+                    if (!Double.isNaN(min) && !Double.isNaN(max) && !Double.isNaN(sum)) continue;
+                    expectThrows(IllegalArgumentException.class, () -> new DoubleSummaryStatistics(1, min, max, sum));
+                }
+            }
+        }
     }
 }