1 /* 2 * Copyright (C) 2013 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 5 * in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the License 10 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 11 * or implied. See the License for the specific language governing permissions and limitations under 12 * the License. 13 */ 14 15 package com.google.common.io; 16 17 import static com.google.common.base.Preconditions.checkArgument; 18 import static com.google.common.base.Preconditions.checkNotNull; 19 import static com.google.common.base.Preconditions.checkPositionIndexes; 20 import static java.util.Objects.requireNonNull; 21 22 import com.google.common.annotations.GwtIncompatible; 23 import com.google.common.annotations.J2ktIncompatible; 24 import java.io.IOException; 25 import java.io.Reader; 26 import java.nio.CharBuffer; 27 import javax.annotation.CheckForNull; 28 29 /** 30 * A {@link Reader} that reads the characters in a {@link CharSequence}. Like {@code StringReader}, 31 * but works with any {@link CharSequence}. 32 * 33 * @author Colin Decker 34 */ 35 // TODO(cgdecker): make this public? as a type, or a method in CharStreams? 36 @J2ktIncompatible 37 @GwtIncompatible 38 @ElementTypesAreNonnullByDefault 39 final class CharSequenceReader extends Reader { 40 41 @CheckForNull private CharSequence seq; 42 private int pos; 43 private int mark; 44 45 /** Creates a new reader wrapping the given character sequence. */ CharSequenceReader(CharSequence seq)46 public CharSequenceReader(CharSequence seq) { 47 this.seq = checkNotNull(seq); 48 } 49 checkOpen()50 private void checkOpen() throws IOException { 51 if (seq == null) { 52 throw new IOException("reader closed"); 53 } 54 } 55 hasRemaining()56 private boolean hasRemaining() { 57 return remaining() > 0; 58 } 59 remaining()60 private int remaining() { 61 requireNonNull(seq); // safe as long as we call this only after checkOpen 62 return seq.length() - pos; 63 } 64 65 /* 66 * To avoid the need to call requireNonNull so much, we could consider more clever approaches, 67 * such as: 68 * 69 * - Make checkOpen return the non-null `seq`. Then callers can assign that to a local variable or 70 * even back to `this.seq`. However, that may suggest that we're defending against concurrent 71 * mutation, which is not an actual risk because we use `synchronized`. 72 * - Make `remaining` require a non-null `seq` argument. But this is a bit weird because the 73 * method, while it would avoid the instance field `seq` would still access the instance field 74 * `pos`. 75 */ 76 77 @Override read(CharBuffer target)78 public synchronized int read(CharBuffer target) throws IOException { 79 checkNotNull(target); 80 checkOpen(); 81 requireNonNull(seq); // safe because of checkOpen 82 if (!hasRemaining()) { 83 return -1; 84 } 85 int charsToRead = Math.min(target.remaining(), remaining()); 86 for (int i = 0; i < charsToRead; i++) { 87 target.put(seq.charAt(pos++)); 88 } 89 return charsToRead; 90 } 91 92 @Override read()93 public synchronized int read() throws IOException { 94 checkOpen(); 95 requireNonNull(seq); // safe because of checkOpen 96 return hasRemaining() ? seq.charAt(pos++) : -1; 97 } 98 99 @Override read(char[] cbuf, int off, int len)100 public synchronized int read(char[] cbuf, int off, int len) throws IOException { 101 checkPositionIndexes(off, off + len, cbuf.length); 102 checkOpen(); 103 requireNonNull(seq); // safe because of checkOpen 104 if (!hasRemaining()) { 105 return -1; 106 } 107 int charsToRead = Math.min(len, remaining()); 108 for (int i = 0; i < charsToRead; i++) { 109 cbuf[off + i] = seq.charAt(pos++); 110 } 111 return charsToRead; 112 } 113 114 @Override skip(long n)115 public synchronized long skip(long n) throws IOException { 116 checkArgument(n >= 0, "n (%s) may not be negative", n); 117 checkOpen(); 118 int charsToSkip = (int) Math.min(remaining(), n); // safe because remaining is an int 119 pos += charsToSkip; 120 return charsToSkip; 121 } 122 123 @Override ready()124 public synchronized boolean ready() throws IOException { 125 checkOpen(); 126 return true; 127 } 128 129 @Override markSupported()130 public boolean markSupported() { 131 return true; 132 } 133 134 @Override mark(int readAheadLimit)135 public synchronized void mark(int readAheadLimit) throws IOException { 136 checkArgument(readAheadLimit >= 0, "readAheadLimit (%s) may not be negative", readAheadLimit); 137 checkOpen(); 138 mark = pos; 139 } 140 141 @Override reset()142 public synchronized void reset() throws IOException { 143 checkOpen(); 144 pos = mark; 145 } 146 147 @Override close()148 public synchronized void close() throws IOException { 149 seq = null; 150 } 151 } 152