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