1/*
2 * Copyright (c) 2001, 2005, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26/*
27 */
28
29package sun.nio.cs;
30
31import java.io.*;
32import java.nio.*;
33import java.nio.channels.*;
34import java.nio.charset.*;
35
36public class StreamEncoder extends Writer
37{
38
39    private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
40
41    private volatile boolean closed;
42
43    private void ensureOpen() throws IOException {
44        if (closed)
45            throw new IOException("Stream closed");
46    }
47
48    // Factories for java.io.OutputStreamWriter
49    public static StreamEncoder forOutputStreamWriter(OutputStream out,
50                                                      Object lock,
51                                                      String charsetName)
52        throws UnsupportedEncodingException
53    {
54        String csn = charsetName;
55        if (csn == null)
56            csn = Charset.defaultCharset().name();
57        try {
58            if (Charset.isSupported(csn))
59                return new StreamEncoder(out, lock, Charset.forName(csn));
60        } catch (IllegalCharsetNameException x) { }
61        throw new UnsupportedEncodingException (csn);
62    }
63
64    public static StreamEncoder forOutputStreamWriter(OutputStream out,
65                                                      Object lock,
66                                                      Charset cs)
67    {
68        return new StreamEncoder(out, lock, cs);
69    }
70
71    public static StreamEncoder forOutputStreamWriter(OutputStream out,
72                                                      Object lock,
73                                                      CharsetEncoder enc)
74    {
75        return new StreamEncoder(out, lock, enc);
76    }
77
78
79    // Factory for java.nio.channels.Channels.newWriter
80
81    public static StreamEncoder forEncoder(WritableByteChannel ch,
82                                           CharsetEncoder enc,
83                                           int minBufferCap)
84    {
85        return new StreamEncoder(ch, enc, minBufferCap);
86    }
87
88
89    // -- Public methods corresponding to those in OutputStreamWriter --
90
91    // All synchronization and state/argument checking is done in these public
92    // methods; the concrete stream-encoder subclasses defined below need not
93    // do any such checking.
94
95    public String getEncoding() {
96        if (isOpen())
97            return encodingName();
98        return null;
99    }
100
101    public void flushBuffer() throws IOException {
102        synchronized (lock) {
103            if (isOpen())
104                implFlushBuffer();
105            else
106                throw new IOException("Stream closed");
107        }
108    }
109
110    public void write(int c) throws IOException {
111        char cbuf[] = new char[1];
112        cbuf[0] = (char) c;
113        write(cbuf, 0, 1);
114    }
115
116    public void write(char cbuf[], int off, int len) throws IOException {
117        synchronized (lock) {
118            ensureOpen();
119            if ((off < 0) || (off > cbuf.length) || (len < 0) ||
120                ((off + len) > cbuf.length) || ((off + len) < 0)) {
121                throw new IndexOutOfBoundsException();
122            } else if (len == 0) {
123                return;
124            }
125            implWrite(cbuf, off, len);
126        }
127    }
128
129    public void write(String str, int off, int len) throws IOException {
130        /* Check the len before creating a char buffer */
131        if (len < 0)
132            throw new IndexOutOfBoundsException();
133        char cbuf[] = new char[len];
134        str.getChars(off, off + len, cbuf, 0);
135        write(cbuf, 0, len);
136    }
137
138    public void write(CharBuffer cb) throws IOException {
139        int position = cb.position();
140        try {
141            synchronized (lock) {
142                ensureOpen();
143                implWrite(cb);
144            }
145        } finally {
146            cb.position(position);
147        }
148    }
149
150    public void flush() throws IOException {
151        synchronized (lock) {
152            ensureOpen();
153            implFlush();
154        }
155    }
156
157    public void close() throws IOException {
158        synchronized (lock) {
159            if (closed)
160                return;
161            implClose();
162            closed = true;
163        }
164    }
165
166    private boolean isOpen() {
167        return !closed;
168    }
169
170
171    // -- Charset-based stream encoder impl --
172
173    private Charset cs;
174    private CharsetEncoder encoder;
175    private ByteBuffer bb;
176
177    // Exactly one of these is non-null
178    private final OutputStream out;
179    private WritableByteChannel ch;
180
181    // Leftover first char in a surrogate pair
182    private boolean haveLeftoverChar = false;
183    private char leftoverChar;
184    private CharBuffer lcb = null;
185
186    private StreamEncoder(OutputStream out, Object lock, Charset cs) {
187        this(out, lock,
188         cs.newEncoder()
189         .onMalformedInput(CodingErrorAction.REPLACE)
190         .onUnmappableCharacter(CodingErrorAction.REPLACE));
191    }
192
193    private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
194        super(lock);
195        this.out = out;
196        this.ch = null;
197        this.cs = enc.charset();
198        this.encoder = enc;
199
200        // This path disabled until direct buffers are faster
201        if (false && out instanceof FileOutputStream) {
202                ch = ((FileOutputStream)out).getChannel();
203        if (ch != null)
204                    bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
205        }
206            if (ch == null) {
207        bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
208        }
209    }
210
211    private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
212        this.out = null;
213        this.ch = ch;
214        this.cs = enc.charset();
215        this.encoder = enc;
216        this.bb = ByteBuffer.allocate(mbc < 0
217                                  ? DEFAULT_BYTE_BUFFER_SIZE
218                                  : mbc);
219    }
220
221    private void writeBytes() throws IOException {
222        bb.flip();
223        int lim = bb.limit();
224        int pos = bb.position();
225        assert (pos <= lim);
226        int rem = (pos <= lim ? lim - pos : 0);
227
228            if (rem > 0) {
229        if (ch != null) {
230            if (ch.write(bb) != rem)
231                assert false : rem;
232        } else {
233            out.write(bb.array(), bb.arrayOffset() + pos, rem);
234        }
235        }
236        bb.clear();
237        }
238
239    private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
240        throws IOException
241    {
242        if (!haveLeftoverChar && !endOfInput)
243            return;
244        if (lcb == null)
245            lcb = CharBuffer.allocate(2);
246        else
247            lcb.clear();
248        if (haveLeftoverChar)
249            lcb.put(leftoverChar);
250        if ((cb != null) && cb.hasRemaining())
251            lcb.put(cb.get());
252        lcb.flip();
253        while (lcb.hasRemaining() || endOfInput) {
254            CoderResult cr = encoder.encode(lcb, bb, endOfInput);
255            if (cr.isUnderflow()) {
256                if (lcb.hasRemaining()) {
257                    leftoverChar = lcb.get();
258                    if (cb != null && cb.hasRemaining()) {
259                        lcb.clear();
260                        lcb.put(leftoverChar).put(cb.get()).flip();
261                        continue;
262                    }
263                    return;
264                }
265                break;
266            }
267            if (cr.isOverflow()) {
268                assert bb.position() > 0;
269                writeBytes();
270                continue;
271            }
272            cr.throwException();
273        }
274        haveLeftoverChar = false;
275    }
276
277    void implWrite(char cbuf[], int off, int len)
278        throws IOException
279    {
280        CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
281        implWrite(cb);
282    }
283
284    void implWrite(CharBuffer cb)
285        throws IOException
286    {
287        if (haveLeftoverChar) {
288            flushLeftoverChar(cb, false);
289        }
290
291        while (cb.hasRemaining()) {
292            CoderResult cr = encoder.encode(cb, bb, false);
293            if (cr.isUnderflow()) {
294                assert (cb.remaining() <= 1) : cb.remaining();
295                if (cb.remaining() == 1) {
296                    haveLeftoverChar = true;
297                    leftoverChar = cb.get();
298                }
299                break;
300            }
301            if (cr.isOverflow()) {
302                assert bb.position() > 0;
303                writeBytes();
304                continue;
305            }
306            cr.throwException();
307        }
308    }
309
310    void implFlushBuffer() throws IOException {
311        if (bb.position() > 0)
312        writeBytes();
313    }
314
315    void implFlush() throws IOException {
316        implFlushBuffer();
317        if (out != null)
318        out.flush();
319    }
320
321    void implClose() throws IOException {
322        flushLeftoverChar(null, true);
323        try {
324            for (;;) {
325                CoderResult cr = encoder.flush(bb);
326                if (cr.isUnderflow())
327                    break;
328                if (cr.isOverflow()) {
329                    assert bb.position() > 0;
330                    writeBytes();
331                    continue;
332                }
333                cr.throwException();
334            }
335
336            if (bb.position() > 0)
337                writeBytes();
338            if (ch != null)
339                ch.close();
340            else
341                out.close();
342        } catch (IOException x) {
343            encoder.reset();
344            throw x;
345        }
346    }
347
348    String encodingName() {
349        return ((cs instanceof HistoricallyNamedCharset)
350            ? ((HistoricallyNamedCharset)cs).historicalName()
351            : cs.name());
352    }
353}
354