diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TBufferedOutputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TBufferedOutputStream.java index d9bac2ec4..ee5a6b6e4 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TBufferedOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TBufferedOutputStream.java @@ -17,17 +17,18 @@ package org.teavm.classlib.java.io; import java.io.IOException; +import java.io.OutputStream; public class TBufferedOutputStream extends TFilterOutputStream { protected byte[] buf; protected int count; - public TBufferedOutputStream(TOutputStream out) { + public TBufferedOutputStream(OutputStream out) { super(out); buf = new byte[8192]; } - public TBufferedOutputStream(TOutputStream out, int size) { + public TBufferedOutputStream(OutputStream out, int size) { super(out); if (size <= 0) { throw new IllegalArgumentException("size must be > 0"); diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayOutputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayOutputStream.java index 55052c151..ec3f1d211 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TByteArrayOutputStream.java @@ -18,6 +18,7 @@ package org.teavm.classlib.java.io; import java.io.IOException; import org.teavm.classlib.java.lang.TMath; import org.teavm.classlib.java.lang.TString; +import org.teavm.classlib.java.nio.charset.TCharset; import org.teavm.classlib.java.util.TArrays; public class TByteArrayOutputStream extends TOutputStream { @@ -66,6 +67,10 @@ public class TByteArrayOutputStream extends TOutputStream { return new TString(buf, 0, count, charsetName); } + public TString toString(TCharset charset) { + return new TString(buf, 0, count, charset); + } + public void writeTo(TOutputStream out) throws IOException { out.write(buf, 0, count); } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TDataOutputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TDataOutputStream.java index 3cc9b0bad..e7285a045 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TDataOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TDataOutputStream.java @@ -14,8 +14,14 @@ * limitations under the License. */ package org.teavm.classlib.java.io; + import java.io.IOException; -import org.teavm.classlib.java.lang.*; +import java.io.OutputStream; +import org.teavm.classlib.java.lang.TDouble; +import org.teavm.classlib.java.lang.TFloat; +import org.teavm.classlib.java.lang.TInteger; +import org.teavm.classlib.java.lang.TNullPointerException; +import org.teavm.classlib.java.lang.TString; public class TDataOutputStream extends TFilterOutputStream implements TDataOutput { /** @@ -24,7 +30,7 @@ public class TDataOutputStream extends TFilterOutputStream implements TDataOutpu protected int written; byte[] buff; - public TDataOutputStream(TOutputStream out) { + public TDataOutputStream(OutputStream out) { super(out); buff = new byte[8]; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TFilterOutputStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TFilterOutputStream.java index 324c3cf4d..34282ed10 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TFilterOutputStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TFilterOutputStream.java @@ -16,11 +16,12 @@ package org.teavm.classlib.java.io; import java.io.IOException; +import java.io.OutputStream; public class TFilterOutputStream extends TOutputStream { - protected TOutputStream out; + protected OutputStream out; - public TFilterOutputStream(TOutputStream out) { + public TFilterOutputStream(OutputStream out) { this.out = out; } diff --git a/classlib/src/main/java/org/teavm/classlib/java/io/TPrintStream.java b/classlib/src/main/java/org/teavm/classlib/java/io/TPrintStream.java index 664d55287..063a726ce 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/io/TPrintStream.java +++ b/classlib/src/main/java/org/teavm/classlib/java/io/TPrintStream.java @@ -16,6 +16,8 @@ package org.teavm.classlib.java.io; import java.io.IOException; +import java.io.OutputStream; +import java.util.Locale; import org.teavm.classlib.java.lang.TMath; import org.teavm.classlib.java.lang.TObject; import org.teavm.classlib.java.lang.TStringBuilder; @@ -27,6 +29,7 @@ import org.teavm.classlib.java.nio.charset.TCodingErrorAction; import org.teavm.classlib.java.nio.charset.TIllegalCharsetNameException; import org.teavm.classlib.java.nio.charset.TUnsupportedCharsetException; import org.teavm.classlib.java.nio.charset.impl.TUTF8Charset; +import org.teavm.classlib.java.util.TFormatter; public class TPrintStream extends TFilterOutputStream { private boolean autoFlush; @@ -35,7 +38,7 @@ public class TPrintStream extends TFilterOutputStream { private char[] buffer = new char[32]; private TCharset charset; - public TPrintStream(TOutputStream out, boolean autoFlush, String encoding) throws TUnsupportedEncodingException { + public TPrintStream(OutputStream out, boolean autoFlush, String encoding) throws TUnsupportedEncodingException { super(out); this.autoFlush = autoFlush; try { @@ -45,13 +48,23 @@ public class TPrintStream extends TFilterOutputStream { } } - public TPrintStream(TOutputStream out, boolean autoFlush) { + public TPrintStream(OutputStream out, boolean autoFlush) { super(out); this.autoFlush = autoFlush; this.charset = TUTF8Charset.INSTANCE; } - public TPrintStream(TOutputStream out) { + public TPrintStream(OutputStream out, boolean autoFlush, TCharset charset) { + super(out); + this.autoFlush = autoFlush; + this.charset = charset; + } + + public TPrintStream(OutputStream out, TCharset charset) { + this(out, false, charset); + } + + public TPrintStream(OutputStream out) { this(out, false); } @@ -157,6 +170,31 @@ public class TPrintStream extends TFilterOutputStream { } } + private void print(CharSequence s, int begin, int end) { + TCharBuffer src = TCharBuffer.wrap(s, begin, end - begin); + byte[] destBytes = new byte[TMath.max(16, TMath.min(end - begin, 1024))]; + TByteBuffer dest = TByteBuffer.wrap(destBytes); + TCharsetEncoder encoder = charset.newEncoder() + .onMalformedInput(TCodingErrorAction.REPLACE) + .onUnmappableCharacter(TCodingErrorAction.REPLACE); + while (true) { + boolean overflow = encoder.encode(src, dest, true).isOverflow(); + write(destBytes, 0, dest.position()); + dest.clear(); + if (!overflow) { + break; + } + } + while (true) { + boolean overflow = encoder.flush(dest).isOverflow(); + write(destBytes, 0, dest.position()); + dest.clear(); + if (!overflow) { + break; + } + } + } + public void print(char c) { buffer[0] = c; print(buffer, 0, 1); @@ -231,10 +269,54 @@ public class TPrintStream extends TFilterOutputStream { print('\n'); } + public TPrintStream format(String format, Object... args) { + return format(Locale.getDefault(), format, args); + } + + public TPrintStream format(Locale locale, String format, Object... args) { + if (args == null) { + args = new Object[1]; + } + try (var formatter = new TFormatter(getAppendable(), locale)) { + formatter.format(format, args); + if (formatter.ioException() != null) { + errorState = true; + } + } + return this; + } + private void printSB() { char[] buffer = sb.length() > this.buffer.length ? new char[sb.length()] : this.buffer; sb.getChars(0, sb.length(), buffer, 0); print(buffer, 0, sb.length()); sb.setLength(0); } + + private Appendable appendable; + + private Appendable getAppendable() { + if (appendable == null) { + appendable = new Appendable() { + @Override + public Appendable append(CharSequence csq) { + print(csq, 0, csq.length()); + return this; + } + + @Override + public Appendable append(CharSequence csq, int start, int end) { + print(csq, start, end); + return this; + } + + @Override + public Appendable append(char c) { + print(c); + return this; + } + }; + } + return appendable; + } } diff --git a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java index d9f5c6395..5446675ba 100644 --- a/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java +++ b/classlib/src/main/java/org/teavm/classlib/java/lang/TSystem.java @@ -30,7 +30,6 @@ import org.teavm.classlib.impl.console.StderrOutputStream; import org.teavm.classlib.impl.console.StdoutOutputStream; import org.teavm.classlib.java.io.TConsole; import org.teavm.classlib.java.io.TInputStream; -import org.teavm.classlib.java.io.TOutputStream; import org.teavm.classlib.java.io.TPrintStream; import org.teavm.classlib.java.lang.reflect.TArray; import org.teavm.dependency.PluggableDependency; @@ -60,7 +59,7 @@ public final class TSystem extends TObject { if (PlatformDetector.isJavaScript()) { outCache = (TPrintStream) (Object) new JSStdoutPrintStream(); } else { - outCache = new TPrintStream((TOutputStream) (Object) StdoutOutputStream.INSTANCE, false); + outCache = new TPrintStream(StdoutOutputStream.INSTANCE, false); } } return outCache; @@ -71,7 +70,7 @@ public final class TSystem extends TObject { if (PlatformDetector.isJavaScript()) { errCache = (TPrintStream) (Object) new JSStderrPrintStream(); } else { - errCache = new TPrintStream((TOutputStream) (Object) StderrOutputStream.INSTANCE, false); + errCache = new TPrintStream(StderrOutputStream.INSTANCE, false); } } return errCache; diff --git a/tests/src/test/java/org/teavm/classlib/java/io/PrintStreamTest.java b/tests/src/test/java/org/teavm/classlib/java/io/PrintStreamTest.java new file mode 100644 index 000000000..4262db9d5 --- /dev/null +++ b/tests/src/test/java/org/teavm/classlib/java/io/PrintStreamTest.java @@ -0,0 +1,37 @@ +/* + * Copyright 2024 Alexey Andreev. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.teavm.classlib.java.io; + +import static org.junit.Assert.assertEquals; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import org.junit.runner.RunWith; +import org.teavm.junit.TeaVMTestRunner; +import org.testng.annotations.Test; + +@RunWith(TeaVMTestRunner.class) +public class PrintStreamTest { + @Test + public void format() { + var bytes = new ByteArrayOutputStream(); + var stream = new PrintStream(bytes, false, StandardCharsets.UTF_8); + stream.format("n=%d; ", 23); + stream.format("s=%s", null); + stream.flush(); + assertEquals("n=23; s=null", bytes.toString(StandardCharsets.UTF_8)); + } +}