diff --git a/src/main/java/io/usethesource/vallang/IString.java b/src/main/java/io/usethesource/vallang/IString.java index cee3ef89..10dc4744 100644 --- a/src/main/java/io/usethesource/vallang/IString.java +++ b/src/main/java/io/usethesource/vallang/IString.java @@ -13,6 +13,7 @@ package io.usethesource.vallang; import java.io.IOException; +import java.io.Reader; import java.io.Writer; import java.util.PrimitiveIterator.OfInt; @@ -99,6 +100,13 @@ default int getMatchFingerprint() { */ public void write(Writer w) throws IOException; + /** + * Generates a reader that can be used to stream the contents of the string + * Note, this will generate java characters, users are responsible for dealing with surrogate-pairs. + * See {@link #iterator()} for a more unicode compatible approach to iterate over the characters of an IString. + */ + public Reader asReader(); + /** * Build an iterator which generates the Unicode UTF-32 codepoints of the IString one-by-one. * @see Character for more information on Unicode UTF-32 codepoints. diff --git a/src/main/java/io/usethesource/vallang/impl/primitive/StringValue.java b/src/main/java/io/usethesource/vallang/impl/primitive/StringValue.java index 942cad30..4f3f4ec4 100644 --- a/src/main/java/io/usethesource/vallang/impl/primitive/StringValue.java +++ b/src/main/java/io/usethesource/vallang/impl/primitive/StringValue.java @@ -16,6 +16,8 @@ package io.usethesource.vallang.impl.primitive; import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; import java.io.StringWriter; import java.io.Writer; import java.lang.management.ManagementFactory; @@ -279,6 +281,11 @@ public boolean isNewlineTerminated() { public IString concat(IString other) { return other; } + + @Override + public Reader asReader() { + return Reader.nullReader(); + } } private static class FullUnicodeString extends AbstractString { @@ -501,6 +508,11 @@ public void write(Writer w) throws IOException { w.write(value); } + @Override + public Reader asReader() { + return new StringReader(value); + } + @Override public void indentedWrite(Writer w, Deque whitespace, boolean indentFirstLine) throws IOException { if (value.isEmpty()) { @@ -633,6 +645,11 @@ public int nextInt() { } }; } + + @Override + public Reader asReader() { + return new StringReader(value); + } } /** @@ -1146,6 +1163,47 @@ public void write(Writer w) throws IOException { right.write(w); } + @Override + public Reader asReader() { + return new Reader() { + private Reader currentReader = left.asReader(); + private boolean readingRight = false; + + private void continueRight() throws IOException { + assert !readingRight; + currentReader.close(); + currentReader = right.asReader(); + readingRight = true; + } + + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int result = currentReader.read(cbuf, off, len); + if (result == -1 && !readingRight) { + continueRight(); + return read(cbuf, off, len); + } + return result; + } + + @Override + public int read() throws IOException { + int result = currentReader.read(); + if (result == -1 && !readingRight) { + continueRight(); + return read(); + } + return result; + } + + @Override + public void close() throws IOException { + currentReader.close(); + } + }; + } + @Override public void indentedWrite(Writer w, Deque whitespace, boolean indentFirstLine) throws IOException { left.indentedWrite(w, whitespace, indentFirstLine); @@ -1380,6 +1438,72 @@ public void write(Writer w) throws IOException { assert indents.isEmpty(); } + @Override + public Reader asReader() { + return new Reader() { + private final OfInt chars = iterator(); + private char endedWithHalfSurrogate = 0; + + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + if (off < 0 || len < 0 || len > (cbuf.length - off)) { + throw new IndexOutOfBoundsException(); + } + if (len == 0) { + return 0; + } + + int pos = off; + if (endedWithHalfSurrogate != 0) { + cbuf[pos++] = endedWithHalfSurrogate; + endedWithHalfSurrogate = 0; + } + int endPos = off + len; + while (pos <= endPos) { + if (!chars.hasNext()) { + break; + } + int nextChar = chars.nextInt(); + if (Character.isBmpCodePoint(nextChar)) { + cbuf[pos++] = (char)nextChar; + } else { + cbuf[pos++] = Character.highSurrogate(nextChar); + char lowSide = Character.lowSurrogate(nextChar); + if (pos <= endPos) { + cbuf[pos++] = lowSide; + } else { + endedWithHalfSurrogate = lowSide; + } + } + } + int written = pos - off; + return written == 0 ? /* EOF */ -1 : written; + } + + @Override + public int read() throws IOException { + if (endedWithHalfSurrogate != 0) { + int result = endedWithHalfSurrogate; + endedWithHalfSurrogate = 0; + return result; + } + if (chars.hasNext()) { + int nextChar = chars.nextInt(); + if (Character.isBmpCodePoint(nextChar)) { + return nextChar; + } else { + endedWithHalfSurrogate = Character.lowSurrogate(nextChar); + return Character.highSurrogate(nextChar); + } + } + return -1; + } + @Override + public void close() throws IOException { + } + }; + } + @Override public void indentedWrite(Writer w, Deque whitespace, boolean indentFirstLine) throws IOException { if (flattened != null) {