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(String str, int off, int len)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 
flush()138     public void flush() throws IOException {
139         synchronized (lock) {
140             ensureOpen();
141             implFlush();
142         }
143     }
144 
close()145     public void close() throws IOException {
146         synchronized (lock) {
147             if (!isOpen)
148                 return;
149             implClose();
150             isOpen = false;
151         }
152     }
153 
isOpen()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 
StreamEncoder(OutputStream out, Object lock, Charset cs)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 
StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc)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 
StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc)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 
writeBytes()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 
flushLeftoverChar(CharBuffer cb, boolean endOfInput)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 
implWrite(char cbuf[], int off, int len)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 
implFlushBuffer()289     void implFlushBuffer() throws IOException {
290         if (bb.position() > 0)
291         writeBytes();
292     }
293 
implFlush()294     void implFlush() throws IOException {
295         implFlushBuffer();
296         if (out != null)
297         out.flush();
298     }
299 
implClose()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 
encodingName()327     String encodingName() {
328         return ((cs instanceof HistoricallyNamedCharset)
329             ? ((HistoricallyNamedCharset)cs).historicalName()
330             : cs.name());
331     }
332 }
333