1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.commons.text.io;
19
20 import java.io.FilterReader;
21 import java.io.IOException;
22 import java.io.Reader;
23
24 import org.apache.commons.text.StringSubstitutor;
25 import org.apache.commons.text.TextStringBuilder;
26 import org.apache.commons.text.matcher.StringMatcher;
27 import org.apache.commons.text.matcher.StringMatcherFactory;
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42 public class StringSubstitutorReader extends FilterReader {
43
44
45 private static final int EOS = -1;
46
47
48 private final TextStringBuilder buffer = new TextStringBuilder();
49
50
51 private boolean eos;
52
53
54 private final StringMatcher prefixEscapeMatcher;
55
56
57 private final char[] read1CharBuffer = {0};
58
59
60 private final StringSubstitutor stringSubstitutor;
61
62
63 private int toDrain;
64
65
66
67
68
69
70
71
72
73 public StringSubstitutorReader(final Reader reader, final StringSubstitutor stringSubstitutor) {
74 super(reader);
75 this.stringSubstitutor = new StringSubstitutor(stringSubstitutor);
76 this.prefixEscapeMatcher = StringMatcherFactory.INSTANCE.charMatcher(stringSubstitutor.getEscapeChar())
77 .andThen(stringSubstitutor.getVariablePrefixMatcher());
78 }
79
80
81
82
83 private int buffer(final int requestReadCount) throws IOException {
84 final int actualReadCount = buffer.readFrom(super.in, requestReadCount);
85 eos = actualReadCount == EOS;
86 return actualReadCount;
87 }
88
89
90
91
92
93 private int bufferOrDrainOnEos(final int requestReadCount, final char[] target, final int targetIndex,
94 final int targetLength) throws IOException {
95 final int actualReadCount = buffer(requestReadCount);
96 return drainOnEos(actualReadCount, target, targetIndex, targetLength);
97 }
98
99
100
101
102 private int drain(final char[] target, final int targetIndex, final int targetLength) {
103 final int actualLen = Math.min(buffer.length(), targetLength);
104 final int drainCount = buffer.drainChars(0, actualLen, target, targetIndex);
105 toDrain -= drainCount;
106 if (buffer.isEmpty() || toDrain == 0) {
107
108 toDrain = 0;
109 }
110 return drainCount;
111 }
112
113
114
115
116
117 private int drainOnEos(final int readCountOrEos, final char[] target, final int targetIndex,
118 final int targetLength) {
119 if (readCountOrEos == EOS) {
120
121 if (buffer.isNotEmpty()) {
122 toDrain = buffer.size();
123 return drain(target, targetIndex, targetLength);
124 }
125 return EOS;
126 }
127 return readCountOrEos;
128 }
129
130
131
132
133 private boolean isBufferMatchAt(final StringMatcher stringMatcher, final int pos) {
134 return stringMatcher.isMatch(buffer, pos) == stringMatcher.size();
135 }
136
137
138
139
140 private boolean isDraining() {
141 return toDrain > 0;
142 }
143
144
145
146
147
148
149
150 @Override
151 public int read() throws IOException {
152 int count = 0;
153
154 do {
155 count = read(read1CharBuffer, 0, 1);
156 if (count == EOS) {
157 return EOS;
158 }
159
160 } while (count < 1);
161 return read1CharBuffer[0];
162 }
163
164
165
166
167
168
169
170
171
172
173 @Override
174 public int read(final char[] target, final int targetIndexIn, final int targetLengthIn) throws IOException {
175
176
177
178
179
180
181 if (eos && buffer.isEmpty()) {
182 return EOS;
183 }
184 if (targetLengthIn <= 0) {
185
186 return 0;
187 }
188
189 int targetIndex = targetIndexIn;
190 int targetLength = targetLengthIn;
191 if (isDraining()) {
192
193 final int drainCount = drain(target, targetIndex, Math.min(toDrain, targetLength));
194 if (drainCount == targetLength) {
195
196 return targetLength;
197 }
198
199 targetIndex += drainCount;
200 targetLength -= drainCount;
201 }
202
203 final int minReadLenPrefix = prefixEscapeMatcher.size();
204
205 int readCount = buffer(readCount(minReadLenPrefix, 0));
206 if (buffer.length() < minReadLenPrefix && targetLength < minReadLenPrefix) {
207
208 final int drainCount = drain(target, targetIndex, targetLength);
209 targetIndex += drainCount;
210 final int targetSize = targetIndex - targetIndexIn;
211 return eos && targetSize <= 0 ? EOS : targetSize;
212 }
213 if (eos) {
214
215 stringSubstitutor.replaceIn(buffer);
216 toDrain = buffer.size();
217 final int drainCount = drain(target, targetIndex, targetLength);
218 targetIndex += drainCount;
219 final int targetSize = targetIndex - targetIndexIn;
220 return eos && targetSize <= 0 ? EOS : targetSize;
221 }
222
223
224 int balance = 0;
225 final StringMatcher prefixMatcher = stringSubstitutor.getVariablePrefixMatcher();
226 int pos = 0;
227 while (targetLength > 0) {
228 if (isBufferMatchAt(prefixMatcher, 0)) {
229 balance = 1;
230 pos = prefixMatcher.size();
231 break;
232 }
233 if (isBufferMatchAt(prefixEscapeMatcher, 0)) {
234 balance = 1;
235 pos = prefixEscapeMatcher.size();
236 break;
237 }
238
239 final int drainCount = drain(target, targetIndex, 1);
240 targetIndex += drainCount;
241 targetLength -= drainCount;
242 if (buffer.size() < minReadLenPrefix) {
243 readCount = bufferOrDrainOnEos(minReadLenPrefix, target, targetIndex, targetLength);
244 if (eos || isDraining()) {
245
246 if (readCount != EOS) {
247 targetIndex += readCount;
248 targetLength -= readCount;
249 }
250 final int actual = targetIndex - targetIndexIn;
251 return actual > 0 ? actual : EOS;
252 }
253 }
254 }
255
256 if (targetLength <= 0) {
257
258 return targetLengthIn;
259 }
260
261
262 final StringMatcher suffixMatcher = stringSubstitutor.getVariableSuffixMatcher();
263 final int minReadLenSuffix = Math.max(minReadLenPrefix, suffixMatcher.size());
264 readCount = buffer(readCount(minReadLenSuffix, pos));
265 if (eos) {
266
267 stringSubstitutor.replaceIn(buffer);
268 toDrain = buffer.size();
269 final int drainCount = drain(target, targetIndex, targetLength);
270 return targetIndex + drainCount - targetIndexIn;
271 }
272
273 while (true) {
274 if (isBufferMatchAt(suffixMatcher, pos)) {
275 balance--;
276 pos++;
277 if (balance == 0) {
278 break;
279 }
280 } else if (isBufferMatchAt(prefixMatcher, pos)) {
281 balance++;
282 pos += prefixMatcher.size();
283 } else if (isBufferMatchAt(prefixEscapeMatcher, pos)) {
284 balance++;
285 pos += prefixEscapeMatcher.size();
286 } else {
287 pos++;
288 }
289 readCount = buffer(readCount(minReadLenSuffix, pos));
290 if (readCount == EOS && pos >= buffer.size()) {
291 break;
292 }
293 }
294
295 final int endPos = pos + 1;
296 final int leftover = Math.max(0, buffer.size() - pos);
297 stringSubstitutor.replaceIn(buffer, 0, Math.min(buffer.size(), endPos));
298 pos = buffer.size() - leftover;
299 final int drainLen = Math.min(targetLength, pos);
300
301 toDrain = pos;
302 drain(target, targetIndex, drainLen);
303 return targetIndex - targetIndexIn + drainLen;
304 }
305
306
307
308
309
310 private int readCount(final int count, final int pos) {
311 final int avail = buffer.size() - pos;
312 return avail >= count ? 0 : count - avail;
313 }
314
315 }