in src/main/java/org/apache/commons/text/io/StringSubstitutorReader.java [175:305]
public int read(final char[] target, final int targetIndexIn, final int targetLengthIn) throws IOException {
// The whole thing is inefficient because we must look for a balanced suffix to match the starting prefix
// Trying to substitute an incomplete expression can perform replacements when it should not.
// At a high level:
// - if draining, drain until empty or target length hit
// - copy to target until we find a variable start
// - buffer until a balanced suffix is read, then substitute.
if (eos && buffer.isEmpty()) {
return EOS;
}
if (targetLengthIn <= 0) {
// short-circuit: ask nothing, give nothing
return 0;
}
// drain check
int targetIndex = targetIndexIn;
int targetLength = targetLengthIn;
if (isDraining()) {
// drain as much as possible
final int drainCount = drain(target, targetIndex, Math.min(toDrain, targetLength));
if (drainCount == targetLength) {
// drained length requested, target is full, can only do more in the next invocation
return targetLength;
}
// drained less than requested, target not full.
targetIndex += drainCount;
targetLength -= drainCount;
}
// BUFFER from the underlying reader
final int minReadLenPrefix = prefixEscapeMatcher.size();
// READ enough to test for an [optionally escaped] variable start
int readCount = buffer(readCount(minReadLenPrefix, 0));
if (buffer.length() < minReadLenPrefix && targetLength < minReadLenPrefix) {
// read less than minReadLenPrefix, no variable possible
final int drainCount = drain(target, targetIndex, targetLength);
targetIndex += drainCount;
final int targetSize = targetIndex - targetIndexIn;
return eos && targetSize <= 0 ? EOS : targetSize;
}
if (eos) {
// EOS
stringSubstitutor.replaceIn(buffer);
toDrain = buffer.size();
final int drainCount = drain(target, targetIndex, targetLength);
targetIndex += drainCount;
final int targetSize = targetIndex - targetIndexIn;
return eos && targetSize <= 0 ? EOS : targetSize;
}
// PREFIX
// buffer and drain until we find a variable start, escaped or plain.
int balance = 0;
final StringMatcher prefixMatcher = stringSubstitutor.getVariablePrefixMatcher();
int pos = 0;
while (targetLength > 0) {
if (isBufferMatchAt(prefixMatcher, 0)) {
balance = 1;
pos = prefixMatcher.size();
break;
}
if (isBufferMatchAt(prefixEscapeMatcher, 0)) {
balance = 1;
pos = prefixEscapeMatcher.size();
break;
}
// drain first char
final int drainCount = drain(target, targetIndex, 1);
targetIndex += drainCount;
targetLength -= drainCount;
if (buffer.size() < minReadLenPrefix) {
readCount = bufferOrDrainOnEos(minReadLenPrefix, target, targetIndex, targetLength);
if (eos || isDraining()) {
// if draining, readCount is a drain count
if (readCount != EOS) {
targetIndex += readCount;
targetLength -= readCount;
}
final int actual = targetIndex - targetIndexIn;
return actual > 0 ? actual : EOS;
}
}
}
// we found a variable start
if (targetLength <= 0) {
// no more room in target
return targetLengthIn;
}
// SUFFIX
// buffer more to find a balanced suffix
final StringMatcher suffixMatcher = stringSubstitutor.getVariableSuffixMatcher();
final int minReadLenSuffix = Math.max(minReadLenPrefix, suffixMatcher.size());
readCount = buffer(readCount(minReadLenSuffix, pos));
if (eos) {
// EOS
stringSubstitutor.replaceIn(buffer);
toDrain = buffer.size();
final int drainCount = drain(target, targetIndex, targetLength);
return targetIndex + drainCount - targetIndexIn;
}
// buffer and break out when we find the end or a balanced suffix
while (true) {
if (isBufferMatchAt(suffixMatcher, pos)) {
balance--;
pos++;
if (balance == 0) {
break;
}
} else if (isBufferMatchAt(prefixMatcher, pos)) {
balance++;
pos += prefixMatcher.size();
} else if (isBufferMatchAt(prefixEscapeMatcher, pos)) {
balance++;
pos += prefixEscapeMatcher.size();
} else {
pos++;
}
readCount = buffer(readCount(minReadLenSuffix, pos));
if (readCount == EOS && pos >= buffer.size()) {
break;
}
}
// substitute
final int endPos = pos + 1;
final int leftover = Math.max(0, buffer.size() - pos);
stringSubstitutor.replaceIn(buffer, 0, Math.min(buffer.size(), endPos));
pos = buffer.size() - leftover;
final int drainLen = Math.min(targetLength, pos);
// only drain up to what we've substituted
toDrain = pos;
drain(target, targetIndex, drainLen);
return targetIndex - targetIndexIn + drainLen;
}