diff --git a/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java b/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java index 16d8dbb..0cc144a 100644 --- a/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java +++ b/src/main/java/com/apptasticsoftware/rssreader/AbstractRssReader.java @@ -23,6 +23,8 @@ */ package com.apptasticsoftware.rssreader; +import com.apptasticsoftware.rssreader.internal.StreamUtil; +import com.apptasticsoftware.rssreader.internal.stream.AutoCloseStream; import com.apptasticsoftware.rssreader.util.Mapper; import com.apptasticsoftware.rssreader.util.DaemonThreadFactory; import com.apptasticsoftware.rssreader.util.XMLInputFactorySecurity; @@ -43,11 +45,11 @@ import java.time.Duration; import java.util.*; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Stream; -import java.util.stream.StreamSupport; import java.util.zip.GZIPInputStream; import static com.apptasticsoftware.rssreader.util.Mapper.mapLong; @@ -363,7 +365,7 @@ public AbstractRssReader addItemExtension(String tag, String attribute, Bi Objects.requireNonNull(consumer, "Item consumer must not be null"); itemAttributes.computeIfAbsent(tag, k -> new HashMap<>()) - .put(attribute, consumer); + .put(attribute, consumer); return this; } @@ -394,7 +396,7 @@ public AbstractRssReader addChannelExtension(String tag, String attribute, Objects.requireNonNull(consumer, "Channel consumer must not be null"); channelAttributes.computeIfAbsent(tag, k -> new HashMap<>()) - .put(attribute, consumer); + .put(attribute, consumer); return this; } @@ -438,29 +440,29 @@ public Stream read(Collection urls) { initialize(); isInitialized = true; } - return urls.stream() - .parallel() - .map(url -> { - try { - return Map.entry(url, readAsync(url)); - } catch (Exception e) { - if (LOGGER.isLoggable(Level.WARNING)) { - LOGGER.log(Level.WARNING, () -> String.format("Failed read URL %s. Message: %s", url, e.getMessage())); - } - return null; + return AutoCloseStream.of(urls.stream() + .parallel() + .map(url -> { + try { + return Map.entry(url, readAsync(url)); + } catch (Exception e) { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log(Level.WARNING, () -> String.format("Failed read URL %s. Message: %s", url, e.getMessage())); } - }) - .filter(Objects::nonNull) - .flatMap(f -> { - try { - return f.getValue().join(); - } catch (Exception e) { - if (LOGGER.isLoggable(Level.WARNING)) { - LOGGER.log(Level.WARNING, () -> String.format("Failed to read URL %s. Message: %s", f.getKey(), e.getMessage())); - } - return Stream.empty(); - } - }); + return null; + } + }) + .filter(Objects::nonNull) + .flatMap(f -> { + try { + return f.getValue().join(); + } catch (Exception e) { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log(Level.WARNING, () -> String.format("Failed to read URL %s. Message: %s", f.getKey(), e.getMessage())); + } + return Stream.empty(); + } + })); } /** @@ -479,8 +481,7 @@ public Stream read(InputStream inputStream) { inputStream = new BufferedInputStream(inputStream); removeBadData(inputStream); var itemIterator = new RssItemIterator(inputStream); - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(itemIterator, Spliterator.ORDERED), false) - .onClose(itemIterator::close); + return AutoCloseStream.of(StreamUtil.asStream(itemIterator).onClose(itemIterator::close)); } /** @@ -505,7 +506,7 @@ public CompletableFuture> readAsync(String url) { */ protected CompletableFuture> sendAsyncRequest(String url) { var builder = HttpRequest.newBuilder(URI.create(url)) - .header("Accept-Encoding", "gzip"); + .header("Accept-Encoding", "gzip"); if (requestTimeout.toMillis() > 0) { builder.timeout(requestTimeout); } @@ -533,8 +534,7 @@ private Function, Stream> processResponse() { inputStream = new BufferedInputStream(inputStream); removeBadData(inputStream); var itemIterator = new RssItemIterator(inputStream); - return StreamSupport.stream(Spliterators.spliteratorUnknownSize(itemIterator, Spliterator.ORDERED), false) - .onClose(itemIterator::close); + return AutoCloseStream.of(StreamUtil.asStream(itemIterator).onClose(itemIterator::close)); } catch (IOException e) { throw new CompletionException(e); } @@ -568,6 +568,7 @@ class RssItemIterator implements Iterator { private boolean isChannelPart = false; private boolean isItemPart = false; private ScheduledFuture parseWatchdog; + private final AtomicBoolean isClosed; public RssItemIterator(InputStream is) { this.is = is; @@ -575,6 +576,7 @@ public RssItemIterator(InputStream is) { textBuilder = new StringBuilder(); childNodeTextBuilder = new HashMap<>(); elementStack = new ArrayDeque<>(); + isClosed = new AtomicBoolean(false); try { // disable XML external entity (XXE) processing @@ -595,15 +597,17 @@ public RssItemIterator(InputStream is) { } public void close() { - try { - if (parseWatchdog != null) { - parseWatchdog.cancel(false); - } - reader.close(); - is.close(); - } catch (XMLStreamException | IOException e) { - if (LOGGER.isLoggable(Level.WARNING)) { - LOGGER.log(Level.WARNING, "Failed to close XML stream. ", e); + if (isClosed.compareAndSet(false,true)) { + try { + if (parseWatchdog != null) { + parseWatchdog.cancel(false); + } + reader.close(); + is.close(); + } catch (XMLStreamException | IOException e) { + if (LOGGER.isLoggable(Level.WARNING)) { + LOGGER.log(Level.WARNING, "Failed to close XML stream. ", e); + } } } } @@ -669,16 +673,16 @@ private void collectChildNodes(int type) { // Add namespaces to start tag for (int i = 0; i < reader.getNamespaceCount(); ++i) { startTagBuilder.append(" ") - .append(toNamespacePrefix(reader.getNamespacePrefix(i))) - .append("=") - .append(reader.getNamespaceURI(i)); + .append(toNamespacePrefix(reader.getNamespacePrefix(i))) + .append("=") + .append(reader.getNamespaceURI(i)); } // Add attributes to start tag for (int i = 0; i < reader.getAttributeCount(); ++i) { startTagBuilder.append(" ") - .append(toNsName(reader.getAttributePrefix(i), reader.getAttributeLocalName(i))) - .append("=") - .append(reader.getAttributeValue(i)); + .append(toNsName(reader.getAttributePrefix(i), reader.getAttributeLocalName(i))) + .append("=") + .append(reader.getAttributeValue(i)); } startTagBuilder.append(">"); var startTag = startTagBuilder.toString(); @@ -699,9 +703,9 @@ private void collectChildNodes(int type) { var nsTagName = toNsName(reader.getPrefix(), reader.getLocalName()); var endTag = ""; childNodeTextBuilder.entrySet() - .stream() - .filter(e -> !e.getKey().equals(nsTagName)) - .forEach(e -> e.getValue().append(endTag)); + .stream() + .filter(e -> !e.getKey().equals(nsTagName)) + .forEach(e -> e.getValue().append(endTag)); } } diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/StreamUtil.java b/src/main/java/com/apptasticsoftware/rssreader/internal/StreamUtil.java new file mode 100644 index 0000000..8f87e7a --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/StreamUtil.java @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal; + +import java.util.Iterator; +import java.util.Spliterator; +import java.util.Spliterators; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import static java.util.Objects.requireNonNull; + +/** + * Internal utility class for working with streams. + */ +public class StreamUtil { + + private StreamUtil() { } + + /** + * Creates a Stream from an Iterator. + * + * @param iterator The Iterator to create the Stream from. + * @param The type of the elements in the Stream. + * @return A Stream created from the Iterator. + */ + public static Stream asStream(Iterator iterator) { + requireNonNull(iterator); + return StreamSupport.stream(Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AbstractAutoCloseStream.java b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AbstractAutoCloseStream.java new file mode 100644 index 0000000..0c32753 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AbstractAutoCloseStream.java @@ -0,0 +1,80 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal.stream; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.*; +import java.util.stream.*; + +public class AbstractAutoCloseStream> implements AutoCloseable { + private final S stream; + private final AtomicBoolean isClosed; + + AbstractAutoCloseStream(S stream) { + this.stream = Objects.requireNonNull(stream); + this.isClosed = new AtomicBoolean(); + } + + protected S stream() { + return stream; + } + + @Override + public void close() { + if (isClosed.compareAndSet(false,true)) { + stream().close(); + } + } + + R autoClose(Function function) { + try (S s = stream()) { + return function.apply(s); + } + } + + Stream asAutoCloseStream(Stream stream) { + return asAutoCloseStream(stream, AutoCloseStream::new); + } + + IntStream asAutoCloseStream(IntStream stream) { + return asAutoCloseStream(stream, AutoCloseIntStream::new); + } + + LongStream asAutoCloseStream(LongStream stream) { + return asAutoCloseStream(stream, AutoCloseLongStream::new); + } + + DoubleStream asAutoCloseStream(DoubleStream stream) { + return asAutoCloseStream(stream, AutoCloseDoubleStream::new); + } + + private U asAutoCloseStream(U stream, Function wrapper) { + if (stream instanceof AbstractAutoCloseStream) { + return stream; + } + return wrapper.apply(stream); + } + +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseDoubleStream.java b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseDoubleStream.java new file mode 100644 index 0000000..8fb4dae --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseDoubleStream.java @@ -0,0 +1,227 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal.stream; + +import java.util.DoubleSummaryStatistics; +import java.util.OptionalDouble; +import java.util.PrimitiveIterator; +import java.util.Spliterator; +import java.util.function.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class AutoCloseDoubleStream extends AbstractAutoCloseStream implements DoubleStream { + + AutoCloseDoubleStream(DoubleStream stream) { + super(stream); + } + + @Override + public DoubleStream filter(DoublePredicate predicate) { + return asAutoCloseStream(stream().filter(predicate)); + } + + @Override + public DoubleStream map(DoubleUnaryOperator mapper) { + return asAutoCloseStream(stream().map(mapper)); + } + + @Override + public Stream mapToObj(DoubleFunction mapper) { + return asAutoCloseStream(stream().mapToObj(mapper)); + } + + @Override + public IntStream mapToInt(DoubleToIntFunction mapper) { + return asAutoCloseStream(stream().mapToInt(mapper)); + } + + @Override + public LongStream mapToLong(DoubleToLongFunction mapper) { + return asAutoCloseStream(stream().mapToLong(mapper)); + } + + @Override + public DoubleStream flatMap(DoubleFunction mapper) { + return asAutoCloseStream(stream().flatMap(mapper)); + } + + @Override + public DoubleStream distinct() { + return asAutoCloseStream(stream().distinct()); + } + + @Override + public DoubleStream sorted() { + return asAutoCloseStream(stream().sorted()); + } + + @Override + public DoubleStream peek(DoubleConsumer action) { + return asAutoCloseStream(stream().peek(action)); + } + + @Override + public DoubleStream limit(long maxSize) { + return asAutoCloseStream(stream().limit(maxSize)); + } + + @Override + public DoubleStream skip(long n) { + return asAutoCloseStream(stream().skip(n)); + } + + @Override + public void forEach(DoubleConsumer action) { + autoClose(stream -> { + stream.forEach(action); + return null; + }); + } + + @Override + public void forEachOrdered(DoubleConsumer action) { + autoClose(stream -> { + stream.forEachOrdered(action); + return null; + }); + } + + @Override + public double[] toArray() { + return autoClose(DoubleStream::toArray); + } + + @Override + public double reduce(double identity, DoubleBinaryOperator op) { + return autoClose(stream -> stream.reduce(identity, op)); + } + + @Override + public OptionalDouble reduce(DoubleBinaryOperator op) { + return autoClose(stream -> stream.reduce(op)); + } + + @Override + public R collect(Supplier supplier, ObjDoubleConsumer accumulator, BiConsumer combiner) { + return autoClose(stream -> stream.collect(supplier, accumulator, combiner)); + } + + @Override + public double sum() { + return autoClose(DoubleStream::sum); + } + + @Override + public OptionalDouble min() { + return autoClose(DoubleStream::min); + } + + @Override + public OptionalDouble max() { + return autoClose(DoubleStream::max); + } + + @Override + public long count() { + return autoClose(DoubleStream::count); + } + + @Override + public OptionalDouble average() { + return autoClose(DoubleStream::average); + } + + @Override + public DoubleSummaryStatistics summaryStatistics() { + return autoClose(DoubleStream::summaryStatistics); + } + + @Override + public boolean anyMatch(DoublePredicate predicate) { + return autoClose(stream -> stream.anyMatch(predicate)); + } + + @Override + public boolean allMatch(DoublePredicate predicate) { + return autoClose(stream -> stream.allMatch(predicate)); + } + + @Override + public boolean noneMatch(DoublePredicate predicate) { + return autoClose(stream -> stream.noneMatch(predicate)); + } + + @Override + public OptionalDouble findFirst() { + return autoClose(DoubleStream::findFirst); + } + + @Override + public OptionalDouble findAny() { + return autoClose(DoubleStream::findAny); + } + + @Override + public Stream boxed() { + return asAutoCloseStream(stream().boxed()); + } + + @Override + public DoubleStream sequential() { + return asAutoCloseStream(stream().sequential()); + } + + @Override + public DoubleStream parallel() { + return asAutoCloseStream(stream().parallel()); + } + + @Override + public PrimitiveIterator.OfDouble iterator() { + return stream().iterator(); + } + + @Override + public Spliterator.OfDouble spliterator() { + return stream().spliterator(); + } + + @Override + public boolean isParallel() { + return stream().isParallel(); + } + + @Override + public DoubleStream unordered() { + return asAutoCloseStream(stream().unordered()); + } + + @Override + public DoubleStream onClose(Runnable closeHandler) { + return asAutoCloseStream(stream().onClose(closeHandler)); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseIntStream.java b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseIntStream.java new file mode 100644 index 0000000..46e51f3 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseIntStream.java @@ -0,0 +1,234 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal.stream; + +import java.util.*; +import java.util.function.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class AutoCloseIntStream extends AbstractAutoCloseStream implements IntStream { + + AutoCloseIntStream(IntStream stream) { + super(stream); + } + + @Override + public IntStream filter(IntPredicate predicate) { + return asAutoCloseStream(stream().filter(predicate)); + } + + @Override + public IntStream map(IntUnaryOperator mapper) { + return asAutoCloseStream(stream().map(mapper)); + } + + @Override + public Stream mapToObj(IntFunction mapper) { + return asAutoCloseStream(stream().mapToObj(mapper)); + } + + @Override + public LongStream mapToLong(IntToLongFunction mapper) { + return asAutoCloseStream(stream().mapToLong(mapper)); + } + + @Override + public DoubleStream mapToDouble(IntToDoubleFunction mapper) { + return asAutoCloseStream(stream().mapToDouble(mapper)); + } + + @Override + public IntStream flatMap(IntFunction mapper) { + return asAutoCloseStream(stream().flatMap(mapper)); + } + + @Override + public IntStream distinct() { + return asAutoCloseStream(stream().distinct()); + } + + @Override + public IntStream sorted() { + return asAutoCloseStream(stream().sorted()); + } + + @Override + public IntStream peek(IntConsumer action) { + return asAutoCloseStream(stream().peek(action)); + } + + @Override + public IntStream limit(long maxSize) { + return asAutoCloseStream(stream().limit(maxSize)); + } + + @Override + public IntStream skip(long n) { + return asAutoCloseStream(stream().skip(n)); + } + + @Override + public void forEach(IntConsumer action) { + autoClose(stream -> { + stream.forEach(action); + return null; + }); + } + + @Override + public void forEachOrdered(IntConsumer action) { + autoClose(stream -> { + stream.forEachOrdered(action); + return null; + }); + } + + @Override + public int[] toArray() { + return autoClose(IntStream::toArray); + } + + @Override + public int reduce(int identity, IntBinaryOperator op) { + return autoClose(stream -> stream.reduce(identity, op)); + } + + @Override + public OptionalInt reduce(IntBinaryOperator op) { + return autoClose(stream -> stream.reduce(op)); + } + + @Override + public R collect(Supplier supplier, ObjIntConsumer accumulator, BiConsumer combiner) { + return autoClose(stream -> stream.collect(supplier, accumulator, combiner)); + } + + @Override + public int sum() { + return autoClose(IntStream::sum); + } + + @Override + public OptionalInt min() { + return autoClose(IntStream::min); + } + + @Override + public OptionalInt max() { + return autoClose(IntStream::max); + } + + @Override + public long count() { + return autoClose(IntStream::count); + } + + @Override + public OptionalDouble average() { + return autoClose(IntStream::average); + } + + @Override + public IntSummaryStatistics summaryStatistics() { + return autoClose(IntStream::summaryStatistics); + } + + @Override + public boolean anyMatch(IntPredicate predicate) { + return autoClose(stream -> stream.anyMatch(predicate)); + } + + @Override + public boolean allMatch(IntPredicate predicate) { + return autoClose(stream -> stream.allMatch(predicate)); + } + + @Override + public boolean noneMatch(IntPredicate predicate) { + return autoClose(stream -> stream.noneMatch(predicate)); + } + + @Override + public OptionalInt findFirst() { + return autoClose(IntStream::findFirst); + } + + @Override + public OptionalInt findAny() { + return autoClose(IntStream::findAny); + } + + @Override + public LongStream asLongStream() { + return asAutoCloseStream(stream().asLongStream()); + } + + @Override + public DoubleStream asDoubleStream() { + return asAutoCloseStream(stream().asDoubleStream()); + } + + @Override + public Stream boxed() { + return asAutoCloseStream(stream().boxed()); + } + + @Override + public IntStream sequential() { + return asAutoCloseStream(stream().sequential()); + } + + @Override + public IntStream parallel() { + return asAutoCloseStream(stream().parallel()); + } + + @Override + public PrimitiveIterator.OfInt iterator() { + return stream().iterator(); + } + + @Override + public Spliterator.OfInt spliterator() { + return stream().spliterator(); + } + + @Override + public boolean isParallel() { + return stream().isParallel(); + } + + @Override + public IntStream unordered() { + return asAutoCloseStream(stream().unordered()); + } + + @Override + public IntStream onClose(Runnable closeHandler) { + return asAutoCloseStream(stream().onClose(closeHandler)); + } +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseLongStream.java b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseLongStream.java new file mode 100644 index 0000000..ed4ee16 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseLongStream.java @@ -0,0 +1,230 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal.stream; + +import java.util.*; +import java.util.function.*; +import java.util.stream.DoubleStream; +import java.util.stream.IntStream; +import java.util.stream.LongStream; +import java.util.stream.Stream; + +public class AutoCloseLongStream extends AbstractAutoCloseStream implements LongStream { + + AutoCloseLongStream(LongStream stream) { + super(stream); + } + + @Override + public LongStream filter(LongPredicate predicate) { + return asAutoCloseStream(stream().filter(predicate)); + } + + @Override + public LongStream map(LongUnaryOperator mapper) { + return asAutoCloseStream(stream().map(mapper)); + } + + @Override + public Stream mapToObj(LongFunction mapper) { + return asAutoCloseStream(stream().mapToObj(mapper)); + } + + @Override + public IntStream mapToInt(LongToIntFunction mapper) { + return asAutoCloseStream(stream().mapToInt(mapper)); + } + + @Override + public DoubleStream mapToDouble(LongToDoubleFunction mapper) { + return asAutoCloseStream(stream().mapToDouble(mapper)); + } + + @Override + public LongStream flatMap(LongFunction mapper) { + return asAutoCloseStream(stream().flatMap(mapper)); + } + + @Override + public LongStream distinct() { + return asAutoCloseStream(stream().distinct()); + } + + @Override + public LongStream sorted() { + return asAutoCloseStream(stream().sorted()); + } + + @Override + public LongStream peek(LongConsumer action) { + return asAutoCloseStream(stream().peek(action)); + } + + @Override + public LongStream limit(long maxSize) { + return asAutoCloseStream(stream().limit(maxSize)); + } + + @Override + public LongStream skip(long n) { + return asAutoCloseStream(stream().skip(n)); + } + + @Override + public void forEach(LongConsumer action) { + autoClose(stream -> { + stream.forEach(action); + return null; + }); + } + + @Override + public void forEachOrdered(LongConsumer action) { + autoClose(stream -> { + stream.forEachOrdered(action); + return null; + }); + } + + @Override + public long[] toArray() { + return autoClose(LongStream::toArray); + } + + @Override + public long reduce(long identity, LongBinaryOperator op) { + return autoClose(stream -> stream.reduce(identity, op)); + } + + @Override + public OptionalLong reduce(LongBinaryOperator op) { + return autoClose(stream -> stream.reduce(op)); + } + + @Override + public R collect(Supplier supplier, ObjLongConsumer accumulator, BiConsumer combiner) { + return autoClose(stream -> stream.collect(supplier, accumulator, combiner)); + } + + @Override + public long sum() { + return autoClose(LongStream::sum); + } + + @Override + public OptionalLong min() { + return autoClose(LongStream::min); + } + + @Override + public OptionalLong max() { + return autoClose(LongStream::max); + } + + @Override + public long count() { + return autoClose(LongStream::count); + } + + @Override + public OptionalDouble average() { + return autoClose(LongStream::average); + } + + @Override + public LongSummaryStatistics summaryStatistics() { + return autoClose(LongStream::summaryStatistics); + } + + @Override + public boolean anyMatch(LongPredicate predicate) { + return autoClose(stream -> stream.anyMatch(predicate)); + } + + @Override + public boolean allMatch(LongPredicate predicate) { + return autoClose(stream -> stream.allMatch(predicate)); + } + + @Override + public boolean noneMatch(LongPredicate predicate) { + return autoClose(stream -> stream.noneMatch(predicate)); + } + + @Override + public OptionalLong findFirst() { + return autoClose(LongStream::findFirst); + } + + @Override + public OptionalLong findAny() { + return autoClose(LongStream::findAny); + } + + @Override + public DoubleStream asDoubleStream() { + return asAutoCloseStream(stream().asDoubleStream()); + } + + @Override + public Stream boxed() { + return asAutoCloseStream(stream().boxed()); + } + + @Override + public LongStream sequential() { + return asAutoCloseStream(stream().sequential()); + } + + @Override + public LongStream parallel() { + return asAutoCloseStream(stream().parallel()); + } + + @Override + public PrimitiveIterator.OfLong iterator() { + return stream().iterator(); + } + + @Override + public Spliterator.OfLong spliterator() { + return stream().spliterator(); + } + + @Override + public boolean isParallel() { + return stream().isParallel(); + } + + @Override + public LongStream unordered() { + return asAutoCloseStream(stream().unordered()); + } + + @Override + public LongStream onClose(Runnable closeHandler) { + return asAutoCloseStream(stream().onClose(closeHandler)); + } + +} diff --git a/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseStream.java b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseStream.java new file mode 100644 index 0000000..2108c59 --- /dev/null +++ b/src/main/java/com/apptasticsoftware/rssreader/internal/stream/AutoCloseStream.java @@ -0,0 +1,255 @@ +/* + * MIT License + * + * Copyright (c) 2024, Apptastic Software + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.apptasticsoftware.rssreader.internal.stream; + +import java.util.*; +import java.util.function.*; +import java.util.stream.*; + +/** + * A Stream that automatically calls its {@link #close()} method after a terminating operation, such as limit(), forEach(), or collect(), has been executed. + *

+ * This class is useful for working with streams that have resources that need to be closed, such as file streams or network connections. + *

+ * If the {@link #close()} method is called manually, the stream will be closed and any subsequent operations will throw an {@link IllegalStateException}. + *

+ * The {@link #iterator()} and {@link #spliterator()} methods are not supported by this class. + */ +public class AutoCloseStream extends AbstractAutoCloseStream> implements Stream { + + AutoCloseStream(Stream stream) { + super(stream); + } + + /** + * Creates a new AutoCloseStream from the given stream. + * @param stream the stream to wrap + * @return a new AutoCloseStream + */ + public static AutoCloseStream of(Stream stream) { + Objects.requireNonNull(stream); + return new AutoCloseStream<>(stream); + } + + @Override + public Stream filter(Predicate predicate) { + return asAutoCloseStream(stream().filter(predicate)); + } + + @Override + public Stream map(Function mapper) { + return asAutoCloseStream(stream().map(mapper)); + } + + @Override + public IntStream mapToInt(ToIntFunction mapper) { + return asAutoCloseStream(stream().mapToInt(mapper)); + } + + @Override + public LongStream mapToLong(ToLongFunction mapper) { + return asAutoCloseStream(stream().mapToLong(mapper)); + } + + @Override + public DoubleStream mapToDouble(ToDoubleFunction mapper) { + return asAutoCloseStream(stream().mapToDouble(mapper)); + } + + @Override + public Stream flatMap(Function> mapper) { + return asAutoCloseStream(stream().flatMap(mapper)); + } + + @Override + public IntStream flatMapToInt(Function mapper) { + return asAutoCloseStream(stream().flatMapToInt(mapper)); + } + + @Override + public LongStream flatMapToLong(Function mapper) { + return asAutoCloseStream(stream().flatMapToLong(mapper)); + } + + @Override + public DoubleStream flatMapToDouble(Function mapper) { + return asAutoCloseStream(stream().flatMapToDouble(mapper)); + } + + @Override + public Stream distinct() { + return asAutoCloseStream(stream().distinct()); + } + + @Override + public Stream sorted() { + return asAutoCloseStream(stream().sorted()); + } + + @Override + public Stream sorted(Comparator comparator) { + return asAutoCloseStream(stream().sorted(comparator)); + } + + @Override + public Stream peek(Consumer action) { + return asAutoCloseStream(stream().peek(action)); + } + + @Override + public Stream limit(long maxSize) { + return asAutoCloseStream(stream().limit(maxSize)); + } + + @Override + public Stream skip(long n) { + return asAutoCloseStream(stream().skip(n)); + } + + @Override + public void forEach(Consumer action) { + autoClose(stream -> { + stream.forEach(action); + return null; + }); + } + + @Override + public void forEachOrdered(Consumer action) { + autoClose(stream -> { + stream.forEachOrdered(action); + return null; + }); + } + + @Override + public Object[] toArray() { + return autoClose(Stream::toArray); + } + + @Override + public A[] toArray(IntFunction generator) { + return autoClose(stream -> stream.toArray(generator)); + } + + @Override + public T reduce(T identity, BinaryOperator accumulator) { + return autoClose(stream -> stream.reduce(identity, accumulator)); + } + + @Override + public Optional reduce(BinaryOperator accumulator) { + return autoClose(stream -> stream.reduce(accumulator)); + } + + @Override + public U reduce(U identity, BiFunction accumulator, BinaryOperator combiner) { + return autoClose(stream -> stream.reduce(identity, accumulator, combiner)); + } + + @Override + public R collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner) { + return autoClose(stream -> stream.collect(supplier, accumulator, combiner)); + } + + @Override + public R collect(Collector collector) { + return autoClose(stream -> stream.collect(collector)); + } + + @Override + public Optional min(Comparator comparator) { + return autoClose(stream -> stream.min(comparator)); + } + + @Override + public Optional max(Comparator comparator) { + return autoClose(stream -> stream.max(comparator)); + } + + @Override + public long count() { + return autoClose(Stream::count); + } + + @Override + public boolean anyMatch(Predicate predicate) { + return autoClose(stream -> stream.anyMatch(predicate)); + } + + @Override + public boolean allMatch(Predicate predicate) { + return autoClose(stream -> stream.allMatch(predicate)); + } + + @Override + public boolean noneMatch(Predicate predicate) { + return autoClose(stream -> stream.noneMatch(predicate)); + } + + @Override + public Optional findFirst() { + return autoClose(Stream::findFirst); + } + + @Override + public Optional findAny() { + return autoClose(Stream::findAny); + } + + @Override + public Iterator iterator() { + return stream().iterator(); + } + + @Override + public Spliterator spliterator() { + return stream().spliterator(); + } + + @Override + public boolean isParallel() { + return stream().isParallel(); + } + + @Override + public Stream sequential() { + return asAutoCloseStream(stream().sequential()); + } + + @Override + public Stream parallel() { + return asAutoCloseStream(stream().parallel()); + } + + @Override + public Stream unordered() { + return asAutoCloseStream(stream().unordered()); + } + + @Override + public Stream onClose(Runnable closeHandler) { + return asAutoCloseStream(stream().onClose(closeHandler)); + } +} diff --git a/src/test/java/com/apptasticsoftware/integrationtest/CloseTest.java b/src/test/java/com/apptasticsoftware/integrationtest/CloseTest.java new file mode 100644 index 0000000..7bdb9f2 --- /dev/null +++ b/src/test/java/com/apptasticsoftware/integrationtest/CloseTest.java @@ -0,0 +1,978 @@ +package com.apptasticsoftware.integrationtest; + +import com.apptasticsoftware.rssreader.Item; +import com.apptasticsoftware.rssreader.RssReader; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.*; + +import static org.junit.jupiter.api.Assertions.*; + +public class CloseTest { + private static final String FILE_NAME = "rss-feed.xml"; + private int closeCounter; + + @BeforeEach + void setup() { + closeCounter = 0; + } + + @Test + void testCount() { + long count = getReader().count(); + assertEquals(1, closeCounter); + assertEquals(19, count); + } + + @Test + void testUrl() throws IOException { + long count = new RssReader().read("https://gizmodo.com/feed").onClose(this::close).count(); + assertEquals(1, closeCounter); + assertTrue(count > 0); + } + + @Test + void testTwoUrls() { + var urls = List.of("https://gizmodo.com/feed", "https://feeds.a.dj.com/rss/RSSMarketsMain.xml"); + long count = new RssReader().read(urls).onClose(this::close).count(); + assertEquals(1, closeCounter); + assertTrue(count > 0); + } + + @Test + void testLimit() { + long count = getReader().limit(3).count(); + assertEquals(1, closeCounter); + assertEquals(3, count); + } + + @Test + void testSkip() { + long count = getReader().skip(3).count(); + assertEquals(1, closeCounter); + assertEquals(16, count); + } + + @Test + void testFilter() { + long count = getReader().filter(i -> i.getCategories().contains("AI")).count(); + assertEquals(1, closeCounter); + assertEquals(6, count); + } + + @Test + void testMap() { + var titles = getReader().map(Item::getTitle).collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, titles.size()); + } + + @Test + void testMapToInt() { + int sum = getReader().map(i -> i.getCategories().size()).mapToInt(Integer::intValue).sum(); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testMapToLong() { + long sum = getReader().map(i -> i.getCategories().size()).mapToLong(Integer::longValue).sum(); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testMapToDouble() { + double sum = getReader().map(i -> i.getCategories().size()).mapToDouble(Integer::doubleValue).sum(); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testFlatMap() { + long count = getReader().flatMap(i -> i.getCategories().stream()).limit(100).count(); + assertEquals(1, closeCounter); + assertEquals(100, count); + } + + @Test + void testFlatMapToInt() { + int count = getReader().flatMapToInt(i -> i.getCategories().stream().mapToInt(String::length)).sum(); + assertEquals(1, closeCounter); + assertEquals(903, count); + } + + @Test + void testFlatMapToLong() { + long count = getReader().flatMapToLong(i -> i.getCategories().stream().mapToLong(String::length)).sum(); + assertEquals(1, closeCounter); + assertEquals(903, count); + } + + @Test + void testFlatMapToDouble() { + double count = getReader().flatMapToDouble(i -> i.getCategories().stream().mapToDouble(String::length)).sum(); + assertEquals(1, closeCounter); + assertEquals(903, count); + } + + @Test + void testDistinct() { + long count = getReader().flatMap(i -> i.getCategories().stream()).distinct().count(); + assertEquals(1, closeCounter); + assertEquals(72, count); + } + + @Test + void testSorted() { + var items = getReader().flatMap(i -> i.getCategories().stream()).sorted().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(102, items.size()); + } + + @Test + void testSortedWithComparator() { + var items = getReader().flatMap(i -> i.getCategories().stream()).sorted(String::compareToIgnoreCase).collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(102, items.size()); + } + + @Test + void testPeek() { + final AtomicInteger counter = new AtomicInteger(); + var items = getReader().peek(i -> counter.incrementAndGet()).collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + assertEquals(19, items.size()); + } + + @Test + void testForEach() { + final AtomicInteger counter = new AtomicInteger(); + getReader().forEach(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testForEachOrdered() { + final AtomicInteger counter = new AtomicInteger(); + getReader().forEachOrdered(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testToArray() { + var items = getReader().toArray(); + assertEquals(1, closeCounter); + assertEquals(19, items.length); + } + + @Test + void testToArray2() { + String[] items = getReader().map(i -> i.getTitle().orElse("")).toArray(String[]::new); + assertEquals(1, closeCounter); + assertEquals(19, items.length); + } + + @Test + void testReduce() { + var count = getReader().map(i -> i.getCategories().size()).reduce(Integer::sum).orElse(0); + assertEquals(1, closeCounter); + assertEquals(102, count); + } + + @Test + void testReduce2() { + var count = getReader().map(i -> i.getCategories().size()).reduce(0, Integer::sum); + assertEquals(1, closeCounter); + assertEquals(102, count); + } + + @Test + void testReduce3() { + var count = getReader().map(i -> i.getCategories().size()).reduce(0, Integer::sum, Integer::sum); + assertEquals(1, closeCounter); + assertEquals(102, count); + } + + @Test + void testCollect() { + var items = getReader().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, items.size()); + } + + @Test + void testCollect2() { + var items = getReader().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + assertEquals(1, closeCounter); + assertEquals(19, items.size()); + } + + @Test + void testMin() { + var count = getReader().map(i -> i.getTitle().map(String::length).orElse(0)).min(Integer::compare).orElse(-1); + assertEquals(1, closeCounter); + assertEquals(34, count); + } + + @Test + void testMax() { + var count = getReader().map(i -> i.getTitle().map(String::length).orElse(0)).max(Integer::compare).orElse(-1); + assertEquals(1, closeCounter); + assertEquals(109, count); + } + + @Test + void testAnyMatch() { + var isMatch = getReader().anyMatch(i -> i.getCategories().contains("AI")); + assertEquals(1, closeCounter); + assertTrue(isMatch); + } + + @Test + void testAllMatch() { + var allMatch = getReader().allMatch(i -> i.getCategories().contains("AI")); + assertEquals(1, closeCounter); + assertFalse(allMatch); + } + + @Test + void testNoneMatch() { + var noMatch = getReader().noneMatch(i -> i.getCategories().contains("AI")); + assertEquals(1, closeCounter); + assertFalse(noMatch); + } + + @Test + void testFindFirst() { + var item = getReader().findFirst(); + assertEquals(1, closeCounter); + assertNotNull(item); + } + + @Test + void testFindAny() { + var item = getReader().findAny(); + assertEquals(1, closeCounter); + assertNotNull(item); + } + + @Test + void testIsParallel() { + var stream = getReader(); + assertFalse(stream.isParallel()); + stream.close(); + assertEquals(1, closeCounter); + } + + @Test + void testParallel() { + var stream = getReader().parallel(); + assertTrue(stream.isParallel()); + assertEquals(19, stream.count()); + assertEquals(1, closeCounter); + } + + @Test + void testSequential() { + long count = getReader().sequential().count(); + assertEquals(1, closeCounter); + assertEquals(19, count); + } + + @Test + void testUnordered() { + long count = getReader().unordered().count(); + assertEquals(1, closeCounter); + assertEquals(19, count); + } + + @Test + void testIterator() { + var stream = getReader(); + var iterator = stream.iterator(); + assertTrue(iterator.hasNext()); + assertNotNull(iterator.next()); + stream.close(); + assertEquals(1, closeCounter); + } + + @Test + void testSpliterator() { + var stream = getReader(); + var spliterator = stream.spliterator(); + assertNotNull(spliterator); + stream.close(); + assertEquals(1, closeCounter); + } + + + + @Test + void testIntStream() { + AtomicInteger counter = new AtomicInteger(); + long sum = getReaderIntStream().peek(i -> counter.incrementAndGet()).skip(1).limit(4).distinct().sorted().filter(i -> i > 5).map(i -> i * 2).sum(); + assertEquals(1, closeCounter); + assertEquals(5, counter.intValue()); + assertEquals(20, sum); + } + + @Test + void testIntStream2() { + int length = getReaderIntStream().mapToObj(String::valueOf).reduce("", String::concat).length(); + assertEquals(1, closeCounter); + assertEquals(20, length); + } + + @Test + void testIntStreamMapToLong() { + long sum = getReaderIntStream().mapToLong(Long::valueOf).sum(); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testIntStreamMapToDouble() { + double sum = getReaderIntStream().mapToDouble(Double::valueOf).sum(); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testIntStreamFlatMap() { + var sumX2 = getReaderIntStream().flatMap(i -> IntStream.of(i + i)).sum(); + assertEquals(1, closeCounter); + assertEquals(204, sumX2); + } + + @Test + void testIntStreamForEach() { + AtomicInteger counter = new AtomicInteger(); + getReaderIntStream().forEach(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testIntStreamForEachOrdered() { + AtomicInteger counter = new AtomicInteger(); + getReaderIntStream().forEachOrdered(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testIntStreamToArray() { + var array = getReaderIntStream().toArray(); + assertEquals(1, closeCounter); + assertEquals(19, array.length); + } + + @Test + void testIntStreamReduce() { + int sum = getReaderIntStream().reduce(Integer::sum).orElse(0); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testIntStreamReduce2() { + int sum = getReaderIntStream().reduce(0, Integer::sum); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testIntStreamCollect() { + var array = getReaderIntStream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + assertEquals(1, closeCounter); + assertEquals(19, array.size()); + } + + @Test + void testIntStreamMin() { + var value = getReaderIntStream().min().orElse(Integer.MAX_VALUE); + assertEquals(1, closeCounter); + assertEquals(2, value); + } + + @Test + void testIntStreamMax() { + var value = getReaderIntStream().max().orElse(Integer.MIN_VALUE); + assertEquals(1, closeCounter); + assertEquals(10, value); + } + + @Test + void testIntStreamCount() { + var value = getReaderIntStream().count(); + assertEquals(1, closeCounter); + assertEquals(19, value); + } + + @Test + void testIntStreamAverage() { + double value = getReaderIntStream().average().orElse(0.0); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testIntStreamSummaryStatistics() { + double value = getReaderIntStream().summaryStatistics().getAverage(); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testIntStreamAnyMatch() { + boolean anyMatch = getReaderIntStream().anyMatch(i -> i > 5); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testIntStreamAllMatch() { + boolean anyMatch = getReaderIntStream().allMatch(i -> i > 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testIntStreamNoneMatch() { + boolean anyMatch = getReaderIntStream().noneMatch(i -> i == 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testIntStreamFindFirst() { + int value = getReaderIntStream().findFirst().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6, value); + } + + @Test + void testIntStreamFindAny() { + int value = getReaderIntStream().findAny().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6, value); + } + + @Test + void testIntStreamAsLongStream() { + var values = getReaderIntStream().asLongStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamAsDoubleStream() { + var values = getReaderIntStream().asDoubleStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamBoxed() { + var values = getReaderIntStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamSequential() { + var stream = getReaderIntStream().sequential(); + assertFalse(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamParallel() { + //var values = getReaderIntStream().parallel().boxed().collect(Collectors.toList()); + var stream = getReaderIntStream().parallel(); + assertTrue(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamUnordered() { + var values = getReaderIntStream().unordered().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testIntStreamIterator() { + var stream = getReaderIntStream(); + var iterator = stream.iterator(); + assertTrue(iterator.hasNext()); + assertNotNull(iterator.next()); + stream.close(); + assertEquals(1, closeCounter); + } + + @Test + void testIntStreamSpliterator() { + var stream = getReaderIntStream(); + var spliterator = stream.spliterator(); + assertNotNull(spliterator); + stream.close(); + assertEquals(1, closeCounter); + } + + + + @Test + void testLongStream() { + AtomicInteger counter = new AtomicInteger(); + long sum = getReaderLongStream().peek(i -> counter.incrementAndGet()).skip(1).limit(4).distinct().sorted().filter(i -> i > 5).map(i -> i * 2).sum(); + assertEquals(1, closeCounter); + assertEquals(5, counter.intValue()); + assertEquals(20, sum); + } + + @Test + void testLongStream2() { + int length = getReaderLongStream().mapToObj(String::valueOf).reduce("", String::concat).length(); + assertEquals(1, closeCounter); + assertEquals(20, length); + } + + @Test + void testLongStreamMapToLong() { + int sum = getReaderLongStream().mapToInt(i -> (int) i).sum(); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testLongStreamMapToDouble() { + double sum = getReaderLongStream().mapToDouble(Double::valueOf).sum(); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testLongStreamFlatMap() { + var sumX2 = getReaderLongStream().flatMap(i -> LongStream.of(i + i)).sum(); + assertEquals(1, closeCounter); + assertEquals(204, sumX2); + } + + @Test + void testLongStreamForEach() { + AtomicInteger counter = new AtomicInteger(); + getReaderLongStream().forEach(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testLongStreamForEachOrdered() { + AtomicInteger counter = new AtomicInteger(); + getReaderLongStream().forEachOrdered(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testLongStreamToArray() { + var array = getReaderLongStream().toArray(); + assertEquals(1, closeCounter); + assertEquals(19, array.length); + } + + @Test + void testLongStreamReduce() { + long sum = getReaderLongStream().reduce(Long::sum).orElse(0); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testLongStreamReduce2() { + long sum = getReaderLongStream().reduce(0, Long::sum); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testLongStreamCollect() { + var array = getReaderLongStream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + assertEquals(1, closeCounter); + assertEquals(19, array.size()); + } + + @Test + void testLongStreamMin() { + var value = getReaderLongStream().min().orElse(Integer.MAX_VALUE); + assertEquals(1, closeCounter); + assertEquals(2, value); + } + + @Test + void testLongStreamMax() { + var value = getReaderLongStream().max().orElse(Integer.MIN_VALUE); + assertEquals(1, closeCounter); + assertEquals(10, value); + } + + @Test + void testLongStreamCount() { + var value = getReaderLongStream().count(); + assertEquals(1, closeCounter); + assertEquals(19, value); + } + + @Test + void testLongStreamAverage() { + double value = getReaderLongStream().average().orElse(0.0); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testLongStreamSummaryStatistics() { + double value = getReaderLongStream().summaryStatistics().getAverage(); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testLongStreamAnyMatch() { + boolean anyMatch = getReaderLongStream().anyMatch(i -> i > 5); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testLongStreamAllMatch() { + boolean anyMatch = getReaderLongStream().allMatch(i -> i > 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testLongStreamNoneMatch() { + boolean anyMatch = getReaderLongStream().noneMatch(i -> i == 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testLongStreamFindFirst() { + long value = getReaderLongStream().findFirst().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6, value); + } + + @Test + void testLongStreamFindAny() { + long value = getReaderLongStream().findAny().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6, value); + } + + @Test + void testLongStreamAsDoubleStream() { + var values = getReaderLongStream().asDoubleStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testLongStreamBoxed() { + var values = getReaderLongStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testLongStreamSequential() { + var stream = getReaderLongStream().sequential(); + assertFalse(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testLongStreamParallel() { + var stream = getReaderLongStream().parallel(); + assertTrue(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testLongStreamUnordered() { + var values = getReaderLongStream().unordered().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testLongStreamIterator() { + var stream = getReaderLongStream(); + var iterator = stream.iterator(); + assertTrue(iterator.hasNext()); + assertNotNull(iterator.next()); + stream.close(); + assertEquals(1, closeCounter); + } + + @Test + void testLongStreamSpliterator() { + var stream = getReaderLongStream(); + var spliterator = stream.spliterator(); + assertNotNull(spliterator); + stream.close(); + assertEquals(1, closeCounter); + } + + + + @Test + void testDoubleStream() { + AtomicInteger counter = new AtomicInteger(); + double sum = getReaderDoubleStream().peek(i -> counter.incrementAndGet()).skip(1).limit(4).distinct().sorted().filter(i -> i > 5.0).map(i -> i * 2.0).sum(); + assertEquals(1, closeCounter); + assertEquals(5, counter.intValue()); + assertEquals(20, sum); + } + + @Test + void testDoubleStream2() { + int length = getReaderDoubleStream().mapToObj(String::valueOf).reduce("", String::concat).length(); + assertEquals(1, closeCounter); + assertEquals(58, length); + } + + @Test + void testDoubleStreamMapToInt() { + int sum = getReaderDoubleStream().mapToInt(i -> Double.valueOf(i).intValue()).sum(); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testDoubleStreamMapToLong() { + long sum = getReaderDoubleStream().mapToLong(i -> Double.valueOf(i).longValue()).sum(); + assertEquals(1, closeCounter); + assertEquals(102, sum); + } + + @Test + void testDoubleStreamFlatMap() { + var sumX2 = getReaderDoubleStream().flatMap(i -> DoubleStream.of(i + i)).sum(); + assertEquals(1, closeCounter); + assertEquals(204.0, sumX2); + } + + @Test + void testDoubleStreamForEach() { + AtomicInteger counter = new AtomicInteger(); + getReaderDoubleStream().forEach(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testDoubleStreamForEachOrdered() { + AtomicInteger counter = new AtomicInteger(); + getReaderDoubleStream().forEachOrdered(i -> counter.incrementAndGet()); + assertEquals(1, closeCounter); + assertEquals(19, counter.intValue()); + } + + @Test + void testDoubleStreamToArray() { + var array = getReaderDoubleStream().toArray(); + assertEquals(1, closeCounter); + assertEquals(19, array.length); + } + + @Test + void testDoubleStreamReduce() { + double sum = getReaderDoubleStream().reduce(Double::sum).orElse(0); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testDoubleStreamReduce2() { + double sum = getReaderDoubleStream().reduce(0, Double::sum); + assertEquals(1, closeCounter); + assertEquals(102.0, sum); + } + + @Test + void testDoubleStreamCollect() { + var array = getReaderDoubleStream().collect(ArrayList::new, ArrayList::add, ArrayList::addAll); + assertEquals(1, closeCounter); + assertEquals(19, array.size()); + } + + @Test + void testDoubleStreamMin() { + var value = getReaderDoubleStream().min().orElse(Integer.MAX_VALUE); + assertEquals(1, closeCounter); + assertEquals(2, value); + } + + @Test + void testDoubleStreamMax() { + var value = getReaderDoubleStream().max().orElse(Integer.MIN_VALUE); + assertEquals(1, closeCounter); + assertEquals(10, value); + } + + @Test + void testDoubleStreamCount() { + var value = getReaderDoubleStream().count(); + assertEquals(1, closeCounter); + assertEquals(19, value); + } + + @Test + void testDoubleStreamAverage() { + double value = getReaderDoubleStream().average().orElse(0.0); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testDoubleStreamSummaryStatistics() { + double value = getReaderDoubleStream().summaryStatistics().getAverage(); + assertEquals(1, closeCounter); + assertEquals(5.368421052631579, value); + } + + @Test + void testDoubleStreamAnyMatch() { + boolean anyMatch = getReaderDoubleStream().anyMatch(i -> i > 5); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testDoubleStreamAllMatch() { + boolean anyMatch = getReaderDoubleStream().allMatch(i -> i > 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testDoubleStreamNoneMatch() { + boolean anyMatch = getReaderDoubleStream().noneMatch(i -> i == 0); + assertEquals(1, closeCounter); + assertTrue(anyMatch); + } + + @Test + void testDoubleStreamFindFirst() { + double value = getReaderDoubleStream().findFirst().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6.0, value); + } + + @Test + void testDoubleStreamFindAny() { + double value = getReaderDoubleStream().findAny().orElse(0); + assertEquals(1, closeCounter); + assertEquals(6.0, value); + } + + @Test + void testDoubleStreamBoxed() { + var values = getReaderDoubleStream().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testDoubleStreamSequential() { + var stream = getReaderDoubleStream().sequential(); + assertFalse(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testDoubleStreamParallel() { + //var values = getReaderDoubleStream().parallel().boxed().collect(Collectors.toList()); + var stream = getReaderDoubleStream().parallel(); + assertTrue(stream.isParallel()); + var values = stream.boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testDoubleStreamUnordered() { + var values = getReaderDoubleStream().unordered().boxed().collect(Collectors.toList()); + assertEquals(1, closeCounter); + assertEquals(19, values.size()); + } + + @Test + void testDoubleStreamIterator() { + var stream = getReaderDoubleStream(); + var iterator = stream.iterator(); + assertTrue(iterator.hasNext()); + assertNotNull(iterator.next()); + stream.close(); + assertEquals(1, closeCounter); + } + + @Test + void testDoubleStreamSpliterator() { + var stream = getReaderDoubleStream(); + var spliterator = stream.spliterator(); + assertNotNull(spliterator); + stream.close(); + assertEquals(1, closeCounter); + } + + + + void close() { + closeCounter++; + } + + private Stream getReader() { + return new RssReader().read(fromFile(FILE_NAME)).onClose(this::close).limit(19); + } + + private IntStream getReaderIntStream() { + return new RssReader().read(fromFile(FILE_NAME)).mapToInt(i -> i.getCategories().size()).onClose(this::close).limit(19); + } + + private LongStream getReaderLongStream() { + return new RssReader().read(fromFile(FILE_NAME)).mapToLong(i -> i.getCategories().size()).onClose(this::close).limit(19); + } + + private DoubleStream getReaderDoubleStream() { + return new RssReader().read(fromFile(FILE_NAME)).mapToDouble(i -> i.getCategories().size()).onClose(this::close).limit(19); + } + + private InputStream fromFile(String fileName) { + return getClass().getClassLoader().getResourceAsStream(fileName); + } +} diff --git a/src/test/java/com/apptasticsoftware/integrationtest/ConnectionTest.java b/src/test/java/com/apptasticsoftware/integrationtest/ConnectionTest.java index ff21f71..e06c44c 100644 --- a/src/test/java/com/apptasticsoftware/integrationtest/ConnectionTest.java +++ b/src/test/java/com/apptasticsoftware/integrationtest/ConnectionTest.java @@ -2,7 +2,7 @@ import com.apptasticsoftware.rssreader.Item; import com.apptasticsoftware.rssreader.RssReader; -import com.apptasticsoftware.rssreader.util.RssServer; +import com.apptasticsoftware.rssreader.internal.RssServer; import org.junit.jupiter.api.Test; import java.io.File; diff --git a/src/test/java/com/apptasticsoftware/integrationtest/SortTest.java b/src/test/java/com/apptasticsoftware/integrationtest/SortTest.java index 54cedac..3df5734 100644 --- a/src/test/java/com/apptasticsoftware/integrationtest/SortTest.java +++ b/src/test/java/com/apptasticsoftware/integrationtest/SortTest.java @@ -10,7 +10,6 @@ import java.util.List; import java.util.Optional; import java.util.stream.Collectors; -import java.util.stream.Stream; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -45,17 +44,17 @@ void testTimestampSortTest() { "https://moxie.foxnews.com/google-publisher/latest.xml", "https://techcrunch.com/feed/", "https://feeds.arstechnica.com/arstechnica/science" - ); + ); List extendedUrlList = new ArrayList<>(urlList); extendedUrlList.add(null); var timestamps = new RssReader().read(extendedUrlList) - .sorted() - .map(Item::getPubDateZonedDateTime) - .flatMap(Optional::stream) - .map(t -> t.toInstant().toEpochMilli()) - .collect(Collectors.toList()); + .sorted() + .map(Item::getPubDateZonedDateTime) + .flatMap(Optional::stream) + .map(t -> t.toInstant().toEpochMilli()) + .collect(Collectors.toList()); assertTrue(timestamps.size() > 200); @@ -99,12 +98,11 @@ void testSortOldestFirst() throws IOException { } @Test - void testSortChannelTitle() throws IOException { - - var list = Stream.concat(new RssReader().read("https://feeds.a.dj.com/rss/RSSMarketsMain.xml"), - new RssReader().read("https://gizmodo.com/feed")) - .sorted(ItemComparator.channelTitle()) - .collect(Collectors.toList()); + void testSortChannelTitle() { + var urls = List.of("https://feeds.a.dj.com/rss/RSSMarketsMain.xml", "https://gizmodo.com/feed"); + var list = new RssReader().read(urls) + .sorted(ItemComparator.channelTitle()) + .collect(Collectors.toList()); var first = list.get(0); var last = list.get(list.size() - 1); diff --git a/src/test/java/com/apptasticsoftware/rssreader/util/RssServer.java b/src/test/java/com/apptasticsoftware/rssreader/internal/RssServer.java similarity index 99% rename from src/test/java/com/apptasticsoftware/rssreader/util/RssServer.java rename to src/test/java/com/apptasticsoftware/rssreader/internal/RssServer.java index 2f4f93c..83cf8da 100644 --- a/src/test/java/com/apptasticsoftware/rssreader/util/RssServer.java +++ b/src/test/java/com/apptasticsoftware/rssreader/internal/RssServer.java @@ -1,4 +1,4 @@ -package com.apptasticsoftware.rssreader.util; +package com.apptasticsoftware.rssreader.internal; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpHandler; diff --git a/src/test/resources/rss-feed.xml b/src/test/resources/rss-feed.xml new file mode 100644 index 0000000..e9923ea --- /dev/null +++ b/src/test/resources/rss-feed.xml @@ -0,0 +1,330 @@ + + + + TechCrunch + + https://techcrunch.com/ + Startup and Technology News + Fri, 23 Aug 2024 01:11:17 +0000 + en-US + hourly + 1 + https://wordpress.org/?v=6.5.5 + + https://techcrunch.com/wp-content/uploads/2015/02/cropped-cropped-favicon-gradient.png?w=32 + TechCrunch + https://techcrunch.com/ + 32 + 32 + + + DeepMind workers sign letter in protest of Google’s defense contracts + https://techcrunch.com/2024/08/22/deepmind-workers-sign-letter-in-protest-of-googles-defense-contracts/ + + Thu, 22 Aug 2024 22:15:21 +0000 + + + + + + + https://techcrunch.com/?p=2841593 + At least 200 workers at DeepMind, Google’s AI R&D division, are displeased with Google’s reported defense contracts — and according to Time, they circulated a letter internally back in May to say as much. The letter, dated May 16, says the undersigned are concerned by “Google’s contracts with military organizations,” citing articles about the tech […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]> +
+ + NASA to decide Saturday whether astronauts will ride Boeing’s Starliner home — or use SpaceX’s Dragon instead + https://techcrunch.com/2024/08/22/nasa-to-decide-saturday-whether-astronauts-will-ride-boeings-starliner-home-or-use-spacexs-dragon-instead/ + + Thu, 22 Aug 2024 22:14:04 +0000 + + + https://techcrunch.com/?p=2841608 + NASA officials will announce their final decision on Saturday as to whether two NASA astronauts — Butch Wilmore and Sunita Williams — will return to Earth on Boeing’s Starliner spacecraft or hitch a ride home with SpaceX instead — a decision that could have a huge impact across the rapidly evolving space industry. Here’s the […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Gimbal Space takes on legacy suppliers with fast-paced component supply chain + https://techcrunch.com/2024/08/22/gimbal-space-takes-on-legacy-suppliers-with-fast-paced-component-supply-chain/ + + Thu, 22 Aug 2024 21:15:16 +0000 + + + https://techcrunch.com/?p=2840801 + America’s space industry seems mature, but the supply chain that provides all the parts and components for rockets, satellites, and other spacecraft is considerably less so. Gimbal Space is aiming to change that, starting with components in the crucial subsystem that enables a spacecraft to orient itself in space — but delivered cheaper and a whole […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Waymo wants to chauffeur your kids + https://techcrunch.com/2024/08/22/waymo-wants-to-chauffeur-your-kids/ + + Thu, 22 Aug 2024 20:36:06 +0000 + + + + + + + + + + + https://techcrunch.com/?p=2841545 + Soon, parents in range of Waymo robotaxis might not have to worry about picking up their kids from after-school activities — or any time, really. The San Francisco Standard reports that Waymo, the Alphabet subsidiary, is considering a subscription program that would let teens hail one of its cars solo and send pickup and drop-off […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Synapse’s collapse has frozen nearly $160M from fintech users — here’s how it happened + https://techcrunch.com/2024/08/22/synapses-collapse-has-frozen-nearly-160m-from-fintech-users-heres-how-it-happened/ + + Thu, 22 Aug 2024 20:33:27 +0000 + + + + + + https://techcrunch.com/?p=2807823 + Here is a timeline of Synapse’s troubles and the ongoing impact it is having on banking consumers.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Founder of failed fintech Synapse says he’s raised $11M for new robotics startup + https://techcrunch.com/2024/08/22/founder-of-failed-fintech-synapse-says-hes-raised-11m-for-new-robotics-startup/ + + Thu, 22 Aug 2024 20:21:36 +0000 + + + + + + + https://techcrunch.com/?p=2841538 + Tens of millions of customer dollars remain unaccounted for at his previous startup, fintech Synapse. But that’s not deterring Sankaet Pathak from forging full steam ahead with his new robotics venture. Foundation is a robotics startup with a self-proclaimed mission “to create advanced humanoid robots that can operate in complex environments” to address the labor […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Cruise’s robotaxis are coming to the Uber app in 2025 + https://techcrunch.com/2024/08/22/cruises-robotaxis-are-coming-to-the-uber-app-in-2025/ + + Thu, 22 Aug 2024 20:04:47 +0000 + + + + + + + + https://techcrunch.com/?p=2841539 + Cruise, General Motors’ self-driving subsidiary, said it has signed a multi-year partnership with ride-hailing giant Uber to bring its robotaxis to the ride-hailing platform in 2025.  Cruise didn’t say when exactly customers would see its vehicles on Uber’s platform, but a spokesperson told TechCrunch that this partnership will follow the re-launch of Cruise’s own driverless […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Boston Dynamics’ new electric Atlas can do push-ups + https://techcrunch.com/2024/08/22/boston-dynamics-new-electric-atlas-can-do-push-ups/ + + Thu, 22 Aug 2024 19:28:43 +0000 + + + + https://techcrunch.com/?p=2841507 + Until today, we’ve seen exactly 40 seconds of Boston Dynamics’ new electric Atlas in action. The Hyundai-owned robotics stalwart is very much still in the early stages of commercializing the biped for factory floors. For now, however, it’s doing the thing Boston Dynamics does second best after building robots: showing off in viral video form. […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + AI sales rep startups are booming. So why are VCs wary? + https://techcrunch.com/2024/08/22/ai-sdr-startups-are-booming-so-why-are-vcs-wary/ + + Thu, 22 Aug 2024 18:23:41 +0000 + + + + + + + + + + https://techcrunch.com/?p=2841348 + When you really probe venture capitalists about investing in AI startups, they’ll tell you that businesses are experimenting wildly but are very slow to add AI solutions into their ongoing business processes.  But there are some exceptions. And one of them appears to be an area known as AI sales development representatives, or AI SDRs. […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Former Alphabet X spinout Mineral sells technology to John Deere + https://techcrunch.com/2024/08/22/former-alphabet-x-spinout-mineral-sells-technology-to-john-deere/ + + Thu, 22 Aug 2024 17:29:59 +0000 + + + + + + + + https://techcrunch.com/?p=2841333 + Citing a crowded market and profit concerns, Mineral ceased operation and pivoted to technology licensing.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Relive the Myspace days by adding a favorite song to your Instagram profile + https://techcrunch.com/2024/08/22/relive-the-myspace-days-by-adding-a-favorite-song-to-your-instagram-profile/ + + Thu, 22 Aug 2024 17:11:15 +0000 + + + + https://techcrunch.com/?p=2841261 + A nod to the Myspace era, Instagram just launched a new feature that allows you to add music to your profile. The feature is in collaboration with singer Sabrina Carpenter, who released a teaser of her new song “Taste” on her Instagram profile ahead of her album release on Friday. To add a song to […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Ford pivots on EVs, Waymo doubles its robotaxi ridership and Canoo leaves California + https://techcrunch.com/2024/08/22/ford-pivots-on-evs-waymo-doubles-its-robotaxi-ridership-and-canoo-leaves-california/ + + Thu, 22 Aug 2024 17:05:00 +0000 + + + + + + https://techcrunch.com/?p=2840820 + Welcome back to TechCrunch Mobility — your central hub for news and insights on the future of transportation. Sign up here for free — just click TechCrunch Mobility! Monterey Car Week is a wrap. And while it is still steeped in tradition, the collection of parties, auctions and car shows that were held throughout the […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Ecovacs says it will fix bugs that can be abused to spy on robot owners + https://techcrunch.com/2024/08/22/ecovacs-says-it-will-fix-bugs-that-can-be-abused-to-spy-on-robot-owners/ + + Thu, 22 Aug 2024 16:52:22 +0000 + + + + + + + + + + https://techcrunch.com/?p=2841191 + After saying users "do not need to worry excessively" about a series of security flaws, Ecovacs said it will — in fact — roll out fixes.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Alphabet X’s latest spinout brings computer vision and AI to salmon farms + https://techcrunch.com/2024/08/22/alphabet-xs-latest-spinout-brings-computer-vision-and-ai-to-salmon-farms/ + + Thu, 22 Aug 2024 16:32:24 +0000 + + + + + + https://techcrunch.com/?p=2841276 + Tidal, which quietly spun out of the department in mid-July, has its own grand ambitions to “feed humanity sustainably.”

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + India’s open commerce network expands into digital lending + https://techcrunch.com/2024/08/22/indias-open-commerce-network-expands-into-digital-lending/ + + Thu, 22 Aug 2024 16:21:32 +0000 + + + + + https://techcrunch.com/?p=2841078 + India's ONDC has launched digital lending on its network as it expands into financial services after powering e-commerce, mobility and logistics with its standardized framework.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Under DMA probe, Apple tweaks design of EU browser choice screens, expands app default settings + https://techcrunch.com/2024/08/22/under-dma-probe-apple-tweaks-design-of-eu-browser-choice-screens-expands-app-default-settings/ + + Thu, 22 Aug 2024 16:02:40 +0000 + + + + + https://techcrunch.com/?p=2841174 + Apple continues to adjust its approach to compliance with the European Union’s Digital Markets Act (DMA): Announcing another batch of changes Thursday, the iPhone maker showed off redesigned browser choice screens it said would be coming to iOS and iPadOS “later this year,” with version 18 of its mobile software platforms. The tweaked browser choice […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + TechCrunch Minute: AI could help design and test F1 cars faster + https://techcrunch.com/video/techcrunch-minute-ai-could-help-design-and-test-f1-cars-faster/ + + Thu, 22 Aug 2024 16:00:00 +0000 + + + + + https://techcrunch.com/?post_type=tc_video&p=2841127 + Formula One teams are looking at a startup called BeyondMath to bring their car construction to the next level. BeyondMath is working in the field of computational fluid dynamics, which attempts to digitally model how an object moves through water or air. But that’s a lot easier said than done — even with excessive computing […]

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Threads spotted exploring ads, but says ‘no immediate timeline’ toward monetization + https://techcrunch.com/2024/08/22/threads-explores-ads-but-says-no-immediate-timeline-toward-monetization/ + + Thu, 22 Aug 2024 15:36:56 +0000 + + + + + https://techcrunch.com/?p=2841142 + These findings indicate that Threads engineers are exploring ad technology, but that doesn't mean Threads will debut ads anytime soon, as some suspect.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Cruise recalls robotaxi fleet to resolve federal safety probe + https://techcrunch.com/2024/08/22/cruise-recall-av-fleet-nhtsa-probe-closed/ + + Thu, 22 Aug 2024 15:33:05 +0000 + + + + + + + + https://techcrunch.com/?p=2841150 + The recall and the conclusion of the probe takes one worry off of Cruise's plate at a time when the company is under great scrutiny.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + Cache Energy’s mysterious white pellets could help kill coal and natural gas + https://techcrunch.com/2024/08/22/cache-energys-mysterious-white-pellets-could-help-kill-coal-and-natural-gas/ + + Thu, 22 Aug 2024 15:30:00 +0000 + + + + + + + + https://techcrunch.com/?p=2839682 + The pellets can be stored in piles or silos, moved around using conveyor belts, and transported via rail cars.

+

© 2024 TechCrunch. All rights reserved. For personal use only.

+]]>
+
+ + \ No newline at end of file