com.google.common.io.CharSequenceReader Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guava Show documentation
Show all versions of guava Show documentation
Guava library, with additional type annotations for verification
The newest version!
/*
* Copyright (C) 2013 The Guava Authors
*
* Licensed 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 com.google.common.io;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkPositionIndexes;
import static java.util.Objects.requireNonNull;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import java.io.IOException;
import java.io.Reader;
import java.nio.CharBuffer;
import javax.annotation.CheckForNull;
import org.checkerframework.checker.index.qual.GTENegativeOne;
import org.checkerframework.checker.index.qual.IndexOrHigh;
import org.checkerframework.checker.index.qual.LTEqLengthOf;
import org.checkerframework.checker.index.qual.LTLengthOf;
import org.checkerframework.checker.index.qual.NonNegative;
/**
* A {@link Reader} that reads the characters in a {@link CharSequence}. Like {@code StringReader},
* but works with any {@link CharSequence}.
*
* @author Colin Decker
*/
// TODO(cgdecker): make this public? as a type, or a method in CharStreams?
@J2ktIncompatible
@GwtIncompatible
@ElementTypesAreNonnullByDefault
final class CharSequenceReader extends Reader {
@CheckForNull private CharSequence seq;
private int pos;
private int mark;
/** Creates a new reader wrapping the given character sequence. */
public CharSequenceReader(CharSequence seq) {
this.seq = checkNotNull(seq);
}
private void checkOpen() throws IOException {
if (seq == null) {
throw new IOException("reader closed");
}
}
private boolean hasRemaining() {
return remaining() > 0;
}
@SuppressWarnings("index:return") // The method is private and every place it is used returns a non-negative value.
private @NonNegative int remaining() {
requireNonNull(seq); // safe as long as we call this only after checkOpen
return seq.length() - pos;
}
/*
* To avoid the need to call requireNonNull so much, we could consider more clever approaches,
* such as:
*
* - Make checkOpen return the non-null `seq`. Then callers can assign that to a local variable or
* even back to `this.seq`. However, that may suggest that we're defending against concurrent
* mutation, which is not an actual risk because we use `synchronized`.
* - Make `remaining` require a non-null `seq` argument. But this is a bit weird because the
* method, while it would avoid the instance field `seq` would still access the instance field
* `pos`.
*/
@Override
@SuppressWarnings("index:argument") // pos is a valid index for seq because the loop stops at charsToRead steps, which cannot exceed the limit of target or seq.
public synchronized @GTENegativeOne int read(CharBuffer target) throws IOException {
checkNotNull(target);
checkOpen();
requireNonNull(seq); // safe because of checkOpen
if (!hasRemaining()) {
return -1;
}
int charsToRead = Math.min(target.remaining(), remaining());
for (int i = 0; i < charsToRead; i++) {
target.put(seq.charAt(pos++));
}
return charsToRead;
}
@Override
@SuppressWarnings({"index:return", "index:argument"}) /* charAt returns a char, which is known to be non-negative Ascii.
pos is a valid index for seq because hasRemaining() would otherwise return false */
public synchronized @GTENegativeOne int read() throws IOException {
checkOpen();
requireNonNull(seq); // safe because of checkOpen
return hasRemaining() ? seq.charAt(pos++) : -1;
}
@Override
@SuppressWarnings({"index:argument", "index:return"}) /*
#1. pos is a valid index for seq because the loop stops at charsToRead steps, which cannot exceed the limit of seq.
#2. charsToRead is at most equal to len, which is known to be below the length of cbuf */
public synchronized @GTENegativeOne @LTEqLengthOf("#1") int read(char[] cbuf, @IndexOrHigh("#1") int off, @NonNegative @LTLengthOf(value = "#1", offset = "#2 - 1") int len) throws IOException {
checkPositionIndexes(off, off + len, cbuf.length);
checkOpen();
requireNonNull(seq); // safe because of checkOpen
if (!hasRemaining()) {
return -1;
}
int charsToRead = Math.min(len, remaining());
for (int i = 0; i < charsToRead; i++) {
cbuf[off + i] = seq.charAt(pos++); // #1
}
return charsToRead; // #2
}
@Override
public synchronized @NonNegative long skip(@NonNegative long n) throws IOException {
checkArgument(n >= 0, "n (%s) may not be negative", n);
checkOpen();
int charsToSkip = (int) Math.min(remaining(), n); // safe because remaining is an int
pos += charsToSkip;
return charsToSkip;
}
@Override
public synchronized boolean ready() throws IOException {
checkOpen();
return true;
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void mark(@NonNegative int readAheadLimit) throws IOException {
checkArgument(readAheadLimit >= 0, "readAheadLimit (%s) may not be negative", readAheadLimit);
checkOpen();
mark = pos;
}
@Override
public synchronized void reset() throws IOException {
checkOpen();
pos = mark;
}
@Override
public synchronized void close() throws IOException {
seq = null;
}
}