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 static org.junit.jupiter.api.Assertions.assertArrayEquals;
21 import static org.junit.jupiter.api.Assertions.assertEquals;
22 import static org.junit.jupiter.api.Assertions.assertFalse;
23 import static org.junit.jupiter.api.Assertions.assertTrue;
24
25 import java.io.IOException;
26 import java.io.Reader;
27 import java.io.StringReader;
28 import java.io.StringWriter;
29 import java.util.Arrays;
30 import java.util.Objects;
31 import java.util.concurrent.atomic.AtomicInteger;
32
33 import org.apache.commons.io.IOUtils;
34 import org.apache.commons.io.input.NullReader;
35 import org.apache.commons.lang3.StringUtils;
36 import org.apache.commons.text.StringSubstitutor;
37 import org.apache.commons.text.StringSubstitutorTest;
38 import org.junit.jupiter.api.Test;
39
40
41
42
43 class StringSubstitutorFilterReaderTest extends StringSubstitutorTest {
44
45 private StringSubstitutorReader createReader(final StringSubstitutor substitutor, final String template) {
46 return new StringSubstitutorReader(new StringReader(template), substitutor);
47 }
48
49 @Override
50 protected void doTestNoReplace(final StringSubstitutor substitutor, final String replaceTemplate)
51 throws IOException {
52 super.doTestNoReplace(substitutor, replaceTemplate);
53 doTestNoReplaceInSteps(replaceTemplate, substitutor);
54 }
55
56 private void doTestNoReplaceInSteps(final String replaceTemplate, final StringSubstitutor substitutor)
57 throws IOException {
58 doTestReplaceInCharSteps(substitutor, replaceTemplate, replaceTemplate, false);
59 final int minTargetSize = 1;
60 int maxTargetSize = 1024 * 8;
61 for (int targetSize = minTargetSize; targetSize <= maxTargetSize; targetSize++) {
62 doTestReplaceInCharArraySteps(substitutor, replaceTemplate, replaceTemplate, false, targetSize);
63 }
64 maxTargetSize = 400;
65 for (int targetSize = minTargetSize; targetSize <= maxTargetSize; targetSize++) {
66 for (int targetIndex = 0; targetIndex < targetSize; targetIndex++) {
67 doTestReplaceInCharArrayAtSteps(substitutor, replaceTemplate, replaceTemplate, false, targetIndex,
68 targetSize);
69 }
70 }
71 }
72
73 @Override
74 protected void doTestReplace(final StringSubstitutor sub, final String expectedResult, final String replaceTemplate,
75 final boolean substring) throws IOException {
76 doTestReplaceInCharSteps(sub, expectedResult, replaceTemplate, substring);
77 super.doTestReplace(sub, expectedResult, replaceTemplate, substring);
78 }
79
80 private void doTestReplaceInCharArrayAtSteps(final StringSubstitutor substitutor, final String expectedResult,
81 final String replaceTemplate, final boolean substring, final int targetIndex, final int targetSize)
82 throws IOException {
83 final StringWriter actualResultWriter = new StringWriter();
84 final StringWriter expectedResultWriter = new StringWriter();
85 final AtomicInteger index = new AtomicInteger();
86 final int expectedResultLen = StringUtils.length(expectedResult);
87 try (Reader expectedResultReader = toReader(expectedResult);
88 Reader actualReader = new StringSubstitutorReader(toReader(replaceTemplate), substitutor)) {
89 final char[] actualCh = new char[targetSize];
90 final char[] expectedCh = new char[targetSize];
91 int actualCount;
92 while ((actualCount = actualReader.read(actualCh, targetIndex, targetSize - targetIndex)) != -1) {
93 final int expectedCount = expectedResultReader.read(expectedCh, targetIndex, targetSize - targetIndex);
94 if (expectedCount != -1) {
95 expectedResultWriter.write(expectedCh, targetIndex, expectedCount);
96 }
97
98 if (expectedCount == actualCount) {
99 assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
100 assertArrayEquals(expectedCh, actualCh,
101 () -> String.format("[%,d] '%s' != '%s', result so far: \"%s\"", index.get(),
102 String.valueOf(expectedCh), String.valueOf(actualCh), actualResultWriter.toString()));
103 } else if (actualCount < expectedCount) {
104 assertTrue(expectedResultWriter.toString().startsWith(actualResultWriter.toString()));
105 }
106 if (actualCount != -1) {
107 actualResultWriter.write(actualCh, targetIndex, actualCount);
108 } else {
109
110 assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
111 }
112 index.incrementAndGet();
113 assertFalse(index.get() > expectedResultLen, () -> "Index: " + index.get());
114
115 Arrays.fill(actualCh, (char) 0);
116 Arrays.fill(expectedCh, (char) 0);
117 }
118 }
119 assertEquals(Objects.toString(expectedResult, StringUtils.EMPTY), actualResultWriter.toString());
120 }
121
122 private void doTestReplaceInCharArraySteps(final StringSubstitutor substitutor, final String expectedResult,
123 final String replaceTemplate, final boolean substring, final int targetSize) throws IOException {
124 final StringWriter actualResultWriter = new StringWriter();
125 final StringWriter expectedResultWriter = new StringWriter();
126 final AtomicInteger index = new AtomicInteger();
127 final int expectedResultLen = StringUtils.length(expectedResult);
128 try (Reader expectedResultReader = toReader(expectedResult);
129 Reader actualReader = new StringSubstitutorReader(toReader(replaceTemplate), substitutor)) {
130 final char[] actualCh = new char[targetSize];
131 final char[] expectedCh = new char[targetSize];
132 int actualCount;
133 while ((actualCount = actualReader.read(actualCh)) != -1) {
134 final int expectedCount = expectedResultReader.read(expectedCh);
135 if (expectedCount != -1) {
136 expectedResultWriter.write(expectedCh, 0, expectedCount);
137 }
138
139 if (expectedCount == actualCount) {
140 assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
141 assertArrayEquals(expectedCh, actualCh,
142 () -> String.format("[%,d] '%s' != '%s', result so far: \"%s\"", index.get(),
143 String.valueOf(expectedCh), String.valueOf(actualCh), actualResultWriter.toString()));
144 } else if (actualCount < expectedCount) {
145 assertTrue(expectedResultWriter.toString().startsWith(actualResultWriter.toString()));
146 }
147 if (actualCount != -1) {
148 actualResultWriter.write(actualCh, 0, actualCount);
149 } else {
150
151 assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
152 }
153 index.incrementAndGet();
154 assertFalse(index.get() > expectedResultLen, () -> "Index: " + index.get());
155
156 Arrays.fill(actualCh, (char) 0);
157 Arrays.fill(expectedCh, (char) 0);
158 }
159 }
160 assertEquals(Objects.toString(expectedResult, StringUtils.EMPTY), actualResultWriter.toString());
161 }
162
163 private void doTestReplaceInCharSteps(final StringSubstitutor substitutor, final String expectedResult,
164 final String replaceTemplate, final boolean substring) throws IOException {
165 final StringWriter actualResultWriter = new StringWriter();
166 final AtomicInteger index = new AtomicInteger();
167 final int expectedResultLen = StringUtils.length(expectedResult);
168 try (Reader expectedResultReader = toReader(expectedResult);
169 Reader actualReader = new StringSubstitutorReader(toReader(replaceTemplate), substitutor)) {
170 int actualCh;
171 while ((actualCh = actualReader.read()) != -1) {
172 final int expectedCh = expectedResultReader.read();
173 final int actualCh2 = actualCh;
174 assertEquals(expectedCh, actualCh, () -> String.format("[%,d] '%s' != '%s', result so far: \"%s\"",
175 index.get(), toStringChar(expectedCh), toStringChar(actualCh2), actualResultWriter.toString()));
176 if (actualCh != -1) {
177 actualResultWriter.write(actualCh);
178 }
179 index.incrementAndGet();
180 assertFalse(index.get() > expectedResultLen, () -> "Index: " + index.get());
181 }
182 }
183 assertEquals(Objects.toString(expectedResult, StringUtils.EMPTY), actualResultWriter.toString());
184 }
185
186 private int getMinExpressionLength(final StringSubstitutor substitutor) {
187 return substitutor.getVariablePrefixMatcher().size() + 1 + substitutor.getVariableSuffixMatcher().size();
188 }
189
190 @Override
191 protected String replace(final StringSubstitutor substitutor, final String source) throws IOException {
192 if (source == null) {
193 return null;
194 }
195 try (Reader reader = createReader(substitutor, source)) {
196 return IOUtils.toString(reader);
197 }
198 }
199
200 @Test
201 void testReadMixedBufferLengths1ToVarLenPlusNoReplace() throws IOException {
202 final StringSubstitutor substitutor = new StringSubstitutor(values);
203 final String template = "123456";
204 assertTrue(template.length() > getMinExpressionLength(substitutor) + 1);
205 try (Reader reader = createReader(substitutor, template)) {
206 assertEquals('1', reader.read());
207 final char[] cbuf = new char[template.length() - 1];
208 reader.read(cbuf);
209 final String result = String.valueOf(cbuf);
210 assertEquals(template.substring(1), result);
211 }
212 }
213
214 @Test
215 void testReadMixedBufferLengthsReplace() throws IOException {
216 final String template = "${aa}${bb}";
217 final StringSubstitutor substitutor = new StringSubstitutor(values);
218 try (Reader reader = createReader(substitutor, template)) {
219 assertEquals('1', reader.read());
220 final char[] cbuf = new char[3];
221 assertEquals(0, reader.read(cbuf, 0, 0));
222 reader.read(cbuf);
223 final String result = String.valueOf(cbuf);
224 assertEquals("122", result, () -> String.format("length %,d", result.length()));
225 }
226 }
227
228 @Test
229 void testReadMixedBufferLengthsVarLenPlusToNoReplace() throws IOException {
230 final StringSubstitutor substitutor = new StringSubstitutor(values);
231 final String template = "123456";
232 assertTrue(template.length() > getMinExpressionLength(substitutor) + 1);
233 try (Reader reader = createReader(substitutor, template)) {
234 final int endIndex = template.length() - 1;
235 final char[] cbuf = new char[endIndex];
236 reader.read(cbuf);
237 final String result = String.valueOf(cbuf);
238 assertEquals(template.substring(0, endIndex), result);
239 assertEquals('6', reader.read());
240 }
241 }
242
243 private Reader toReader(final String expectedResult) {
244 return expectedResult != null ? new StringReader(expectedResult) : new NullReader();
245 }
246
247 private String toStringChar(final int ch) {
248 switch (ch) {
249 case -1:
250 return "EOS";
251 case 0:
252 return "NUL";
253 default:
254 return String.valueOf((char) ch);
255 }
256 }
257 }