blob: a8b1c350c2715fec6a8c5dc18c1760e9dc5a14ef [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.util;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.nio.CharBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.UnsupportedCharsetException;
import java.util.InputMismatchException;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public final class Scanner implements Iterator<String>, Closeable {
private static final Map<String, Pattern> CACHE = new LinkedHashMap<String, Pattern>() {
@Override
protected boolean removeEldestEntry(Entry<String, Pattern> eldest) {
return size() >= 7;
}
};
private static final String WHITESPACE_PATTERN = "\\s+";
private static final String FIND_ANY_PATTERN = "(?s).*";
private static final int BUFFER_SIZE = 1024;
private Readable source;
private Pattern delimPattern;
private Matcher matcher;
private CharBuffer buf;
private int position;
private boolean inputExhausted;
private boolean needInput;
private boolean skipped;
private int savedPosition = -1;
private boolean closed;
private IOException lastIOException;
public Scanner(InputStream source, String charsetName, String pattern) {
this(new InputStreamReader(Objects.requireNonNull(source, "source"), toDecoder(charsetName)), cachePattern(pattern));
}
public Scanner(File source, String charsetName, String pattern) throws FileNotFoundException {
this(new FileInputStream(Objects.requireNonNull(source, "source")).getChannel(), charsetName, pattern);
}
public Scanner(String source, String pattern) {
this(new StringReader(Objects.requireNonNull(source, "source")), cachePattern(pattern));
}
public Scanner(ReadableByteChannel source, String charsetName, String pattern) {
this(Channels.newReader(Objects.requireNonNull(source, "source"), toDecoder(charsetName), -1), cachePattern(pattern));
}
public Scanner(Readable source, String pattern) {
this(Objects.requireNonNull(source, "source"), cachePattern(pattern));
}
private Scanner(Readable source, Pattern pattern) {
this.source = source;
delimPattern = pattern != null ? pattern : cachePattern(WHITESPACE_PATTERN);
buf = CharBuffer.allocate(BUFFER_SIZE);
buf.limit(0);
matcher = delimPattern.matcher(buf);
matcher.useTransparentBounds(true);
matcher.useAnchoringBounds(false);
}
private static CharsetDecoder toDecoder(String charsetName) {
try {
Charset cs = charsetName != null ? Charset.forName(charsetName) : Charset.defaultCharset();
return cs.newDecoder();
} catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
throw new IllegalArgumentException(e);
}
}
public boolean hasNext() {
checkClosed();
saveState();
while (!inputExhausted) {
if (hasTokenInBuffer()) {
revertState();
return true;
}
readMore();
}
boolean result = hasTokenInBuffer();
revertState();
return result;
}
public String next() {
checkClosed();
while (true) {
String token = getCompleteTokenInBuffer();
if (token != null) {
skipped = false;
return token;
}
if (needInput) {
readMore();
} else {
throwFor();
}
}
}
private void saveState() {
savedPosition = position;
}
private void revertState() {
position = savedPosition;
savedPosition = -1;
skipped = false;
}
private void readMore() {
if (buf.limit() == buf.capacity()) {
expandBuffer();
}
int p = buf.position();
buf.position(buf.limit());
buf.limit(buf.capacity());
int n;
try {
n = source.read(buf);
} catch (IOException ioe) {
lastIOException = ioe;
n = -1;
}
if (n == -1) {
inputExhausted = true;
needInput = false;
} else if (n > 0) {
needInput = false;
}
buf.limit(buf.position());
buf.position(p);
}
private void expandBuffer() {
int offset = savedPosition == -1 ? position : savedPosition;
buf.position(offset);
if (offset > 0) {
buf.compact();
translateSavedIndexes(offset);
position -= offset;
buf.flip();
} else {
int newSize = buf.capacity() * 2;
CharBuffer newBuf = CharBuffer.allocate(newSize);
newBuf.put(buf);
newBuf.flip();
translateSavedIndexes(offset);
position -= offset;
buf = newBuf;
matcher.reset(buf);
}
}
private void translateSavedIndexes(int offset) {
if (savedPosition != -1) {
savedPosition -= offset;
}
}
private void throwFor() {
skipped = false;
if (inputExhausted && position == buf.limit()) {
throw new NoSuchElementException();
} else {
throw new InputMismatchException();
}
}
private boolean hasTokenInBuffer() {
matcher.usePattern(delimPattern);
matcher.region(position, buf.limit());
if (matcher.lookingAt()) {
position = matcher.end();
}
return position != buf.limit();
}
private String getCompleteTokenInBuffer() {
matcher.usePattern(delimPattern);
if (!skipped) {
matcher.region(position, buf.limit());
if (matcher.lookingAt()) {
if (matcher.hitEnd() && !inputExhausted) {
needInput = true;
return null;
}
skipped = true;
position = matcher.end();
}
}
if (position == buf.limit()) {
if (inputExhausted) {
return null;
}
needInput = true;
return null;
}
matcher.region(position, buf.limit());
boolean foundNextDelim = matcher.find();
if (foundNextDelim && (matcher.end() == position)) {
foundNextDelim = matcher.find();
}
if (foundNextDelim) {
if (matcher.requireEnd() && !inputExhausted) {
needInput = true;
return null;
}
int tokenEnd = matcher.start();
matcher.usePattern(cachePattern(FIND_ANY_PATTERN));
matcher.region(position, tokenEnd);
if (matcher.matches()) {
String s = matcher.group();
position = matcher.end();
return s;
} else {
return null;
}
}
if (inputExhausted) {
matcher.usePattern(cachePattern(FIND_ANY_PATTERN));
matcher.region(position, buf.limit());
if (matcher.matches()) {
String s = matcher.group();
position = matcher.end();
return s;
}
return null;
}
needInput = true;
return null;
}
private void checkClosed() {
if (closed) {
throw new IllegalStateException();
}
}
public void close() throws IOException {
if (!closed) {
closed = true;
if (source instanceof Closeable) {
try {
((Closeable) source).close();
} catch (IOException e) {
lastIOException = e;
}
}
}
if (lastIOException != null) {
throw lastIOException;
}
}
private static Pattern cachePattern(String pattern) {
if (pattern == null) {
return null;
}
return CACHE.computeIfAbsent(pattern, Pattern::compile);
}
}