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 closed; 42 43 private void ensureOpen() throws IOException { 44 if (closed) 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 write(CharBuffer cb) throws IOException { 139 int position = cb.position(); 140 try { 141 synchronized (lock) { 142 ensureOpen(); 143 implWrite(cb); 144 } 145 } finally { 146 cb.position(position); 147 } 148 } 149 150 public void flush() throws IOException { 151 synchronized (lock) { 152 ensureOpen(); 153 implFlush(); 154 } 155 } 156 157 public void close() throws IOException { 158 synchronized (lock) { 159 if (closed) 160 return; 161 implClose(); 162 closed = true; 163 } 164 } 165 166 private boolean isOpen() { 167 return !closed; 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 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 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 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 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 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 lcb.clear(); 260 lcb.put(leftoverChar).put(cb.get()).flip(); 261 continue; 262 } 263 return; 264 } 265 break; 266 } 267 if (cr.isOverflow()) { 268 assert bb.position() > 0; 269 writeBytes(); 270 continue; 271 } 272 cr.throwException(); 273 } 274 haveLeftoverChar = false; 275 } 276 277 void implWrite(char cbuf[], int off, int len) 278 throws IOException 279 { 280 CharBuffer cb = CharBuffer.wrap(cbuf, off, len); 281 implWrite(cb); 282 } 283 284 void implWrite(CharBuffer cb) 285 throws IOException 286 { 287 if (haveLeftoverChar) { 288 flushLeftoverChar(cb, false); 289 } 290 291 while (cb.hasRemaining()) { 292 CoderResult cr = encoder.encode(cb, bb, false); 293 if (cr.isUnderflow()) { 294 assert (cb.remaining() <= 1) : cb.remaining(); 295 if (cb.remaining() == 1) { 296 haveLeftoverChar = true; 297 leftoverChar = cb.get(); 298 } 299 break; 300 } 301 if (cr.isOverflow()) { 302 assert bb.position() > 0; 303 writeBytes(); 304 continue; 305 } 306 cr.throwException(); 307 } 308 } 309 310 void implFlushBuffer() throws IOException { 311 if (bb.position() > 0) 312 writeBytes(); 313 } 314 315 void implFlush() throws IOException { 316 implFlushBuffer(); 317 if (out != null) 318 out.flush(); 319 } 320 321 void implClose() throws IOException { 322 flushLeftoverChar(null, true); 323 try { 324 for (;;) { 325 CoderResult cr = encoder.flush(bb); 326 if (cr.isUnderflow()) 327 break; 328 if (cr.isOverflow()) { 329 assert bb.position() > 0; 330 writeBytes(); 331 continue; 332 } 333 cr.throwException(); 334 } 335 336 if (bb.position() > 0) 337 writeBytes(); 338 if (ch != null) 339 ch.close(); 340 else 341 out.close(); 342 } catch (IOException x) { 343 encoder.reset(); 344 throw x; 345 } 346 } 347 348 String encodingName() { 349 return ((cs instanceof HistoricallyNamedCharset) 350 ? ((HistoricallyNamedCharset)cs).historicalName() 351 : cs.name()); 352 } 353} 354