• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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