This is an automated email from the ASF dual-hosted git repository. paulk pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/groovy.git
commit 37af188c3f944f730b6140f44b1e72131a13137a Author: Eric Milles <[email protected]> AuthorDate: Wed Apr 28 16:04:39 2021 -0500 Move stream-related extension methods to StreamGroovyMethods --- build.gradle | 8 +- .../groovy/runtime/DefaultGroovyMethods.java | 20 +- .../groovy/runtime/StreamGroovyMethods.java | 486 ++++++++++++++ .../typehandling/DefaultTypeTransformation.java | 4 +- .../vmplugin/v8/PluginDefaultGroovyMethods.java | 746 +++++---------------- subprojects/groovy-binary/build.gradle | 1 + 6 files changed, 675 insertions(+), 590 deletions(-) diff --git a/build.gradle b/build.gradle index 980e5d9..b3ae7fb 100644 --- a/build.gradle +++ b/build.gradle @@ -231,10 +231,10 @@ tasks.named('test') { if (!JavaVersion.current().java10Compatible) { logger.lifecycle ''' - **************************************** WARNING ******************************************** - ****** You are running the build with an older JDK. NEVER try to release with 1.8. ****** - ****** You must use a JDK 10+ in order to compile all features of the language. ****** - ********************************************************************************************* +**************************************** WARNING ******************************************** +****** You are running the build with an older JDK. NEVER try to release with 1.8. ****** +****** You must use a JDK 10+ in order to compile all features of the language. ****** +********************************************************************************************* ''' } diff --git a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java index c756d84..d58862c 100644 --- a/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/runtime/DefaultGroovyMethods.java @@ -219,15 +219,17 @@ public class DefaultGroovyMethods extends DefaultGroovyMethodsSupport { ProcessGroovyMethods.class, ResourceGroovyMethods.class, SocketGroovyMethods.class, - StringGroovyMethods.class//, - // Below are registered as module extension classes -// DateUtilExtensions.class, -// DateTimeStaticExtensions.class, -// DateTimeExtensions.class, -// SqlExtensions.class, -// SwingGroovyMethods.class, -// XmlExtensions.class, -// NioExtensions.class + StreamGroovyMethods.class, + StringGroovyMethods.class, + /* registered extensions: + DateUtilExtensions.class, + DateTimeExtensions.class, + DateTimeStaticExtensions.class, + NioExtensions.class, + SqlExtensions.class, + SwingGroovyMethods.class, + XmlExtensions.class, + */ }; private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0]; private static final NumberAwareComparator<Comparable> COMPARABLE_NUMBER_AWARE_COMPARATOR = new NumberAwareComparator<>(); diff --git a/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java b/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java new file mode 100644 index 0000000..fa82132 --- /dev/null +++ b/src/main/java/org/codehaus/groovy/runtime/StreamGroovyMethods.java @@ -0,0 +1,486 @@ +package org.codehaus.groovy.runtime; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Collection; +import java.util.Enumeration; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.Set; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.function.Consumer; +import java.util.stream.BaseStream; +import java.util.stream.Collectors; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +public class StreamGroovyMethods { + + private StreamGroovyMethods() { + } + + /** + * TODO + * + * <pre class="groovyTestCase"> + * import java.util.stream.Stream + * assert (Stream.of(1) + [2]).toList() == [1,2] + * assert (Stream.of(1) + []).toList() == [1] + * </pre> + * + * @since 4.0.0 + */ + public static <T> Stream<T> plus(final Stream<? extends T> lhs, final Collection<? extends T> rhs) { + return Stream.concat(lhs, rhs.stream()); + } + + /** + * TODO + * + * <pre class="groovyTestCase"> + * import java.util.stream.Stream + * assert (Stream.of(1) + [2]).toList() == [1,2] + * assert (Stream.of(1) + []).toList() == [1] + * </pre> + * + * @since 4.0.0 + */ + public static <T> Stream<T> plus(final Stream<? extends T> lhs, final Iterable<? extends T> rhs) { + return Stream.concat(lhs, stream(rhs)); + } + + /** + * TODO + * + * <pre class="groovyTestCase"> + * import java.util.stream.Stream + * assert (Stream.of(1) + Stream.<Integer>empty()).toList() == [1] + * assert (Stream.of(1) + Stream.of(2)).toList() == [1,2] + * assert (Stream.of(1) + [2].stream()).toList() == [1,2] + * </pre> + * + * @since 4.0.0 + */ + public static <T> Stream<T> plus(final Stream<? extends T> lhs, final Stream<? extends T> rhs) { + return Stream.concat(lhs, rhs); + } + + //-------------------------------------------------------------------------- + + /** + * Returns a sequential {@link Stream} containing a single element. + * + * <pre class="groovyTestCase"> + * def item = 'string' + * assert item.stream().toList() == ['string'] + * assert item.stream().findFirst().isPresent() + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final T self) { + return Stream.of(self); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param <T> The type of the array elements + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static <T> Stream<T> stream(final T[] self) { + return Arrays.stream(self); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Integer> stream(final int[] self) { + return Arrays.stream(self).boxed(); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Long> stream(final long[] self) { + return Arrays.stream(self).boxed(); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Double> stream(final double[] self) { + return Arrays.stream(self).boxed(); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Character> stream(final char[] self) { + return IntStream.range(0, self.length).mapToObj(i -> self[i]); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Byte> stream(final byte[] self) { + return IntStream.range(0, self.length).mapToObj(i -> self[i]); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Short> stream(final short[] self) { + return IntStream.range(0, self.length).mapToObj(i -> self[i]); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Boolean> stream(final boolean[] self) { + return IntStream.range(0, self.length).mapToObj(i -> self[i]); + } + + /** + * Returns a sequential {@link Stream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 2.5.0 + */ + public static Stream<Float> stream(final float[] self) { + return IntStream.range(0, self.length).mapToObj(i -> self[i]); + } + + /** + * Returns a sequential {@link Stream} with the specified element(s) as its + * source. + * <pre class="groovyTestCase"> + * def tokens = new StringTokenizer('one two') + * assert tokens.stream().toList() == ['one', 'two'] + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final Enumeration<T> self) { + return stream(new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) { + @Override + public void forEachRemaining(final Consumer<? super T> action) { + while (self.hasMoreElements()) { + action.accept(self.nextElement()); + } + } + @Override + public boolean tryAdvance(final Consumer<? super T> action) { + if (self.hasMoreElements()) { + action.accept(self.nextElement()); + return true; + } + return false; + } + }); + } + + /** + * Returns a sequential {@link Stream} with the specified element(s) as its + * source. + * + * <pre class="groovyTestCase"> + * class Items implements Iterable<String> { + * Iterator<String> iterator() { + * ['one', 'two'].iterator() + * } + * } + * def items = new Items() + * assert items.stream().toList() == ['one', 'two'] + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final Iterable<T> self) { + return StreamSupport.stream(self.spliterator(), false); + } + + /** + * Returns a sequential {@link Stream} with the specified element(s) as its + * source. + * + * <pre class="groovyTestCase"> + * [].iterator().stream().toList().isEmpty() + * ['one', 'two'].iterator().stream().toList() == ['one', 'two'] + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final Iterator<T> self) { + return stream(Spliterators.spliteratorUnknownSize(self, Spliterator.ORDERED)); + } + + /** + * Returns a sequential {@link Stream} with the specified element(s) as its + * source. + * + * <pre class="groovyTestCase"> + * assert [].spliterator().stream().toList().isEmpty() + * assert ['one', 'two'].spliterator().stream().toList() == ['one', 'two'] + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final Spliterator<T> self) { + return StreamSupport.stream(self, false); + } + + /** + * Returns an empty sequential {@link Stream}. + * + * <pre class="groovyTestCase"> + * def item = null + * assert item.stream().toList() == [] + * assert !item.stream().findFirst().isPresent() + * </pre> + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final NullObject self) { + return Stream.empty(); + } + + /** + * If a value is present in the {@link Optional}, returns a {@link Stream} + * with the value as its source or else an empty stream. + * + * @since 3.0.0 + */ + public static <T> Stream<T> stream(final Optional<T> self) { + return self.map(Stream::of).orElseGet(Stream::empty); + } + + // + + /** + * If a value is present in the {@link OptionalInt}, returns an {@link IntStream} + * with the value as its source or else an empty stream. + * + * @since 3.0.0 + */ + public static IntStream stream(final OptionalInt self) { + if (!self.isPresent()) { + return IntStream.empty(); + } + return IntStream.of(self.getAsInt()); + } + + /** + * If a value is present in the {@link OptionalLong}, returns a {@link LongStream} + * with the value as its source or else an empty stream. + * + * @since 3.0.0 + */ + public static LongStream stream(final OptionalLong self) { + if (!self.isPresent()) { + return LongStream.empty(); + } + return LongStream.of(self.getAsLong()); + } + + /** + * If a value is present in the {@link OptionalDouble}, returns a {@link DoubleStream} + * with the value as its source or else an empty stream. + * + * @since 3.0.0 + */ + public static DoubleStream stream(final OptionalDouble self) { + if (!self.isPresent()) { + return DoubleStream.empty(); + } + return DoubleStream.of(self.getAsDouble()); + } + + /** + * Returns a sequential {@link IntStream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 3.0.8 + */ + public static IntStream intStream(final int[] self) { + return Arrays.stream(self); + } + + /** + * Returns a sequential {@link LongStream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 3.0.8 + */ + public static LongStream longStream(final long[] self) { + return Arrays.stream(self); + } + + /** + * Returns a sequential {@link DoubleStream} with the specified array as its + * source. + * + * @param self The array, assumed to be unmodified during use + * @return a {@code Stream} for the array + * + * @since 3.0.8 + */ + public static DoubleStream doubleStream(final double[] self) { + return Arrays.stream(self); + } + + //-------------------------------------------------------------------------- + + /** + * Returns an array containing the elements of the stream. + * <pre class="groovyTestCase"> + * import static groovy.test.GroovyAssert.shouldFail + * + * assert Arrays.equals([].stream().toArray(Object), new Object[0]) + * assert Arrays.equals([].stream().toArray(String), new String[0]) + * assert Arrays.equals([].stream().toArray(String[]), new String[0][]) + * assert Arrays.equals(['x'].stream().toArray(Object), ['x'].toArray()) + * assert Arrays.equals(['x'].stream().toArray(String), ['x'] as String[]) + * assert Arrays.deepEquals([['x'] as String[]].stream().toArray(String[]), [['x'] as String[]] as String[][]) + * assert Arrays.equals(['x'].stream().toArray(CharSequence), ['x'] as CharSequence[]) + * + * shouldFail(ArrayStoreException) { + * ['x'].stream().toArray(Thread) + * } + * + * shouldFail(IllegalArgumentException) { + * ['x'].stream().toArray((Class) null) + * } + * + * // Stream#toArray(IntFunction) should still be used for closure literal: + * assert Arrays.equals(['x'].stream().toArray { n -> new String[n] }, ['x'] as String[]) + * + * // Stream#toArray(IntFunction) should still be used for method reference: + * assert Arrays.equals(['x'].stream().toArray(String[]::new), ['x'] as String[]) + * </pre> + * + * @param self the stream + * @param type the array element type + * + * @since 3.0.4 + */ + public static <T> T[] toArray(final Stream<? extends T> self, final Class<T> type) { + if (type == null) throw new IllegalArgumentException("type cannot be null"); + return self.toArray(length -> (T[]) Array.newInstance(type, length)); + } + + /** + * Accumulates the elements of stream into a new List. + * + * @param self the stream + * @param <T> the type of element + * @return a new {@code java.util.List} instance + * + * @since 2.5.0 + */ + public static <T> List<T> toList(final Stream<T> self) { + return self.collect(Collectors.toList()); + } + + /** + * Accumulates the elements of stream into a new List. + * + * @param self the {@code java.util.stream.BaseStream} + * @param <T> the type of element + * @return a new {@code java.util.List} instance + * + * @since 2.5.0 + */ + public static <T> List<T> toList(final BaseStream<T, ? extends BaseStream> self) { + return stream(self.iterator()).collect(Collectors.toList()); + } + + /** + * Accumulates the elements of stream into a new Set. + * + * @param self the stream + * @param <T> the type of element + * @return a new {@code java.util.Set} instance + * + * @since 2.5.0 + */ + public static <T> Set<T> toSet(final Stream<T> self) { + return self.collect(Collectors.toSet()); + } + + /** + * Accumulates the elements of stream into a new Set. + * + * @param self the {@code java.util.stream.BaseStream} + * @param <T> the type of element + * @return a new {@code java.util.Set} instance + * + * @since 2.5.0 + */ + public static <T> Set<T> toSet(final BaseStream<T, ? extends BaseStream> self) { + return stream(self.iterator()).collect(Collectors.toSet()); + } +} diff --git a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java index c96e4f9..503d782 100644 --- a/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java +++ b/src/main/java/org/codehaus/groovy/runtime/typehandling/DefaultTypeTransformation.java @@ -30,8 +30,8 @@ import org.codehaus.groovy.runtime.IteratorClosureAdapter; import org.codehaus.groovy.runtime.MethodClosure; import org.codehaus.groovy.runtime.NullObject; import org.codehaus.groovy.runtime.ResourceGroovyMethods; +import org.codehaus.groovy.runtime.StreamGroovyMethods; import org.codehaus.groovy.runtime.StringGroovyMethods; -import org.codehaus.groovy.vmplugin.v8.PluginDefaultGroovyMethods; import java.io.File; import java.io.IOException; @@ -476,7 +476,7 @@ public class DefaultTypeTransformation { } else if (value.getClass().isArray()) { return arrayAsCollection(value); } else if (value instanceof BaseStream) { - return PluginDefaultGroovyMethods.toList((BaseStream) value); + return StreamGroovyMethods.toList((BaseStream) value); } else if (value instanceof MethodClosure) { MethodClosure method = (MethodClosure) value; IteratorClosureAdapter adapter = new IteratorClosureAdapter(method.getDelegate()); diff --git a/src/main/java/org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.java b/src/main/java/org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.java index 106d209..874b4a6 100644 --- a/src/main/java/org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.java +++ b/src/main/java/org/codehaus/groovy/vmplugin/v8/PluginDefaultGroovyMethods.java @@ -26,29 +26,19 @@ import groovy.transform.stc.ClosureParams; import groovy.transform.stc.FirstParam; import org.codehaus.groovy.runtime.DefaultGroovyMethodsSupport; import org.codehaus.groovy.runtime.InvokerHelper; -import org.codehaus.groovy.runtime.NullObject; import org.codehaus.groovy.runtime.RangeInfo; -import java.lang.management.ManagementFactory; -import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.Enumeration; -import java.util.Iterator; -import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.Set; -import java.util.Spliterator; -import java.util.Spliterators; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.function.Consumer; import java.util.function.DoubleFunction; import java.util.function.DoublePredicate; import java.util.function.IntFunction; @@ -59,13 +49,6 @@ import java.util.function.Supplier; import java.util.function.ToDoubleFunction; import java.util.function.ToIntFunction; import java.util.function.ToLongFunction; -import java.util.stream.BaseStream; -import java.util.stream.Collectors; -import java.util.stream.DoubleStream; -import java.util.stream.IntStream; -import java.util.stream.LongStream; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; /** * Defines new Groovy methods which appear on standard Java 8 classes within the @@ -75,10 +58,116 @@ import java.util.stream.StreamSupport; */ public class PluginDefaultGroovyMethods extends DefaultGroovyMethodsSupport { - // No instances, static methods only private PluginDefaultGroovyMethods() { } + //-------------------------------------------------------------------------- + // Enum + + /** + * Overloads the {@code ++} operator for enums. It will invoke Groovy's + * default next behaviour for enums that do not have their own next method. + * + * @param self an Enum + * @return the next defined enum from the enum class + * + * @since 1.5.2 + */ + public static Object next(final Enum self) { + for (Method method : self.getClass().getMethods()) { + if (method.getName().equals("next") && method.getParameterCount() == 0) { + return InvokerHelper.invokeMethod(self, "next", InvokerHelper.EMPTY_ARGS); + } + } + Object[] values = (Object[]) InvokerHelper.invokeStaticMethod(self.getClass(), "values", InvokerHelper.EMPTY_ARGS); + int index = Arrays.asList(values).indexOf(self); + return values[index < values.length - 1 ? index + 1 : 0]; + } + + /** + * Overloads the {@code --} operator for enums. It will invoke Groovy's + * default previous behaviour for enums that do not have their own previous method. + * + * @param self an Enum + * @return the previous defined enum from the enum class + * + * @since 1.5.2 + */ + public static Object previous(final Enum self) { + for (Method method : self.getClass().getMethods()) { + if (method.getName().equals("previous") && method.getParameterCount() == 0) { + return InvokerHelper.invokeMethod(self, "previous", InvokerHelper.EMPTY_ARGS); + } + } + Object[] values = (Object[]) InvokerHelper.invokeStaticMethod(self.getClass(), "values", InvokerHelper.EMPTY_ARGS); + int index = Arrays.asList(values).indexOf(self); + return values[index > 0 ? index - 1 : values.length - 1]; + } + + //-------------------------------------------------------------------------- + // Future + + /** + * Returns a Future asynchronously returning a transformed result. + * <pre class="_temp_disabled_groovyTestCase"> + * import java.util.concurrent.* + * def executor = Executors.newSingleThreadExecutor() + * Future<String> foobar = executor.submit{ "foobar" } + * Future<Integer> foobarSize = foobar.collect{ it.size() } + * assert foobarSize.get() == 6 + * executor.shutdown() + * </pre> + * + * @param self a Future + * @param transform the closure used to transform the Future value + * @return a Future allowing the transformed value to be obtained asynchronously + * + * @since 3.0.0 + */ + public static <S,T> Future<T> collect(final Future<S> self, @ClosureParams(FirstParam.FirstGenericType.class) final Closure<T> transform) { + Objects.requireNonNull(self); + Objects.requireNonNull(transform); + return new TransformedFuture<T>(self, transform); + } + + private static class TransformedFuture<E> implements Future<E> { + private final Future delegate; + private final Closure<E> transform; + + private TransformedFuture(final Future delegate, final Closure<E> transform) { + this.delegate = delegate; + this.transform = transform; + } + + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return delegate.cancel(mayInterruptIfRunning); + } + + @Override + public boolean isCancelled() { + return delegate.isCancelled(); + } + + @Override + public boolean isDone() { + return delegate.isDone(); + } + + @Override + public E get() throws InterruptedException, ExecutionException { + return transform.call(delegate.get()); + } + + @Override + public E get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return transform.call(delegate.get(timeout, unit)); + } + } + + //-------------------------------------------------------------------------- + // Optional + /** * Coerce an {@code Optional} instance to a {@code boolean} value. * <pre class="groovyTestCase"> @@ -135,6 +224,26 @@ public class PluginDefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** + * If the optional contains a value, returns an optional containing the transformed value obtained using the <code>transform</code> closure + * or otherwise an empty optional. + * <pre class="groovyTestCase"> + * assert Optional.of("foobar").collect{ it.size() }.get() == 6 + * assert !Optional.empty().collect{ it.size() }.isPresent() + * </pre> + * + * @param self an Optional + * @param transform the closure used to transform the optional value if present + * @return an Optional containing the transformed value or empty if the optional is empty or the transform returns null + * + * @since 3.0.0 + */ + public static <S,T> Optional<T> collect(final Optional<S> self, @ClosureParams(FirstParam.FirstGenericType.class) final Closure<T> transform) { + Objects.requireNonNull(self); + Objects.requireNonNull(transform); + return self.map(transform::call); + } + + /** * Tests given value against specified type and changes generics of result. * This is equivalent to: <code>self.filter(it -> it instanceof Type).map(it -> (Type) it)</code> * <pre class="groovyTestCase"> @@ -310,102 +419,42 @@ public class PluginDefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * If the optional contains a value, returns an optional containing the transformed value obtained using the <code>transform</code> closure - * or otherwise an empty optional. + * Provides similar functionality to JDK9 {@code or} on JDK8. * <pre class="groovyTestCase"> - * assert Optional.of("foobar").collect{ it.size() }.get() == 6 - * assert !Optional.empty().collect{ it.size() }.isPresent() + * def x = Optional.empty() + * def y = Optional.of('y') + * assert y.orOptional(() -> Optional.of('z')).get() == 'y' + * assert x.orOptional(() -> Optional.of('z')).get() == 'z' * </pre> * - * @param self an Optional - * @param transform the closure used to transform the optional value if present - * @return an Optional containing the transformed value or empty if the optional is empty or the transform returns null - * - * @since 3.0.0 - */ - public static <S,T> Optional<T> collect(final Optional<S> self, @ClosureParams(FirstParam.FirstGenericType.class) final Closure<T> transform) { - Objects.requireNonNull(self); - Objects.requireNonNull(transform); - return self.map(transform::call); - } - - /** - * Returns a Future asynchronously returning a transformed result. - * <pre class="_temp_disabled_groovyTestCase"> - * import java.util.concurrent.* - * def executor = Executors.newSingleThreadExecutor() - * Future<String> foobar = executor.submit{ "foobar" } - * Future<Integer> foobarSize = foobar.collect{ it.size() } - * assert foobarSize.get() == 6 - * executor.shutdown() - * </pre> - * - * @param self a Future - * @param transform the closure used to transform the Future value - * @return a Future allowing the transformed value to be obtained asynchronously - * - * @since 3.0.0 - */ - public static <S,T> Future<T> collect(final Future<S> self, @ClosureParams(FirstParam.FirstGenericType.class) final Closure<T> transform) { - Objects.requireNonNull(self); - Objects.requireNonNull(transform); - return new TransformedFuture<T>(self, transform); - } - - /** - * Overloads the {@code ++} operator for enums. It will invoke Groovy's - * default next behaviour for enums that do not have their own next method. - * - * @param self an Enum - * @return the next defined enum from the enum class - * - * @since 1.5.2 + * @since 3.0.6 */ - public static Object next(final Enum self) { - for (Method method : self.getClass().getMethods()) { - if (method.getName().equals("next") && method.getParameterCount() == 0) { - return InvokerHelper.invokeMethod(self, "next", InvokerHelper.EMPTY_ARGS); - } + public static <T> Optional<T> orOptional(final Optional<T> self, final Supplier<Optional<? extends T>> supplier) { + if (self.isPresent()) { + return self; } - Object[] values = (Object[]) InvokerHelper.invokeStaticMethod(self.getClass(), "values", InvokerHelper.EMPTY_ARGS); - int index = Arrays.asList(values).indexOf(self); - return values[index < values.length - 1 ? index + 1 : 0]; + return (Optional<T>) supplier.get(); } + //-------------------------------------------------------------------------- + // Runtime + /** - * Overloads the {@code --} operator for enums. It will invoke Groovy's - * default previous behaviour for enums that do not have their own previous method. - * - * @param self an Enum - * @return the previous defined enum from the enum class + * Gets the pid of the current Java process. * - * @since 1.5.2 + * @since 4.0.0 */ - public static Object previous(final Enum self) { - for (Method method : self.getClass().getMethods()) { - if (method.getName().equals("previous") && method.getParameterCount() == 0) { - return InvokerHelper.invokeMethod(self, "previous", InvokerHelper.EMPTY_ARGS); - } + public static String getPid(final Runtime self) { + String name = java.lang.management.ManagementFactory.getRuntimeMXBean().getName(); + int index = name.indexOf('@'); + if (index == -1) { // should never happen + return name; } - Object[] values = (Object[]) InvokerHelper.invokeStaticMethod(self.getClass(), "values", InvokerHelper.EMPTY_ARGS); - int index = Arrays.asList(values).indexOf(self); - return values[index > 0 ? index - 1 : values.length - 1]; + return name.substring(0, index); } - /** - * Provides the standard Groovy <code>size()</code> method for <code>StringBuilder</code>. - * - * @param self a StringBuilder - * @return the length of the StringBuilder - * - * @since 1.5.2 - * - * @see org.codehaus.groovy.runtime.StringGroovyMethods#size(CharSequence) - */ - @Deprecated - public static int size(final StringBuilder self) { - return self.length(); - } + //-------------------------------------------------------------------------- + // StringBuilder /** * Overloads the left shift operator to provide an easy way to append multiple @@ -431,18 +480,16 @@ public class PluginDefaultGroovyMethods extends DefaultGroovyMethodsSupport { } /** - * Supports the range subscript operator for StringBuilder. - * Index values are treated as characters within the builder. + * Appends a String to this StringBuilder. * * @param self a StringBuilder - * @param range a Range - * @param value the object that's toString() will be inserted + * @param value a String + * @return a String * * @since 1.5.2 */ - public static void putAt(final StringBuilder self, final IntRange range, final Object value) { - RangeInfo info = DefaultGroovyMethodsSupport.subListBorders(self.length(), range); - self.replace(info.from, info.to, value.toString()); + public static String plus(final StringBuilder self, final String value) { + return self + value; } /** @@ -455,488 +502,37 @@ public class PluginDefaultGroovyMethods extends DefaultGroovyMethodsSupport { * @since 1.5.2 */ public static void putAt(final StringBuilder self, final EmptyRange range, final Object value) { - RangeInfo info = DefaultGroovyMethodsSupport.subListBorders(self.length(), range); + RangeInfo info = subListBorders(self.length(), range); self.replace(info.from, info.to, value.toString()); } /** - * Appends a String to this StringBuilder. + * Supports the range subscript operator for StringBuilder. + * Index values are treated as characters within the builder. * * @param self a StringBuilder - * @param value a String - * @return a String + * @param range a Range + * @param value the object that's toString() will be inserted * * @since 1.5.2 */ - public static String plus(final StringBuilder self, final String value) { - return self + value; - } - - private static class TransformedFuture<E> implements Future<E> { - private final Future delegate; - private final Closure<E> transform; - - private TransformedFuture(final Future delegate, final Closure<E> transform) { - this.delegate = delegate; - this.transform = transform; - } - - @Override - public boolean cancel(final boolean mayInterruptIfRunning) { - return delegate.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return delegate.isCancelled(); - } - - @Override - public boolean isDone() { - return delegate.isDone(); - } - - @Override - public E get() throws InterruptedException, ExecutionException { - return transform.call(delegate.get()); - } - - @Override - public E get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { - return transform.call(delegate.get(timeout, unit)); - } - } - - /** - * Returns an array containing the elements of the stream. - * <pre class="groovyTestCase"> - * import static groovy.test.GroovyAssert.shouldFail - * - * assert Arrays.equals([].stream().toArray(Object), new Object[0]) - * assert Arrays.equals([].stream().toArray(String), new String[0]) - * assert Arrays.equals([].stream().toArray(String[]), new String[0][]) - * assert Arrays.equals(['x'].stream().toArray(Object), ['x'].toArray()) - * assert Arrays.equals(['x'].stream().toArray(String), ['x'] as String[]) - * assert Arrays.deepEquals([['x'] as String[]].stream().toArray(String[]), [['x'] as String[]] as String[][]) - * assert Arrays.equals(['x'].stream().toArray(CharSequence), ['x'] as CharSequence[]) - * - * shouldFail(ArrayStoreException) { - * ['x'].stream().toArray(Thread) - * } - * - * shouldFail(IllegalArgumentException) { - * ['x'].stream().toArray((Class) null) - * } - * - * // Stream#toArray(IntFunction) should still be used for closure literal: - * assert Arrays.equals(['x'].stream().toArray { n -> new String[n] }, ['x'] as String[]) - * - * // Stream#toArray(IntFunction) should still be used for method reference: - * assert Arrays.equals(['x'].stream().toArray(String[]::new), ['x'] as String[]) - * </pre> - * - * @param self the stream - * @param type the array element type - * - * @since 3.0.4 - */ - public static <T> T[] toArray(final Stream<? extends T> self, final Class<T> type) { - if (type == null) throw new IllegalArgumentException("type cannot be null"); - return self.toArray(length -> (T[]) Array.newInstance(type, length)); - } - - /** - * Accumulates the elements of stream into a new List. - * - * @param self the stream - * @param <T> the type of element - * @return a new {@code java.util.List} instance - * - * @since 2.5.0 - */ - public static <T> List<T> toList(final Stream<T> self) { - return self.collect(Collectors.toList()); - } - - /** - * Accumulates the elements of stream into a new Set. - * - * @param self the stream - * @param <T> the type of element - * @return a new {@code java.util.Set} instance - * - * @since 2.5.0 - */ - public static <T> Set<T> toSet(final Stream<T> self) { - return self.collect(Collectors.toSet()); - } - - /** - * Accumulates the elements of stream into a new List. - * - * @param self the {@code java.util.stream.BaseStream} - * @param <T> the type of element - * @return a new {@code java.util.List} instance - * - * @since 2.5.0 - */ - public static <T> List<T> toList(final BaseStream<T, ? extends BaseStream> self) { - return stream(self.iterator()).collect(Collectors.toList()); - } - - /** - * Accumulates the elements of stream into a new Set. - * - * @param self the {@code java.util.stream.BaseStream} - * @param <T> the type of element - * @return a new {@code java.util.Set} instance - * - * @since 2.5.0 - */ - public static <T> Set<T> toSet(final BaseStream<T, ? extends BaseStream> self) { - return stream(self.iterator()).collect(Collectors.toSet()); - } - - /** - * Returns an empty sequential {@link Stream}. - * - * <pre class="groovyTestCase"> - * def item = null - * assert item.stream().toList() == [] - * assert !item.stream().findFirst().isPresent() - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final NullObject self) { - return Stream.empty(); - } - - /** - * Returns a sequential {@link Stream} containing a single element. - * - * <pre class="groovyTestCase"> - * def item = 'string' - * assert item.stream().toList() == ['string'] - * assert item.stream().findFirst().isPresent() - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final T self) { - return Stream.of(self); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param <T> The type of the array elements - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static <T> Stream<T> stream(final T[] self) { - return Arrays.stream(self); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Integer> stream(final int[] self) { - return Arrays.stream(self).boxed(); - } - - /** - * Returns a sequential {@link IntStream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 3.0.8 - */ - public static IntStream intStream(final int[] self) { - return Arrays.stream(self); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Long> stream(final long[] self) { - return Arrays.stream(self).boxed(); - } - - /** - * Returns a sequential {@link LongStream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 3.0.8 - */ - public static LongStream longStream(final long[] self) { - return Arrays.stream(self); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Double> stream(final double[] self) { - return Arrays.stream(self).boxed(); - } - - /** - * Returns a sequential {@link DoubleStream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 3.0.8 - */ - public static DoubleStream doubleStream(final double[] self) { - return Arrays.stream(self); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Character> stream(final char[] self) { - return IntStream.range(0, self.length).mapToObj(i -> self[i]); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Byte> stream(final byte[] self) { - return IntStream.range(0, self.length).mapToObj(i -> self[i]); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Short> stream(final short[] self) { - return IntStream.range(0, self.length).mapToObj(i -> self[i]); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Boolean> stream(final boolean[] self) { - return IntStream.range(0, self.length).mapToObj(i -> self[i]); - } - - /** - * Returns a sequential {@link Stream} with the specified array as its - * source. - * - * @param self The array, assumed to be unmodified during use - * @return a {@code Stream} for the array - * - * @since 2.5.0 - */ - public static Stream<Float> stream(final float[] self) { - return IntStream.range(0, self.length).mapToObj(i -> self[i]); - } - - /** - * Returns a sequential {@link Stream} with the specified element(s) as its - * source. - * <pre class="groovyTestCase"> - * def tokens = new StringTokenizer('one two') - * assert tokens.stream().toList() == ['one', 'two'] - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final Enumeration<T> self) { - return stream(new Spliterators.AbstractSpliterator<T>(Long.MAX_VALUE, Spliterator.ORDERED) { - @Override - public void forEachRemaining(final Consumer<? super T> action) { - while (self.hasMoreElements()) { - action.accept(self.nextElement()); - } - } - @Override - public boolean tryAdvance(final Consumer<? super T> action) { - if (self.hasMoreElements()) { - action.accept(self.nextElement()); - return true; - } - return false; - } - }); - } - - /** - * Returns a sequential {@link Stream} with the specified element(s) as its - * source. - * - * <pre class="groovyTestCase"> - * class Items implements Iterable<String> { - * Iterator<String> iterator() { - * ['one', 'two'].iterator() - * } - * } - * def items = new Items() - * assert items.stream().toList() == ['one', 'two'] - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final Iterable<T> self) { - return StreamSupport.stream(self.spliterator(), false); - } - - /** - * Returns a sequential {@link Stream} with the specified element(s) as its - * source. - * - * <pre class="groovyTestCase"> - * [].iterator().stream().toList().isEmpty() - * ['one', 'two'].iterator().stream().toList() == ['one', 'two'] - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final Iterator<T> self) { - return stream(Spliterators.spliteratorUnknownSize(self, Spliterator.ORDERED)); - } - - /** - * Returns a sequential {@link Stream} with the specified element(s) as its - * source. - * - * <pre class="groovyTestCase"> - * assert [].spliterator().stream().toList().isEmpty() - * assert ['one', 'two'].spliterator().stream().toList() == ['one', 'two'] - * </pre> - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final Spliterator<T> self) { - return StreamSupport.stream(self, false); - } - - /** - * If a value is present in the {@link Optional}, returns a {@link Stream} - * with the value as its source or else an empty stream. - * - * @since 3.0.0 - */ - public static <T> Stream<T> stream(final Optional<T> self) { - return self.map(Stream::of).orElseGet(Stream::empty); - } - - /** - * If a value is present in the {@link OptionalInt}, returns an {@link IntStream} - * with the value as its source or else an empty stream. - * - * @since 3.0.0 - */ - public static IntStream stream(final OptionalInt self) { - if (!self.isPresent()) { - return IntStream.empty(); - } - return IntStream.of(self.getAsInt()); - } - - /** - * If a value is present in the {@link OptionalLong}, returns a {@link LongStream} - * with the value as its source or else an empty stream. - * - * @since 3.0.0 - */ - public static LongStream stream(final OptionalLong self) { - if (!self.isPresent()) { - return LongStream.empty(); - } - return LongStream.of(self.getAsLong()); + public static void putAt(final StringBuilder self, final IntRange range, final Object value) { + RangeInfo info = subListBorders(self.length(), range); + self.replace(info.from, info.to, value.toString()); } /** - * If a value is present in the {@link OptionalDouble}, returns a {@link DoubleStream} - * with the value as its source or else an empty stream. + * Provides the standard Groovy {@code size()} method for StringBuilder. * - * @since 3.0.0 - */ - public static DoubleStream stream(final OptionalDouble self) { - if (!self.isPresent()) { - return DoubleStream.empty(); - } - return DoubleStream.of(self.getAsDouble()); - } - - /** - * Provide similar functionality to JDK9 {@code or} on JDK8. + * @param self a StringBuilder + * @return the length of the StringBuilder * - * @since 3.0.6 - */ - public static <T> Optional<T> orOptional(Optional<T> self, Supplier<? extends Optional<? extends T>> supplier) { - if (self.isPresent()) { - return self; - } - return (Optional<T>) supplier.get(); - } - - /** - * Get the pid of the current Java process + * @since 1.5.2 * - * @param self - * @return the pid - * @since 4.0.0 + * @see org.codehaus.groovy.runtime.StringGroovyMethods#size(CharSequence) */ - public static String getPid(Runtime self) { - String name = ManagementFactory.getRuntimeMXBean().getName(); - int index = name.indexOf('@'); - if (-1 == index) { // should never happen - return name; - } - return name.substring(0, index); + @Deprecated + public static int size(final StringBuilder self) { + return self.length(); } } diff --git a/subprojects/groovy-binary/build.gradle b/subprojects/groovy-binary/build.gradle index 3e4fcb8..61b82c5 100644 --- a/subprojects/groovy-binary/build.gradle +++ b/subprojects/groovy-binary/build.gradle @@ -45,6 +45,7 @@ distribution { 'org.codehaus.groovy.runtime.ProcessGroovyMethods', 'org.codehaus.groovy.runtime.ResourceGroovyMethods', 'org.codehaus.groovy.runtime.SocketGroovyMethods', + 'org.codehaus.groovy.runtime.StreamGroovyMethods', 'org.codehaus.groovy.runtime.StringGroovyMethods', 'org.codehaus.groovy.vmplugin.v8.PluginDefaultGroovyMethods', 'org.codehaus.groovy.vmplugin.v9.PluginDefaultGroovyMethods',
