/** * Copyright (c) 2000-present Liferay, Inc. All rights reserved. * * This library is free software; you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License as published by the Free * Software Foundation; either version 2.1 of the License, or (at your option) * any later version. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more * details. */ package com.liferay.portal.kernel.io; import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream; import com.liferay.portal.kernel.test.ReflectionTestUtil; import com.liferay.portal.kernel.test.rule.CodeCoverageAssertor; import com.liferay.portal.kernel.util.StringPool; import java.io.IOException; import java.io.OutputStream; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharsetEncoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import java.nio.charset.MalformedInputException; import java.nio.charset.UnmappableCharacterException; import java.util.Arrays; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import org.junit.Assert; import org.junit.ClassRule; import org.junit.Test; /** * @author Shuyang Zhou */ public class OutputStreamWriterTest { @ClassRule public static final CodeCoverageAssertor codeCoverageAssertor = CodeCoverageAssertor.INSTANCE; @Test public void testClose() throws IOException { // Normal close MarkerOutputStream markerOutputStream = new MarkerOutputStream(); try (OutputStreamWriter outputStreamWriter = new OutputStreamWriter( markerOutputStream)) { Assert.assertFalse(markerOutputStream._closed); outputStreamWriter.close(); Assert.assertTrue(markerOutputStream._closed); markerOutputStream._closed = false; try { outputStreamWriter.write(0); Assert.fail(); } catch (IOException ioe) { Assert.assertEquals("Stream closed", ioe.getMessage()); } } Assert.assertFalse(markerOutputStream._closed); // Exception close final IOException ioException = new IOException(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( new UnsyncByteArrayOutputStream() { @Override public void close() throws IOException { throw ioException; } }); // First close try { outputStreamWriter.close(); Assert.fail(); } catch (IOException ioe) { Assert.assertSame(ioe, ioException); } // Second close to check first close indeed changed the state outputStreamWriter.close(); } @Test public void testConstructor() { DummyOutputStream dummyOutputStream = new DummyOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( dummyOutputStream); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame( StringPool.DEFAULT_CHARSET_NAME, outputStreamWriter.getEncoding()); Assert.assertEquals( _getDefaultOutputBufferSize(), _getOutputBufferSize(outputStreamWriter)); Assert.assertFalse(_isAutoFlush(outputStreamWriter)); outputStreamWriter = new OutputStreamWriter(dummyOutputStream, null); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame( StringPool.DEFAULT_CHARSET_NAME, outputStreamWriter.getEncoding()); Assert.assertEquals( _getDefaultOutputBufferSize(), _getOutputBufferSize(outputStreamWriter)); Assert.assertFalse(_isAutoFlush(outputStreamWriter)); String encoding = "US-ASCII"; outputStreamWriter = new OutputStreamWriter( dummyOutputStream, encoding); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame(encoding, outputStreamWriter.getEncoding()); Assert.assertEquals( _getDefaultOutputBufferSize(), _getOutputBufferSize(outputStreamWriter)); Assert.assertFalse(_isAutoFlush(outputStreamWriter)); outputStreamWriter = new OutputStreamWriter( dummyOutputStream, encoding, true); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame(encoding, outputStreamWriter.getEncoding()); Assert.assertEquals( _getDefaultOutputBufferSize(), _getOutputBufferSize(outputStreamWriter)); Assert.assertTrue(_isAutoFlush(outputStreamWriter)); outputStreamWriter = new OutputStreamWriter( dummyOutputStream, encoding, 32); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame(encoding, outputStreamWriter.getEncoding()); Assert.assertEquals(2, _getInputCharBufferSize(outputStreamWriter)); Assert.assertEquals(32, _getOutputBufferSize(outputStreamWriter)); Assert.assertFalse(_isAutoFlush(outputStreamWriter)); outputStreamWriter = new OutputStreamWriter( dummyOutputStream, encoding, 32, true); Assert.assertSame( dummyOutputStream, _getOutputStream(outputStreamWriter)); Assert.assertSame(encoding, outputStreamWriter.getEncoding()); Assert.assertEquals(2, _getInputCharBufferSize(outputStreamWriter)); Assert.assertEquals(32, _getOutputBufferSize(outputStreamWriter)); Assert.assertTrue(_isAutoFlush(outputStreamWriter)); try { new OutputStreamWriter(dummyOutputStream, encoding, 3, true); Assert.fail(); } catch (IllegalArgumentException iae) { Assert.assertEquals( "Output buffer size 3 is less than 4", iae.getMessage()); } } @Test public void testFlush() throws IOException { MarkerOutputStream markerOutputStream = new MarkerOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( markerOutputStream); Assert.assertFalse(markerOutputStream._flushed); outputStreamWriter.flush(); Assert.assertTrue(markerOutputStream._flushed); outputStreamWriter.write('a'); outputStreamWriter.flush(); Assert.assertTrue(markerOutputStream._flushed); } @Test public void testFlushEncoder() throws IOException { MarkerOutputStream markerOutputStream = new MarkerOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( markerOutputStream); // Flush encoder overflow final AtomicInteger flushCounter = new AtomicInteger(); ReflectionTestUtil.setFieldValue( outputStreamWriter, "_charsetEncoder", new CharsetEncoderWrapper( ReflectionTestUtil.getFieldValue( outputStreamWriter, "_charsetEncoder")) { @Override protected CoderResult implFlush(ByteBuffer out) { int count = flushCounter.getAndIncrement(); if (count == 0) { return CoderResult.OVERFLOW; } return super.implFlush(out); } }); outputStreamWriter.close(); Assert.assertEquals(2, flushCounter.get()); // Flush encoder error outputStreamWriter = new OutputStreamWriter(markerOutputStream); ReflectionTestUtil.setFieldValue( outputStreamWriter, "_charsetEncoder", new CharsetEncoderWrapper( ReflectionTestUtil.getFieldValue( outputStreamWriter, "_charsetEncoder")) { @Override protected CoderResult implFlush(ByteBuffer out) { return CoderResult.malformedForLength(1); } }); try { outputStreamWriter.close(); } catch (MalformedInputException mie) { Assert.assertEquals(1, mie.getInputLength()); } } @Test public void testWriteCharArray() throws IOException { _testWriteCharArray(false); _testWriteCharArray(true); } @Test public void testWriteError() throws IOException { OutputStreamWriter outputStreamWriter = new OutputStreamWriter( new DummyOutputStream(), "US-ASCII"); CharsetEncoder charsetEncoder = ReflectionTestUtil.getFieldValue( outputStreamWriter, "_charsetEncoder"); charsetEncoder.onUnmappableCharacter(CodingErrorAction.REPORT); try { outputStreamWriter.write("测试"); Assert.fail(); } catch (UnmappableCharacterException uce) { Assert.assertEquals(1, uce.getInputLength()); } } @Test public void testWriteInt() throws IOException { MarkerOutputStream markerOutputStream = new MarkerOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( markerOutputStream); outputStreamWriter.write('a'); outputStreamWriter.flush(); Assert.assertEquals((byte)'a', markerOutputStream._bytes[0]); Assert.assertEquals(1, markerOutputStream._length); Assert.assertEquals(0, markerOutputStream._offset); } @Test public void testWriteIntUnicodeSurrogatePair() throws IOException { // writeInt + writeInt _testUnicodeSurrogatePair( (outputStreamWriter, surrogatePair) -> { outputStreamWriter.write(surrogatePair[0]); outputStreamWriter.write(surrogatePair[1]); }); // writeInt + writeCharArray _testUnicodeSurrogatePair( (outputStreamWriter, surrogatePair) -> { outputStreamWriter.write(surrogatePair[0]); outputStreamWriter.write(new char[] {surrogatePair[1]}); }); // writeCharArray + writeInt _testUnicodeSurrogatePair( (outputStreamWriter, surrogatePair) -> { outputStreamWriter.write(new char[] {surrogatePair[0]}); outputStreamWriter.write(surrogatePair[1]); }); // writeCharArray + writeCharArray _testUnicodeSurrogatePair( (outputStreamWriter, surrogatePair) -> { outputStreamWriter.write(new char[] {surrogatePair[0]}); outputStreamWriter.write(new char[] {surrogatePair[1]}); }); } @Test public void testWriteString() throws IOException { _testWriteString(false); _testWriteString(true); } private int _getDefaultOutputBufferSize() { return ReflectionTestUtil.getFieldValue( OutputStreamWriter.class, "_DEFAULT_OUTPUT_BUFFER_SIZE"); } private int _getInputCharBufferSize(OutputStreamWriter outputStreamWriter) { CharBuffer inputCharBuffer = ReflectionTestUtil.getFieldValue( outputStreamWriter, "_inputCharBuffer"); return inputCharBuffer.capacity(); } private int _getOutputBufferSize(OutputStreamWriter outputStreamWriter) { ByteBuffer outputBuffer = ReflectionTestUtil.getFieldValue( outputStreamWriter, "_outputByteBuffer"); return outputBuffer.capacity(); } private OutputStream _getOutputStream( OutputStreamWriter outputStreamWriter) { return ReflectionTestUtil.getFieldValue( outputStreamWriter, "_outputStream"); } private boolean _isAutoFlush(OutputStreamWriter outputStreamWriter) { return ReflectionTestUtil.getFieldValue( outputStreamWriter, "_autoFlush"); } private void _testUnicodeSurrogatePair( SurrogatePairConsumer surrogatePairConsumer) throws IOException { UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream(); OutputStreamWriter outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "UTF-8"); char[] surrogatePair = Character.toChars(0x2363A); Assert.assertEquals( Arrays.toString(surrogatePair), 2, surrogatePair.length); surrogatePairConsumer.accept(outputStreamWriter, surrogatePair); outputStreamWriter.flush(); String decodedString = new String( unsyncByteArrayOutputStream.toByteArray(), "UTF-8"); Assert.assertArrayEquals(surrogatePair, decodedString.toCharArray()); } private void _testWriteCharArray(boolean autoFlush) throws IOException { final AtomicBoolean flushed = new AtomicBoolean(); UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream() { @Override public void flush() { flushed.set(true); } }; OutputStreamWriter outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "US-ASCII", 4, autoFlush); outputStreamWriter.write("abcdefg".toCharArray(), 1, 5); Assert.assertFalse(flushed.get()); if (!autoFlush) { outputStreamWriter.flush(); Assert.assertTrue(flushed.get()); flushed.set(false); } Assert.assertArrayEquals( new byte[] {'b', 'c', 'd', 'e', 'f'}, unsyncByteArrayOutputStream.toByteArray()); unsyncByteArrayOutputStream.reset(); outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "US-ASCII", autoFlush); outputStreamWriter.write("abc".toCharArray()); Assert.assertFalse(flushed.get()); if (!autoFlush) { outputStreamWriter.flush(); Assert.assertTrue(flushed.get()); flushed.set(false); } Assert.assertArrayEquals( new byte[] {'a', 'b', 'c'}, unsyncByteArrayOutputStream.toByteArray()); } private void _testWriteString(boolean autoFlush) throws IOException { final AtomicBoolean flushed = new AtomicBoolean(); UnsyncByteArrayOutputStream unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream() { @Override public void flush() { flushed.set(true); } }; OutputStreamWriter outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "US-ASCII", 4, autoFlush); outputStreamWriter.write("abcdefg", 1, 5); Assert.assertFalse(flushed.get()); if (!autoFlush) { outputStreamWriter.flush(); Assert.assertTrue(flushed.get()); flushed.set(false); } Assert.assertArrayEquals( new byte[] {'b', 'c', 'd', 'e', 'f'}, unsyncByteArrayOutputStream.toByteArray()); unsyncByteArrayOutputStream.reset(); outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "US-ASCII", 4, autoFlush); outputStreamWriter.write("abcdefg", 1, 5); Assert.assertFalse(flushed.get()); if (!autoFlush) { outputStreamWriter.flush(); Assert.assertTrue(flushed.get()); flushed.set(false); } Assert.assertArrayEquals( new byte[] {'b', 'c', 'd', 'e', 'f'}, unsyncByteArrayOutputStream.toByteArray()); unsyncByteArrayOutputStream.reset(); outputStreamWriter = new OutputStreamWriter( unsyncByteArrayOutputStream, "US-ASCII", autoFlush); outputStreamWriter.write("abc"); Assert.assertFalse(flushed.get()); if (!autoFlush) { outputStreamWriter.flush(); Assert.assertTrue(flushed.get()); flushed.set(false); } Assert.assertArrayEquals( new byte[] {'a', 'b', 'c'}, unsyncByteArrayOutputStream.toByteArray()); } private static class CharsetEncoderWrapper extends CharsetEncoder { @Override public boolean canEncode(char c) { return _charsetEncoder.canEncode(c); } @Override public boolean canEncode(CharSequence cs) { return _charsetEncoder.canEncode(cs); } @Override public boolean isLegalReplacement(byte[] replacement) { return true; } @Override public CodingErrorAction malformedInputAction() { return _charsetEncoder.malformedInputAction(); } @Override public CodingErrorAction unmappableCharacterAction() { return _charsetEncoder.unmappableCharacterAction(); } @Override protected CoderResult encodeLoop(CharBuffer in, ByteBuffer out) { return ReflectionTestUtil.invoke( _charsetEncoder, "encodeLoop", new Class<?>[] {CharBuffer.class, ByteBuffer.class}, in, out); } private CharsetEncoderWrapper(CharsetEncoder charsetEncoder) { super( charsetEncoder.charset(), charsetEncoder.averageBytesPerChar(), charsetEncoder.maxBytesPerChar(), charsetEncoder.replacement()); _charsetEncoder = charsetEncoder; } private final CharsetEncoder _charsetEncoder; } private static class MarkerOutputStream extends OutputStream { @Override public void close() { _closed = true; } @Override public void flush() { _flushed = true; } @Override public void write(byte[] bytes, int offset, int length) { _bytes = bytes; _offset = offset; _length = length; } @Override public void write(int b) { } private byte[] _bytes; private boolean _closed; private boolean _flushed; private int _length; private int _offset; } private interface SurrogatePairConsumer { public void accept( OutputStreamWriter outputStreamWriter, char[] surrogatePair) throws IOException; } }