blob: 9d12fef2a7cb4a8fc7f72f01bf6567a064ff1334 [file] [log] [blame]
J. Duke319a3b92007-12-01 00:00:00 +00001/*
2 * Copyright 2001-2005 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any 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}