Add incomplete emulations in PrintStream

Change-Id: I35e2c280c8df909d6af4ab7a30b2d1828af651ea
diff --git a/user/super/com/google/gwt/emul/java/io/PrintStream.java b/user/super/com/google/gwt/emul/java/io/PrintStream.java
index fb2739a..a47fb8c 100644
--- a/user/super/com/google/gwt/emul/java/io/PrintStream.java
+++ b/user/super/com/google/gwt/emul/java/io/PrintStream.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2006 Google Inc.
- * 
+ *
  * 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
@@ -16,71 +16,183 @@
 package java.io;
 
 /**
- * @skip
+ * See <a href="http://java.sun.com/j2se/1.5.0/docs/api/java/io/PrintStream.html">the official Java
+ * API doc</a> for details.
  */
 public class PrintStream extends FilterOutputStream {
 
+  /** Indicates whether or not this PrintStream has incurred an error. */
+  private boolean ioError = false;
+
   public PrintStream(OutputStream out) {
     super(out);
   }
 
   public void print(boolean x) {
+    print(String.valueOf(x));
   }
 
   public void print(char x) {
+    print(String.valueOf(x));
   }
 
   public void print(char[] x) {
+    print(new String(x, 0, x.length));
   }
 
   public void print(double x) {
+    print(String.valueOf(x));
   }
 
   public void print(float x) {
+    print(String.valueOf(x));
   }
 
   public void print(int x) {
+    print(String.valueOf(x));
   }
 
   public void print(long x) {
+    print(String.valueOf(x));
   }
 
   public void print(Object x) {
+    print(String.valueOf(x));
   }
 
   public void print(String s) {
+    if (out == null) {
+      setError();
+      return;
+    }
+    if (s == null) {
+      print("null");
+      return;
+    }
+
+    try {
+      write(s.getBytes());
+    } catch (IOException e) {
+      setError();
+    }
   }
 
   public void println() {
+    newline();
   }
 
   public void println(boolean x) {
+    println(String.valueOf(x));
   }
 
   public void println(char x) {
+    println(String.valueOf(x));
   }
 
   public void println(char[] x) {
+    println(new String(x, 0, x.length));
   }
 
   public void println(double x) {
+    println(String.valueOf(x));
   }
 
   public void println(float x) {
+    println(String.valueOf(x));
   }
 
   public void println(int x) {
+    println(String.valueOf(x));
   }
 
   public void println(long x) {
+    println(String.valueOf(x));
   }
 
   public void println(Object x) {
+    println(String.valueOf(x));
   }
 
   public void println(String s) {
+    print(s);
+    newline();
   }
 
+  @Override
   public void flush() {
+    if (out != null) {
+      try {
+        out.flush();
+        return;
+      } catch (IOException e) {
+        // Ignored, fall through to setError
+      }
+    }
+    setError();
+  }
+
+  @Override
+  public void close() {
+    flush();
+    if (out != null) {
+      try {
+        out.close();
+      } catch (IOException e) {
+        setError();
+      } finally {
+        out = null;
+      }
+    }
+  }
+
+  @Override
+  public void write(byte[] buffer, int offset, int length) {
+    // Force buffer null check first!
+    IOUtils.checkOffsetAndCount(buffer, offset, length);
+    if (out == null) {
+      setError();
+      return;
+    }
+    try {
+      out.write(buffer, offset, length);
+    } catch (IOException e) {
+      setError();
+    }
+  }
+
+  @Override
+  public void write(int oneByte) {
+    if (out == null) {
+      setError();
+      return;
+    }
+    try {
+      out.write(oneByte);
+      int b = oneByte & 0xFF;
+      // 0x0A is ASCII newline, 0x15 is EBCDIC newline.
+      boolean isNewline = b == 0x0A || b == 0x15;
+      if (isNewline) {
+        flush();
+      }
+    } catch (IOException e) {
+      setError();
+    }
+  }
+
+  public boolean checkError() {
+    flush();
+    return ioError;
+  }
+
+  protected void setError() {
+    ioError = true;
+  }
+
+  protected void clearError() {
+    ioError = false;
+  }
+
+  private void newline() {
+    print('\n');
   }
 }
diff --git a/user/test/com/google/gwt/emultest/EmulSuite.java b/user/test/com/google/gwt/emultest/EmulSuite.java
index d28f9ec..f6de140 100644
--- a/user/test/com/google/gwt/emultest/EmulSuite.java
+++ b/user/test/com/google/gwt/emultest/EmulSuite.java
@@ -24,6 +24,7 @@
 import com.google.gwt.emultest.java.io.InputStreamTest;
 import com.google.gwt.emultest.java.io.OutputStreamTest;
 import com.google.gwt.emultest.java.io.OutputStreamWriterTest;
+import com.google.gwt.emultest.java.io.PrintStreamTest;
 import com.google.gwt.emultest.java.io.WriterTest;
 import com.google.gwt.emultest.java.lang.BooleanTest;
 import com.google.gwt.emultest.java.lang.ByteTest;
@@ -79,6 +80,7 @@
   InputStreamTest.class,
   OutputStreamTest.class,
   OutputStreamWriterTest.class,
+  PrintStreamTest.class,
   WriterTest.class,
 
   // -- java.lang
diff --git a/user/test/com/google/gwt/emultest/java/io/PrintStreamTest.java b/user/test/com/google/gwt/emultest/java/io/PrintStreamTest.java
new file mode 100644
index 0000000..a0dabfe
--- /dev/null
+++ b/user/test/com/google/gwt/emultest/java/io/PrintStreamTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2020 Google Inc.
+ *
+ * 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 com.google.gwt.emultest.java.io;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.google.gwt.junit.client.GWTTestCase;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+
+/** Unit test for the {@link java.io.PrintStream} emulated class. */
+public class PrintStreamTest extends GWTTestCase {
+
+  /** {@link java.io.PrintStream} object being tested. */
+  private PrintStream ps;
+
+  /** Underlying output stream used by the {@link PrintStream} object. */
+  private ByteArrayOutputStream baos;
+
+  private static class MockPrintStream extends PrintStream {
+
+    public MockPrintStream(OutputStream os) {
+      super(os);
+    }
+
+    @Override
+    public void clearError() {
+      super.clearError();
+    }
+
+    @Override
+    public void setError() {
+      super.setError();
+    }
+  }
+
+  /** Sets module name so that javascript compiler can operate. */
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.emultest.EmulSuite";
+  }
+
+  @Override
+  protected void gwtSetUp() throws Exception {
+    super.gwtSetUp();
+    baos = new ByteArrayOutputStream();
+    ps = new PrintStream(baos);
+  }
+
+  public void testPrintCharArray() {
+    char[] charArray = {'a', 'b', 'c', '"', '&', '<', '>'};
+    ps.print(charArray);
+    ps.close();
+    assertTrue(
+        "Incorrect char[] written",
+        Arrays.equals(new String(charArray).getBytes(UTF_8), baos.toByteArray()));
+  }
+
+  public void testPrintChar() {
+    ps.print('t');
+    ps.close();
+    assertEquals("Incorrect char written", "t", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintDouble() {
+    ps.print(2345.76834720202);
+    ps.close();
+    assertEquals(
+        "Incorrect double written", "2345.76834720202", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintInt() {
+    ps.print(768347202);
+    ps.close();
+    assertEquals("Incorrect int written", "768347202", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintLong() {
+    ps.print(919728433988L);
+    ps.close();
+    assertEquals("Incorrect long written", "919728433988", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintFloat() {
+    ps.print(29.08764);
+    ps.close();
+    assertEquals("Incorrect float written", "29.08764", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintNullObject() {
+    ps.print((Object) null);
+    ps.close();
+    assertEquals("null should be written", "null", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintObject() {
+    ps.print(new ArrayList<String>());
+    ps.close();
+    assertEquals("Incorrect Object written", "[]", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintNullString() {
+    ps.print((String) null);
+    ps.close();
+    assertEquals("null should be written", "null", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintString() {
+    ps.print("Hello World");
+    ps.close();
+    assertEquals("Incorrect String written", "Hello World", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintBoolean() {
+    ps.print(true);
+    ps.close();
+    assertEquals("Incorrect boolean written", "true", new String(baos.toByteArray(), UTF_8));
+  }
+
+  public void testPrintln() {
+    ps.println();
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnCharArray() {
+    char[] charArray = {'a', 'b', 'c', '"', '&', '<', '>'};
+    ps.println(charArray);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[7];
+    bais.read(outBytes, 0, 7);
+    assertTrue(
+        "Incorrect char[] written", Arrays.equals(new String(charArray).getBytes(UTF_8), outBytes));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnChar() {
+    ps.println('t');
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    assertEquals("Incorrect char written", 't', bais.read());
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnDouble() {
+    ps.println(2345.76834720202);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[16];
+    bais.read(outBytes, 0, 16);
+    assertEquals("Incorrect double written", "2345.76834720202", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnInt() {
+    ps.println(768347202);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[9];
+    bais.read(outBytes, 0, 9);
+    assertEquals("Incorrect int written", "768347202", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnLong() {
+    ps.println(919728433988L);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[12];
+    bais.read(outBytes, 0, 12);
+    assertEquals("Incorrect double written", "919728433988", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnFloat() {
+    ps.println(29.08764);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[8];
+    bais.read(outBytes, 0, 8);
+    assertEquals("Incorrect float written", "29.08764", new String(outBytes, 0, 8, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnNullObject() {
+    ps.println((Object) null);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[4];
+    bais.read(outBytes, 0, 4);
+    assertEquals("null should be written", "null", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnObject() {
+    ps.println(new ArrayList<String>());
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[2];
+    bais.read(outBytes, 0, 2);
+    assertEquals("Incorrect Object written", "[]", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnNullString() {
+    ps.println((String) null);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[4];
+    bais.read(outBytes, 0, 4);
+    assertEquals("null should be written", "null", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnString() {
+    ps.println("Hello World");
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[11];
+    bais.read(outBytes, 0, 11);
+    assertEquals("Incorrect String written", "Hello World", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testPrintlnBoolean() {
+    ps.println(true);
+    ps.close();
+    ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
+    byte[] outBytes = new byte[4];
+    bais.read(outBytes, 0, 4);
+    assertEquals("Incorrect boolean written", "true", new String(outBytes, UTF_8));
+    assertEquals("Newline not written", '\n', bais.read());
+  }
+
+  public void testCheckError() {
+    ps =
+        new PrintStream(
+            new OutputStream() {
+
+              @Override
+              public void write(int b) throws IOException {
+                throw new IOException();
+              }
+
+              @Override
+              public void write(byte[] b, int o, int l) throws IOException {
+                throw new IOException();
+              }
+            });
+    ps.print("Hello World");
+    assertTrue(ps.checkError());
+  }
+
+  public void testClearError() {
+    MockPrintStream ps = new MockPrintStream(baos);
+    assertFalse(ps.checkError());
+    ps.setError();
+    assertTrue(ps.checkError());
+    ps.clearError();
+    assertFalse(ps.checkError());
+    ps.close();
+  }
+}