< prev index next >

src/java.base/share/classes/java/util/Scanner.java

Print this page
rev 12670 : 8072722: add stream support to Scanner
Reviewed-by: psandoz

*** 1,7 **** /* ! * Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this --- 1,7 ---- /* ! * Copyright (c) 2003, 2015, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this
*** 23,42 **** * questions. */ package java.util; - import java.nio.file.Path; - import java.nio.file.Files; - import java.util.regex.*; import java.io.*; import java.math.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; import java.text.*; ! import java.util.Locale; import sun.misc.LRUCache; /** * A simple text scanner which can parse primitive types and strings using --- 23,44 ---- * questions. */ package java.util; import java.io.*; import java.math.*; import java.nio.*; import java.nio.channels.*; import java.nio.charset.*; + import java.nio.file.Path; + import java.nio.file.Files; import java.text.*; ! import java.util.function.Consumer; ! import java.util.regex.*; ! import java.util.stream.Stream; ! import java.util.stream.StreamSupport; import sun.misc.LRUCache; /** * A simple text scanner which can parse primitive types and strings using
*** 94,119 **** * System.out.println(result.group(i)); * s.close(); * }</pre></blockquote> * * <p>The <a name="default-delimiter">default whitespace delimiter</a> used ! * by a scanner is as recognized by {@link java.lang.Character}.{@link ! * java.lang.Character#isWhitespace(char) isWhitespace}. The {@link #reset} * method will reset the value of the scanner's delimiter to the default * whitespace delimiter regardless of whether it was previously changed. * * <p>A scanning operation may block waiting for input. * * <p>The {@link #next} and {@link #hasNext} methods and their ! * primitive-type companion methods (such as {@link #nextInt} and * {@link #hasNextInt}) first skip any input that matches the delimiter ! * pattern, and then attempt to return the next token. Both {@code hasNext} ! * and {@code next} methods may block waiting for further input. Whether a ! * {@code hasNext} method blocks has no connection to whether or not its ! * associated {@code next} method will block. ! * ! * <p> The {@link #findInLine}, {@link #findWithinHorizon}, and {@link #skip} * methods operate independently of the delimiter pattern. These methods will * attempt to match the specified pattern with no regard to delimiters in the * input and thus can be used in special circumstances where delimiters are * not relevant. These methods may block waiting for more input. * --- 96,124 ---- * System.out.println(result.group(i)); * s.close(); * }</pre></blockquote> * * <p>The <a name="default-delimiter">default whitespace delimiter</a> used ! * by a scanner is as recognized by {@link Character#isWhitespace(char) ! * Character.isWhitespace()}. The {@link #reset reset()} * method will reset the value of the scanner's delimiter to the default * whitespace delimiter regardless of whether it was previously changed. * * <p>A scanning operation may block waiting for input. * * <p>The {@link #next} and {@link #hasNext} methods and their ! * companion methods (such as {@link #nextInt} and * {@link #hasNextInt}) first skip any input that matches the delimiter ! * pattern, and then attempt to return the next token. Both {@code hasNext()} ! * and {@code next()} methods may block waiting for further input. Whether a ! * {@code hasNext()} method blocks has no connection to whether or not its ! * associated {@code next()} method will block. The {@link #tokens} method ! * may also block waiting for input. ! * ! * <p>The {@link #findInLine findInLine()}, ! * {@link #findWithinHorizon findWithinHorizon()}, ! * {@link #skip skip()}, and {@link #findAll findAll()} * methods operate independently of the delimiter pattern. These methods will * attempt to match the specified pattern with no regard to delimiters in the * input and thus can be used in special circumstances where delimiters are * not relevant. These methods may block waiting for more input. *
*** 127,137 **** * pattern {@code "\\s"} could return empty tokens since it only passes one * space at a time. * * <p> A scanner can read text from any object which implements the {@link * java.lang.Readable} interface. If an invocation of the underlying ! * readable's {@link java.lang.Readable#read} method throws an {@link * java.io.IOException} then the scanner assumes that the end of the input * has been reached. The most recent {@code IOException} thrown by the * underlying readable can be retrieved via the {@link #ioException} method. * * <p>When a {@code Scanner} is closed, it will close its input source --- 132,142 ---- * pattern {@code "\\s"} could return empty tokens since it only passes one * space at a time. * * <p> A scanner can read text from any object which implements the {@link * java.lang.Readable} interface. If an invocation of the underlying ! * readable's {@link java.lang.Readable#read read()} method throws an {@link * java.io.IOException} then the scanner assumes that the end of the input * has been reached. The most recent {@code IOException} thrown by the * underlying readable can be retrieved via the {@link #ioException} method. * * <p>When a {@code Scanner} is closed, it will close its input source
*** 154,164 **** * <p> An instance of this class is capable of scanning numbers in the standard * formats as well as in the formats of the scanner's locale. A scanner's * <a name="initial-locale">initial locale </a>is the value returned by the {@link * java.util.Locale#getDefault(Locale.Category) * Locale.getDefault(Locale.Category.FORMAT)} method; it may be changed via the {@link ! * #useLocale} method. The {@link #reset} method will reset the value of the * scanner's locale to the initial locale regardless of whether it was * previously changed. * * <p>The localized formats are defined in terms of the following parameters, * which for a particular locale are taken from that locale's {@link --- 159,169 ---- * <p> An instance of this class is capable of scanning numbers in the standard * formats as well as in the formats of the scanner's locale. A scanner's * <a name="initial-locale">initial locale </a>is the value returned by the {@link * java.util.Locale#getDefault(Locale.Category) * Locale.getDefault(Locale.Category.FORMAT)} method; it may be changed via the {@link ! * #useLocale useLocale()} method. The {@link #reset} method will reset the value of the * scanner's locale to the initial locale regardless of whether it was * previously changed. * * <p>The localized formats are defined in terms of the following parameters, * which for a particular locale are taken from that locale's {@link
*** 372,381 **** --- 377,391 ---- }; // A holder of the last IOException encountered private IOException lastException; + // Number of times this scanner's state has been modified. + // Generally incremented on most public APIs and checked + // within spliterator implementations. + int modCount; + // A pattern for java whitespace private static Pattern WHITESPACE_PATTERN = Pattern.compile( "\\p{javaWhitespace}+"); // A pattern for any token
*** 993,1004 **** needInput = true; return null; } // Finds the specified pattern in the buffer up to horizon. ! // Returns a match for the specified input pattern. ! private String findPatternInBuffer(Pattern pattern, int horizon) { matchValid = false; matcher.usePattern(pattern); int bufferLimit = buf.limit(); int horizonLimit = -1; int searchLimit = bufferLimit; --- 1003,1015 ---- needInput = true; return null; } // Finds the specified pattern in the buffer up to horizon. ! // Returns true if the specified input pattern was matched, ! // and leaves the matcher field with the current match state. ! private boolean findPatternInBuffer(Pattern pattern, int horizon) { matchValid = false; matcher.usePattern(pattern); int bufferLimit = buf.limit(); int horizonLimit = -1; int searchLimit = bufferLimit;
*** 1012,1069 **** if (matcher.hitEnd() && (!sourceClosed)) { // The match may be longer if didn't hit horizon or real end if (searchLimit != horizonLimit) { // Hit an artificial end; try to extend the match needInput = true; ! return null; } // The match could go away depending on what is next if ((searchLimit == horizonLimit) && matcher.requireEnd()) { // Rare case: we hit the end of input and it happens // that it is at the horizon and the end of input is // required for the match. needInput = true; ! return null; } } // Did not hit end, or hit real end, or hit horizon position = matcher.end(); ! return matcher.group(); } if (sourceClosed) ! return null; // If there is no specified horizon, or if we have not searched // to the specified horizon yet, get more input if ((horizon == 0) || (searchLimit != horizonLimit)) needInput = true; ! return null; } ! // Returns a match for the specified input pattern anchored at ! // the current position ! private String matchPatternInBuffer(Pattern pattern) { matchValid = false; matcher.usePattern(pattern); matcher.region(position, buf.limit()); if (matcher.lookingAt()) { if (matcher.hitEnd() && (!sourceClosed)) { // Get more input and try again needInput = true; ! return null; } position = matcher.end(); ! return matcher.group(); } if (sourceClosed) ! return null; // Read more to find pattern needInput = true; ! return null; } // Throws if the scanner is closed private void ensureOpen() { if (closed) --- 1023,1081 ---- if (matcher.hitEnd() && (!sourceClosed)) { // The match may be longer if didn't hit horizon or real end if (searchLimit != horizonLimit) { // Hit an artificial end; try to extend the match needInput = true; ! return false; } // The match could go away depending on what is next if ((searchLimit == horizonLimit) && matcher.requireEnd()) { // Rare case: we hit the end of input and it happens // that it is at the horizon and the end of input is // required for the match. needInput = true; ! return false; } } // Did not hit end, or hit real end, or hit horizon position = matcher.end(); ! return true; } if (sourceClosed) ! return false; // If there is no specified horizon, or if we have not searched // to the specified horizon yet, get more input if ((horizon == 0) || (searchLimit != horizonLimit)) needInput = true; ! return false; } ! // Attempts to match a pattern anchored at the current position. ! // Returns true if the specified input pattern was matched, ! // and leaves the matcher field with the current match state. ! private boolean matchPatternInBuffer(Pattern pattern) { matchValid = false; matcher.usePattern(pattern); matcher.region(position, buf.limit()); if (matcher.lookingAt()) { if (matcher.hitEnd() && (!sourceClosed)) { // Get more input and try again needInput = true; ! return false; } position = matcher.end(); ! return true; } if (sourceClosed) ! return false; // Read more to find pattern needInput = true; ! return false; } // Throws if the scanner is closed private void ensureOpen() { if (closed)
*** 1126,1135 **** --- 1138,1148 ---- * * @param pattern A delimiting pattern * @return this scanner */ public Scanner useDelimiter(Pattern pattern) { + modCount++; delimPattern = pattern; return this; } /**
*** 1145,1154 **** --- 1158,1168 ---- * * @param pattern A string specifying a delimiting pattern * @return this scanner */ public Scanner useDelimiter(String pattern) { + modCount++; delimPattern = patternCache.forName(pattern); return this; } /**
*** 1179,1188 **** --- 1193,1203 ---- */ public Scanner useLocale(Locale locale) { if (locale.equals(this.locale)) return this; + modCount++; this.locale = locale; DecimalFormat df = (DecimalFormat)NumberFormat.getNumberInstance(locale); DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(locale);
*** 1234,1245 **** * * <p>A scanner's radix affects elements of its default * number matching regular expressions; see * <a href= "#localized-numbers">localized numbers</a> above. * ! * <p>If the radix is less than {@code Character.MIN_RADIX} ! * or greater than {@code Character.MAX_RADIX}, then an * {@code IllegalArgumentException} is thrown. * * <p>Invoking the {@link #reset} method will set the scanner's radix to * {@code 10}. * --- 1249,1260 ---- * * <p>A scanner's radix affects elements of its default * number matching regular expressions; see * <a href= "#localized-numbers">localized numbers</a> above. * ! * <p>If the radix is less than {@link Character#MIN_RADIX Character.MIN_RADIX} ! * or greater than {@link Character#MAX_RADIX Character.MAX_RADIX}, then an * {@code IllegalArgumentException} is thrown. * * <p>Invoking the {@link #reset} method will set the scanner's radix to * {@code 10}. *
*** 1251,1260 **** --- 1266,1276 ---- if ((radix < Character.MIN_RADIX) || (radix > Character.MAX_RADIX)) throw new IllegalArgumentException("radix:"+radix); if (this.defaultRadix == radix) return this; + modCount++; this.defaultRadix = radix; // Force rebuilding and recompilation of radix dependent patterns integerPattern = null; return this; }
*** 1273,1291 **** * Returns the match result of the last scanning operation performed * by this scanner. This method throws {@code IllegalStateException} * if no match has been performed, or if the last match was * not successful. * ! * <p>The various {@code next}methods of {@code Scanner} * make a match result available if they complete without throwing an * exception. For instance, after an invocation of the {@link #nextInt} * method that returned an int, this method returns a * {@code MatchResult} for the search of the * <a href="#Integer-regex"><i>Integer</i></a> regular expression ! * defined above. Similarly the {@link #findInLine}, ! * {@link #findWithinHorizon}, and {@link #skip} methods will make a ! * match available if they succeed. * * @return a match result for the last match operation * @throws IllegalStateException If no match result is available */ public MatchResult match() { --- 1289,1307 ---- * Returns the match result of the last scanning operation performed * by this scanner. This method throws {@code IllegalStateException} * if no match has been performed, or if the last match was * not successful. * ! * <p>The various {@code next} methods of {@code Scanner} * make a match result available if they complete without throwing an * exception. For instance, after an invocation of the {@link #nextInt} * method that returned an int, this method returns a * {@code MatchResult} for the search of the * <a href="#Integer-regex"><i>Integer</i></a> regular expression ! * defined above. Similarly the {@link #findInLine findInLine()}, ! * {@link #findWithinHorizon findWithinHorizon()}, and {@link #skip skip()} ! * methods will make a match available if they succeed. * * @return a match result for the last match operation * @throws IllegalStateException If no match result is available */ public MatchResult match() {
*** 1331,1340 **** --- 1347,1357 ---- * @see java.util.Iterator */ public boolean hasNext() { ensureOpen(); saveState(); + modCount++; while (!sourceClosed) { if (hasTokenInBuffer()) return revertState(true); readInput(); }
*** 1355,1364 **** --- 1372,1382 ---- * @see java.util.Iterator */ public String next() { ensureOpen(); clearCaches(); + modCount++; while (true) { String token = getCompleteTokenInBuffer(null); if (token != null) { matchValid = true;
*** 1433,1442 **** --- 1451,1461 ---- ensureOpen(); if (pattern == null) throw new NullPointerException(); hasNextPattern = null; saveState(); + modCount++; while (true) { if (getCompleteTokenInBuffer(pattern) != null) { matchValid = true; cacheResult();
*** 1464,1473 **** --- 1483,1493 ---- public String next(Pattern pattern) { ensureOpen(); if (pattern == null) throw new NullPointerException(); + modCount++; // Did we already find this pattern? if (hasNextPattern == pattern) return getCachedResult(); clearCaches();
*** 1495,1504 **** --- 1515,1525 ---- * @throws IllegalStateException if this scanner is closed */ public boolean hasNextLine() { saveState(); + modCount++; String result = findWithinHorizon(linePattern(), 0); if (result != null) { MatchResult mr = this.match(); String lineSep = mr.group(1); if (lineSep != null) {
*** 1529,1538 **** --- 1550,1560 ---- * @return the line that was skipped * @throws NoSuchElementException if no line was found * @throws IllegalStateException if this scanner is closed */ public String nextLine() { + modCount++; if (hasNextPattern == linePattern()) return getCachedResult(); clearCaches(); String result = findWithinHorizon(linePattern, 0);
*** 1587,1602 **** public String findInLine(Pattern pattern) { ensureOpen(); if (pattern == null) throw new NullPointerException(); clearCaches(); // Expand buffer to include the next newline or end of input int endPosition = 0; saveState(); while (true) { ! String token = findPatternInBuffer(separatorPattern(), 0); ! if (token != null) { endPosition = matcher.start(); break; // up to next newline } if (needInput) { readInput(); --- 1609,1624 ---- public String findInLine(Pattern pattern) { ensureOpen(); if (pattern == null) throw new NullPointerException(); clearCaches(); + modCount++; // Expand buffer to include the next newline or end of input int endPosition = 0; saveState(); while (true) { ! if (findPatternInBuffer(separatorPattern(), 0)) { endPosition = matcher.start(); break; // up to next newline } if (needInput) { readInput();
*** 1621,1631 **** * specified string, ignoring delimiters. * * <p>An invocation of this method of the form * {@code findWithinHorizon(pattern)} behaves in exactly the same way as * the invocation ! * {@code findWithinHorizon(Pattern.compile(pattern, horizon))}. * * @param pattern a string specifying the pattern to search for * @param horizon the search horizon * @return the text that matched the specified pattern * @throws IllegalStateException if this scanner is closed --- 1643,1653 ---- * specified string, ignoring delimiters. * * <p>An invocation of this method of the form * {@code findWithinHorizon(pattern)} behaves in exactly the same way as * the invocation ! * {@code findWithinHorizon(Pattern.compile(pattern), horizon)}. * * @param pattern a string specifying the pattern to search for * @param horizon the search horizon * @return the text that matched the specified pattern * @throws IllegalStateException if this scanner is closed
*** 1671,1687 **** if (pattern == null) throw new NullPointerException(); if (horizon < 0) throw new IllegalArgumentException("horizon < 0"); clearCaches(); // Search for the pattern while (true) { ! String token = findPatternInBuffer(pattern, horizon); ! if (token != null) { matchValid = true; ! return token; } if (needInput) readInput(); else break; // up to end of input --- 1693,1709 ---- if (pattern == null) throw new NullPointerException(); if (horizon < 0) throw new IllegalArgumentException("horizon < 0"); clearCaches(); + modCount++; // Search for the pattern while (true) { ! if (findPatternInBuffer(pattern, horizon)) { matchValid = true; ! return matcher.group(); } if (needInput) readInput(); else break; // up to end of input
*** 1715,1729 **** public Scanner skip(Pattern pattern) { ensureOpen(); if (pattern == null) throw new NullPointerException(); clearCaches(); // Search for the pattern while (true) { ! String token = matchPatternInBuffer(pattern); ! if (token != null) { matchValid = true; position = matcher.end(); return this; } if (needInput) --- 1737,1751 ---- public Scanner skip(Pattern pattern) { ensureOpen(); if (pattern == null) throw new NullPointerException(); clearCaches(); + modCount++; // Search for the pattern while (true) { ! if (matchPatternInBuffer(pattern)) { matchValid = true; position = matcher.end(); return this; } if (needInput)
*** 1930,1940 **** /** * Scans the next token of the input as a {@code short}. * * <p> An invocation of this method of the form * {@code nextShort()} behaves in exactly the same way as the ! * invocation {@code nextShort(radix)}, where {@code radix} * is the default radix of this scanner. * * @return the {@code short} scanned from the input * @throws InputMismatchException * if the next token does not match the <i>Integer</i> --- 1952,1962 ---- /** * Scans the next token of the input as a {@code short}. * * <p> An invocation of this method of the form * {@code nextShort()} behaves in exactly the same way as the ! * invocation {@link #nextShort(int) nextShort(radix)}, where {@code radix} * is the default radix of this scanner. * * @return the {@code short} scanned from the input * @throws InputMismatchException * if the next token does not match the <i>Integer</i>
*** 2588,2599 **** /** * Resets this scanner. * * <p> Resetting a scanner discards all of its explicit state ! * information which may have been changed by invocations of {@link ! * #useDelimiter}, {@link #useLocale}, or {@link #useRadix}. * * <p> An invocation of this method of the form * {@code scanner.reset()} behaves in exactly the same way as the * invocation * --- 2610,2623 ---- /** * Resets this scanner. * * <p> Resetting a scanner discards all of its explicit state ! * information which may have been changed by invocations of ! * {@link #useDelimiter useDelimiter()}, ! * {@link #useLocale useLocale()}, or ! * {@link #useRadix useRadix()}. * * <p> An invocation of this method of the form * {@code scanner.reset()} behaves in exactly the same way as the * invocation *
*** 2610,2617 **** --- 2634,2841 ---- public Scanner reset() { delimPattern = WHITESPACE_PATTERN; useLocale(Locale.getDefault(Locale.Category.FORMAT)); useRadix(10); clearCaches(); + modCount++; return this; } + + /** + * Returns a stream of delimiter-separated tokens from this scanner. The + * stream contains the same tokens that would be returned, starting from + * this scanner's current state, by calling the {@link #next} method + * repeatedly until the {@link #hasNext} returns false. + * + * <p>The resulting stream is sequential and ordered. All stream elements are + * non-null. + * + * <p>Scanning starts upon initiation of the terminal stream operation, using the + * current state of this scanner. Subsequent calls to any methods on this scanner + * other than {@link #close} and {@link #ioException} may return undefined results + * or may cause undefined effects on the returned stream. The returned stream's source + * {@code Spliterator} is <em>fail-fast</em> and will, on a best-effort basis, throw a + * {@link java.util.ConcurrentModificationException} if any such calls are detected + * during pipeline execution. + * + * <p>After pipeline execution completes, this scanner is left in an indeterminate + * state and cannot be reused. + * + * <p>If this scanner contains a resource that must be released, this scanner + * should be closed, either by calling its {@link #close} method, or by + * closing the returned stream. Closing the stream will close the underlying scanner. + * {@code IllegalStateException} is thrown if the scanner has been closed when this + * method is called, or if this scanner is closed during pipeline execution. + * + * <p>This method might block waiting for more input. + * + * @apiNote + * For example, the following code will create a list of + * comma-delimited tokens from a string: + * + * <pre>{@code + * List<String> result = new Scanner("abc,def,,ghi") + * .useDelimiter(",") + * .tokens() + * .collect(Collectors.toList()); + * }</pre> + * + * <p>The resulting list would contain {@code "abc"}, {@code "def"}, + * the empty string, and {@code "ghi"}. + * + * @return a sequential stream of token strings + * @throws IllegalStateException if this scanner is closed + * @since 1.9 + */ + public Stream<String> tokens() { + ensureOpen(); + Stream<String> stream = StreamSupport.stream(new TokenSpliterator(), false); + return stream.onClose(this::close); + } + + class TokenSpliterator extends Spliterators.AbstractSpliterator<String> { + int expectedCount = -1; + + TokenSpliterator() { + super(Long.MAX_VALUE, + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED); + } + + @Override + public boolean tryAdvance(Consumer<? super String> cons) { + if (expectedCount >= 0 && expectedCount != modCount) { + throw new ConcurrentModificationException(); + } + + if (hasNext()) { + String token = next(); + expectedCount = modCount; + cons.accept(token); + if (expectedCount != modCount) { + throw new ConcurrentModificationException(); + } + return true; + } else { + expectedCount = modCount; + return false; + } + } + } + + /** + * Returns a stream of match results from this scanner. The stream + * contains the same results in the same order that would be returned by + * calling {@code findWithinHorizon(pattern, 0)} and then {@link #match} + * successively as long as {@link #findWithinHorizon findWithinHorizon()} + * finds matches. + * + * <p>The resulting stream is sequential and ordered. All stream elements are + * non-null. + * + * <p>Scanning starts upon initiation of the terminal stream operation, using the + * current state of this scanner. Subsequent calls to any methods on this scanner + * other than {@link #close} and {@link #ioException} may return undefined results + * or may cause undefined effects on the returned stream. The returned stream's source + * {@code Spliterator} is <em>fail-fast</em> and will, on a best-effort basis, throw a + * {@link java.util.ConcurrentModificationException} if any such calls are detected + * during pipeline execution. + * + * <p>After pipeline execution completes, this scanner is left in an indeterminate + * state and cannot be reused. + * + * <p>If this scanner contains a resource that must be released, this scanner + * should be closed, either by calling its {@link #close} method, or by + * closing the returned stream. Closing the stream will close the underlying scanner. + * {@code IllegalStateException} is thrown if the scanner has been closed when this + * method is called, or if this scanner is closed during pipeline execution. + * + * <p>As with the {@link #findWithinHorizon findWithinHorizon()} methods, this method + * might block waiting for additional input, and it might buffer an unbounded amount of + * input searching for a match. + * + * @apiNote + * For example, the following code will read a file and return a list + * of all sequences of characters consisting of seven or more Latin capital + * letters: + * + * <pre>{@code + * try (Scanner sc = new Scanner(Paths.get("input.txt"))) { + * Pattern pat = Pattern.compile("[A-Z]{7,}"); + * List<String> capWords = sc.findAll(pat) + * .map(MatchResult::group) + * .collect(Collectors.toList()); + * } + * }</pre> + * + * @param pattern the pattern to be matched + * @return a sequential stream of match results + * @throws NullPointerException if pattern is null + * @throws IllegalStateException if this scanner is closed + * @since 1.9 + */ + public Stream<MatchResult> findAll(Pattern pattern) { + Objects.requireNonNull(pattern); + ensureOpen(); + Stream<MatchResult> stream = StreamSupport.stream(new FindSpliterator(pattern), false); + return stream.onClose(this::close); + } + + /** + * Returns a stream of match results that match the provided pattern string. + * The effect is equivalent to the following code: + * + * <pre>{@code + * scanner.findAll(Pattern.compile(patString)) + * }</pre> + * + * @param patString the pattern string + * @return a sequential stream of match results + * @throws NullPointerException if patString is null + * @throws IllegalStateException if this scanner is closed + * @throws PatternSyntaxException if the regular expression's syntax is invalid + * @since 1.9 + * @see java.util.regex.Pattern + */ + public Stream<MatchResult> findAll(String patString) { + Objects.requireNonNull(patString); + ensureOpen(); + return findAll(patternCache.forName(patString)); + } + + class FindSpliterator extends Spliterators.AbstractSpliterator<MatchResult> { + final Pattern pattern; + int expectedCount = -1; + + FindSpliterator(Pattern pattern) { + super(Long.MAX_VALUE, + Spliterator.IMMUTABLE | Spliterator.NONNULL | Spliterator.ORDERED); + this.pattern = pattern; + } + + @Override + public boolean tryAdvance(Consumer<? super MatchResult> cons) { + ensureOpen(); + if (expectedCount >= 0) { + if (expectedCount != modCount) { + throw new ConcurrentModificationException(); + } + } else { + expectedCount = modCount; + } + + while (true) { + // assert expectedCount == modCount + if (findPatternInBuffer(pattern, 0)) { // doesn't increment modCount + cons.accept(matcher.toMatchResult()); + if (expectedCount != modCount) { + throw new ConcurrentModificationException(); + } + return true; + } + if (needInput) + readInput(); // doesn't increment modCount + else + return false; // reached end of input + } + } + } }
< prev index next >