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