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 isOpen = true;
42
43    private void ensureOpen() throws IOException {
44        if (!isOpen)
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 flush() throws IOException {
139        synchronized (lock) {
140            ensureOpen();
141            implFlush();
142        }
143    }
144
145    public void close() throws IOException {
146        synchronized (lock) {
147            if (!isOpen)
148                return;
149            implClose();
150            isOpen = false;
151        }
152    }
153
154    private boolean isOpen() {
155        return isOpen;
156    }
157
158
159    // -- Charset-based stream encoder impl --
160
161    private Charset cs;
162    private CharsetEncoder encoder;
163    private ByteBuffer bb;
164
165    // Exactly one of these is non-null
166    private final OutputStream out;
167    private WritableByteChannel ch;
168
169    // Leftover first char in a surrogate pair
170    private boolean haveLeftoverChar = false;
171    private char leftoverChar;
172    private CharBuffer lcb = null;
173
174    private StreamEncoder(OutputStream out, Object lock, Charset cs) {
175        this(out, lock,
176         cs.newEncoder()
177         .onMalformedInput(CodingErrorAction.REPLACE)
178         .onUnmappableCharacter(CodingErrorAction.REPLACE));
179    }
180
181    private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
182        super(lock);
183        this.out = out;
184        this.ch = null;
185        this.cs = enc.charset();
186        this.encoder = enc;
187
188        // This path disabled until direct buffers are faster
189        if (false && out instanceof FileOutputStream) {
190                ch = ((FileOutputStream)out).getChannel();
191        if (ch != null)
192                    bb = ByteBuffer.allocateDirect(DEFAULT_BYTE_BUFFER_SIZE);
193        }
194            if (ch == null) {
195        bb = ByteBuffer.allocate(DEFAULT_BYTE_BUFFER_SIZE);
196        }
197    }
198
199    private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
200        this.out = null;
201        this.ch = ch;
202        this.cs = enc.charset();
203        this.encoder = enc;
204        this.bb = ByteBuffer.allocate(mbc < 0
205                                  ? DEFAULT_BYTE_BUFFER_SIZE
206                                  : mbc);
207    }
208
209    private void writeBytes() throws IOException {
210        bb.flip();
211        int lim = bb.limit();
212        int pos = bb.position();
213        assert (pos <= lim);
214        int rem = (pos <= lim ? lim - pos : 0);
215
216            if (rem > 0) {
217        if (ch != null) {
218            if (ch.write(bb) != rem)
219                assert false : rem;
220        } else {
221            out.write(bb.array(), bb.arrayOffset() + pos, rem);
222        }
223        }
224        bb.clear();
225        }
226
227    private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
228        throws IOException
229    {
230        if (!haveLeftoverChar && !endOfInput)
231            return;
232        if (lcb == null)
233            lcb = CharBuffer.allocate(2);
234        else
235            lcb.clear();
236        if (haveLeftoverChar)
237            lcb.put(leftoverChar);
238        if ((cb != null) && cb.hasRemaining())
239            lcb.put(cb.get());
240        lcb.flip();
241        while (lcb.hasRemaining() || endOfInput) {
242            CoderResult cr = encoder.encode(lcb, bb, endOfInput);
243            if (cr.isUnderflow()) {
244                if (lcb.hasRemaining()) {
245                    leftoverChar = lcb.get();
246                    if (cb != null && cb.hasRemaining())
247                        flushLeftoverChar(cb, endOfInput);
248                    return;
249                }
250                break;
251            }
252            if (cr.isOverflow()) {
253                assert bb.position() > 0;
254                writeBytes();
255                continue;
256            }
257            cr.throwException();
258        }
259        haveLeftoverChar = false;
260    }
261
262    void implWrite(char cbuf[], int off, int len)
263        throws IOException
264    {
265        CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
266
267        if (haveLeftoverChar)
268        flushLeftoverChar(cb, false);
269
270        while (cb.hasRemaining()) {
271        CoderResult cr = encoder.encode(cb, bb, false);
272        if (cr.isUnderflow()) {
273           assert (cb.remaining() <= 1) : cb.remaining();
274           if (cb.remaining() == 1) {
275                haveLeftoverChar = true;
276                leftoverChar = cb.get();
277            }
278            break;
279        }
280        if (cr.isOverflow()) {
281            assert bb.position() > 0;
282            writeBytes();
283            continue;
284        }
285        cr.throwException();
286        }
287    }
288
289    void implFlushBuffer() throws IOException {
290        if (bb.position() > 0)
291        writeBytes();
292    }
293
294    void implFlush() throws IOException {
295        implFlushBuffer();
296        if (out != null)
297        out.flush();
298    }
299
300    void implClose() throws IOException {
301        flushLeftoverChar(null, true);
302        try {
303            for (;;) {
304                CoderResult cr = encoder.flush(bb);
305                if (cr.isUnderflow())
306                    break;
307                if (cr.isOverflow()) {
308                    assert bb.position() > 0;
309                    writeBytes();
310                    continue;
311                }
312                cr.throwException();
313            }
314
315            if (bb.position() > 0)
316                writeBytes();
317            if (ch != null)
318                ch.close();
319            else
320                out.close();
321        } catch (IOException x) {
322            encoder.reset();
323            throw x;
324        }
325    }
326
327    String encodingName() {
328        return ((cs instanceof HistoricallyNamedCharset)
329            ? ((HistoricallyNamedCharset)cs).historicalName()
330            : cs.name());
331    }
332}
333