001    /**
002     * Copyright (c) 2000-present Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.io;
016    
017    import com.liferay.portal.kernel.nio.charset.CharsetDecoderUtil;
018    import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
019    import com.liferay.portal.kernel.util.StringPool;
020    
021    import java.io.IOException;
022    import java.io.OutputStream;
023    import java.io.Writer;
024    
025    import java.nio.ByteBuffer;
026    import java.nio.CharBuffer;
027    import java.nio.charset.CharsetDecoder;
028    import java.nio.charset.CharsetEncoder;
029    import java.nio.charset.CoderResult;
030    
031    /**
032     * @author Shuyang Zhou
033     */
034    public class WriterOutputStream extends OutputStream {
035    
036            public WriterOutputStream(Writer writer) {
037                    this(
038                            writer, StringPool.DEFAULT_CHARSET_NAME,
039                            _DEFAULT_OUTPUT_BUFFER_SIZE, false);
040            }
041    
042            public WriterOutputStream(Writer writer, String charsetName) {
043                    this(writer, charsetName, _DEFAULT_OUTPUT_BUFFER_SIZE, false);
044            }
045    
046            public WriterOutputStream(
047                    Writer writer, String charsetName, boolean autoFlush) {
048    
049                    this(writer, charsetName, _DEFAULT_OUTPUT_BUFFER_SIZE, autoFlush);
050            }
051    
052            public WriterOutputStream(
053                    Writer writer, String charsetName, int outputBufferSize) {
054    
055                    this(writer, charsetName, outputBufferSize, false);
056            }
057    
058            public WriterOutputStream(
059                    Writer writer, String charsetName, int outputBufferSize,
060                    boolean autoFlush) {
061    
062                    if (outputBufferSize <= 0) {
063                            if (autoFlush) {
064                                    outputBufferSize = _DEFAULT_OUTPUT_BUFFER_SIZE;
065                            }
066                            else {
067                                    throw new IllegalArgumentException(
068                                            "Output buffer size " + outputBufferSize +
069                                                    " must be a positive number");
070                            }
071                    }
072    
073                    if (charsetName == null) {
074                            charsetName = StringPool.DEFAULT_CHARSET_NAME;
075                    }
076    
077                    _writer = writer;
078                    _charsetName = charsetName;
079                    _charsetDecoder = CharsetDecoderUtil.getCharsetDecoder(charsetName);
080    
081                    CharsetEncoder charsetEncoder = CharsetEncoderUtil.getCharsetEncoder(
082                            charsetName);
083    
084                    _inputByteBuffer = ByteBuffer.allocate(
085                            (int)Math.ceil(charsetEncoder.maxBytesPerChar()));
086    
087                    _inputByteBuffer.limit(0);
088    
089                    _outputCharBuffer = CharBuffer.allocate(outputBufferSize);
090                    _autoFlush = autoFlush;
091            }
092    
093            @Override
094            public void close() throws IOException {
095                    _doDecode(_inputByteBuffer, true);
096    
097                    _flushBuffer();
098    
099                    _writer.close();
100            }
101    
102            @Override
103            public void flush() throws IOException {
104                    _flushBuffer();
105    
106                    _writer.flush();
107            }
108    
109            public String getEncoding() {
110                    return _charsetName;
111            }
112    
113            @Override
114            public void write(byte[] bytes) throws IOException {
115                    write(bytes, 0, bytes.length);
116            }
117    
118            @Override
119            public void write(byte[] bytes, int offset, int length) throws IOException {
120                    while (_inputByteBuffer.hasRemaining()) {
121                            write(bytes[offset++]);
122    
123                            length--;
124                    }
125    
126                    ByteBuffer inputByteBuffer = ByteBuffer.wrap(bytes, offset, length);
127    
128                    _doDecode(inputByteBuffer, false);
129    
130                    if (inputByteBuffer.hasRemaining()) {
131                            _inputByteBuffer.limit(inputByteBuffer.remaining());
132    
133                            _inputByteBuffer.put(inputByteBuffer);
134    
135                            _inputByteBuffer.flip();
136                    }
137            }
138    
139            @Override
140            public void write(int b) throws IOException {
141                    int limit = _inputByteBuffer.limit();
142    
143                    _inputByteBuffer.limit(limit + 1);
144    
145                    _inputByteBuffer.put(limit, (byte)b);
146    
147                    _doDecode(_inputByteBuffer, false);
148    
149                    if (!_inputByteBuffer.hasRemaining()) {
150                            _inputByteBuffer.position(0);
151    
152                            _inputByteBuffer.limit(0);
153                    }
154            }
155    
156            private void _doDecode(ByteBuffer inputByteBuffer, boolean endOfInput)
157                    throws IOException {
158    
159                    while (true) {
160                            CoderResult coderResult = _charsetDecoder.decode(
161                                    inputByteBuffer, _outputCharBuffer, endOfInput);
162    
163                            if (coderResult.isOverflow()) {
164                                    _flushBuffer();
165                            }
166                            else if (coderResult.isUnderflow()) {
167                                    if (_autoFlush) {
168                                            _flushBuffer();
169                                    }
170    
171                                    break;
172                            }
173                            else {
174                                    throw new IOException("Unexcepted coder result " + coderResult);
175                            }
176                    }
177            }
178    
179            private void _flushBuffer() throws IOException {
180                    if (_outputCharBuffer.position() > 0) {
181                            _writer.write(
182                                    _outputCharBuffer.array(), 0, _outputCharBuffer.position());
183    
184                            _outputCharBuffer.rewind();
185                    }
186            }
187    
188            private static final int _DEFAULT_OUTPUT_BUFFER_SIZE = 8192;
189    
190            private final boolean _autoFlush;
191            private final CharsetDecoder _charsetDecoder;
192            private final String _charsetName;
193            private final ByteBuffer _inputByteBuffer;
194            private final CharBuffer _outputCharBuffer;
195            private final Writer _writer;
196    
197    }