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
174 @Override
175 public int read(final char[] target, final int targetIndexIn, final int targetLengthIn) throws IOException {
176
177
178
179
180
181
182 if (eos && buffer.isEmpty()) {
183 return EOS;
184 }
185 if (targetLengthIn <= 0) {
186
187 return 0;
188 }
189
190 int targetIndex = targetIndexIn;
191 int targetLength = targetLengthIn;
192 if (isDraining()) {
193
194 final int drainCount = drain(target, targetIndex, Math.min(toDrain, targetLength));
195 if (drainCount == targetLength) {
196
197 return targetLength;
198 }
199
200 targetIndex += drainCount;
201 targetLength -= drainCount;
202 }
203
204 final int minReadLenPrefix = prefixEscapeMatcher.size();
205
206 int readCount = buffer(readCount(minReadLenPrefix, 0));
207 if (buffer.length() < minReadLenPrefix && targetLength < minReadLenPrefix) {
208
209 final int drainCount = drain(target, targetIndex, targetLength);
210 targetIndex += drainCount;
211 final int targetSize = targetIndex - targetIndexIn;
212 return eos && targetSize <= 0 ? EOS : targetSize;
213 }
214 if (eos) {
215
216 stringSubstitutor.replaceIn(buffer);
217 toDrain = buffer.size();
218 final int drainCount = drain(target, targetIndex, targetLength);
219 targetIndex += drainCount;
220 final int targetSize = targetIndex - targetIndexIn;
221 return eos && targetSize <= 0 ? EOS : targetSize;
222 }
223
224
225 int balance = 0;
226 final StringMatcher prefixMatcher = stringSubstitutor.getVariablePrefixMatcher();
227 int pos = 0;
228 while (targetLength > 0) {
229 if (isBufferMatchAt(prefixMatcher, 0)) {
230 balance = 1;
231 pos = prefixMatcher.size();
232 break;
233 }
234 if (isBufferMatchAt(prefixEscapeMatcher, 0)) {
235 balance = 1;
236 pos = prefixEscapeMatcher.size();
237 break;
238 }
239
240 final int drainCount = drain(target, targetIndex, 1);
241 targetIndex += drainCount;
242 targetLength -= drainCount;
243 if (buffer.size() < minReadLenPrefix) {
244 readCount = bufferOrDrainOnEos(minReadLenPrefix, target, targetIndex, targetLength);
245 if (eos || isDraining()) {
246
247 if (readCount != EOS) {
248 targetIndex += readCount;
249 targetLength -= readCount;
250 }
251 final int actual = targetIndex - targetIndexIn;
252 return actual > 0 ? actual : EOS;
253 }
254 }
255 }
256
257 if (targetLength <= 0) {
258
259 return targetLengthIn;
260 }
261
262
263 final StringMatcher suffixMatcher = stringSubstitutor.getVariableSuffixMatcher();
264 final int minReadLenSuffix = Math.max(minReadLenPrefix, suffixMatcher.size());
265 readCount = buffer(readCount(minReadLenSuffix, pos));
266 if (eos) {
267
268 stringSubstitutor.replaceIn(buffer);
269 toDrain = buffer.size();
270 final int drainCount = drain(target, targetIndex, targetLength);
271 return targetIndex + drainCount - targetIndexIn;
272 }
273
274 while (true) {
275 if (isBufferMatchAt(suffixMatcher, pos)) {
276 balance--;
277 pos++;
278 if (balance == 0) {
279 break;
280 }
281 } else if (isBufferMatchAt(prefixMatcher, pos)) {
282 balance++;
283 pos += prefixMatcher.size();
284 } else if (isBufferMatchAt(prefixEscapeMatcher, pos)) {
285 balance++;
286 pos += prefixEscapeMatcher.size();
287 } else {
288 pos++;
289 }
290 readCount = buffer(readCount(minReadLenSuffix, pos));
291 if (readCount == EOS && pos >= buffer.size()) {
292 break;
293 }
294 }
295
296 final int endPos = pos + 1;
297 final int leftover = Math.max(0, buffer.size() - pos);
298 stringSubstitutor.replaceIn(buffer, 0, Math.min(buffer.size(), endPos));
299 pos = buffer.size() - leftover;
300 final int drainLen = Math.min(targetLength, pos);
301
302 toDrain = pos;
303 drain(target, targetIndex, drainLen);
304 return targetIndex - targetIndexIn + drainLen;
305 }
306
307
308
309
310
311 private int readCount(final int count, final int pos) {
312 final int avail = buffer.size() - pos;
313 return avail >= count ? 0 : count - avail;
314 }
315
316 }