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 
29 package sun.nio.cs;
30 
31 import java.io.*;
32 import java.nio.*;
33 import java.nio.channels.*;
34 import java.nio.charset.*;
35 
36 public class StreamEncoder extends Writer
37 {
38 
39     private static final int DEFAULT_BYTE_BUFFER_SIZE = 8192;
40 
41     private volatile boolean isOpen = true;
42 
ensureOpen()43     private void ensureOpen() throws IOException {
44         if (!isOpen)
45             throw new IOException("Stream closed");
46     }
47 
48     // Factories for java.io.OutputStreamWriter
forOutputStreamWriter(OutputStream out, Object lock, String charsetName)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 
forOutputStreamWriter(OutputStream out, Object lock, Charset cs)64     public static StreamEncoder forOutputStreamWriter(OutputStream out,
65                                                       Object lock,
66                                                       Charset cs)
67     {
68         return new StreamEncoder(out, lock, cs);
69     }
70 
forOutputStreamWriter(OutputStream out, Object lock, CharsetEncoder enc)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 
forEncoder(WritableByteChannel ch, CharsetEncoder enc, int minBufferCap)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 
getEncoding()95     public String getEncoding() {
96         if (isOpen())
97             return encodingName();
98         return null;
99     }
100 
flushBuffer()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 
write(int c)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 
write(char cbuf[], int off, int len)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 
write(CharBuffer cb)129     public void write(CharBuffer cb) throws IOException {
130         int position = cb.position();
131         try {
132             synchronized (lock) {
133                 ensureOpen();
134                 implWrite(cb);
135             }
136         } finally {
137             cb.position(position);
138         }
139     }
140 
write(String str, int off, int len)141     public void write(String str, int off, int len) throws IOException {
142         /* Check the len before creating a char buffer */
143         if (len < 0)
144             throw new IndexOutOfBoundsException();
145         char cbuf[] = new char[len];
146         str.getChars(off, off + len, cbuf, 0);
147         write(cbuf, 0, len);
148     }
149 
flush()150     public void flush() throws IOException {
151         synchronized (lock) {
152             ensureOpen();
153             implFlush();
154         }
155     }
156 
close()157     public void close() throws IOException {
158         synchronized (lock) {
159             if (!isOpen)
160                 return;
161             implClose();
162             isOpen = false;
163         }
164     }
165 
isOpen()166     private boolean isOpen() {
167         return isOpen;
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 
StreamEncoder(OutputStream out, Object lock, Charset cs)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 
StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc)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 
StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc)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 
writeBytes()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 
flushLeftoverChar(CharBuffer cb, boolean endOfInput)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                         flushLeftoverChar(cb, endOfInput);
260                     return;
261                 }
262                 break;
263             }
264             if (cr.isOverflow()) {
265                 assert bb.position() > 0;
266                 writeBytes();
267                 continue;
268             }
269             cr.throwException();
270         }
271         haveLeftoverChar = false;
272     }
273 
implWrite(char[] cbuf, int off, int len)274     void implWrite(char[] cbuf, int off, int len)
275             throws IOException
276     {
277         CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
278         implWrite(cb);
279     }
280 
implWrite(CharBuffer cb)281     void implWrite(CharBuffer cb)
282             throws IOException
283     {
284         if (haveLeftoverChar) {
285             flushLeftoverChar(cb, false);
286         }
287 
288         while (cb.hasRemaining()) {
289             CoderResult cr = encoder.encode(cb, bb, false);
290             if (cr.isUnderflow()) {
291                 assert (cb.remaining() <= 1) : cb.remaining();
292                 if (cb.remaining() == 1) {
293                     haveLeftoverChar = true;
294                     leftoverChar = cb.get();
295                 }
296                 break;
297             }
298             if (cr.isOverflow()) {
299                 assert bb.position() > 0;
300                 writeBytes();
301                 continue;
302             }
303             cr.throwException();
304         }
305     }
306 
implFlushBuffer()307     void implFlushBuffer() throws IOException {
308         if (bb.position() > 0)
309         writeBytes();
310     }
311 
implFlush()312     void implFlush() throws IOException {
313         implFlushBuffer();
314         if (out != null)
315         out.flush();
316     }
317 
implClose()318     void implClose() throws IOException {
319         flushLeftoverChar(null, true);
320         try {
321             for (;;) {
322                 CoderResult cr = encoder.flush(bb);
323                 if (cr.isUnderflow())
324                     break;
325                 if (cr.isOverflow()) {
326                     assert bb.position() > 0;
327                     writeBytes();
328                     continue;
329                 }
330                 cr.throwException();
331             }
332 
333             if (bb.position() > 0)
334                 writeBytes();
335             if (ch != null)
336                 ch.close();
337             else
338                 out.close();
339         } catch (IOException x) {
340             encoder.reset();
341             throw x;
342         }
343     }
344 
encodingName()345     String encodingName() {
346         return ((cs instanceof HistoricallyNamedCharset)
347             ? ((HistoricallyNamedCharset)cs).historicalName()
348             : cs.name());
349     }
350 }
351