View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      https://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
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   * Tests {@link StringSubstitutorReader}.
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                  // stream can chunk in smaller sizes
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                     // fails
110                     assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
111                 }
112                 index.incrementAndGet();
113                 assertFalse(index.get() > expectedResultLen, () -> "Index: " + index.get());
114                 // simpler to debug if we zero out the buffers.
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                 // stream can chunk in smaller sizes
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                     // fails
151                     assertEquals(expectedCount, actualCount, () -> String.format("Step size %,d", targetSize));
152                 }
153                 index.incrementAndGet();
154                 assertFalse(index.get() > expectedResultLen, () -> "Index: " + index.get());
155                 // simpler to debug if we zero out the buffers.
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 }