View Javadoc
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  
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 }