1 /*
  2  * Copyright (c) 2001, 2021, 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 package sun.nio.cs;
 27 
 28 import java.io.IOException;
 29 import java.io.OutputStream;
 30 import java.io.UnsupportedEncodingException;
 31 import java.io.Writer;
 32 import java.nio.ByteBuffer;
 33 import java.nio.CharBuffer;
 34 import java.nio.channels.WritableByteChannel;
 35 import java.nio.charset.Charset;
 36 import java.nio.charset.CharsetEncoder;
 37 import java.nio.charset.CoderResult;
 38 import java.nio.charset.CodingErrorAction;
 39 import java.nio.charset.IllegalCharsetNameException;
 40 import java.nio.charset.UnsupportedCharsetException;
 41 import jdk.internal.misc.InternalLock;
 42 
 43 public final class StreamEncoder extends Writer {
 44 
 45     private static final int INITIAL_BYTE_BUFFER_CAPACITY = 512;
 46     private static final int MAX_BYTE_BUFFER_CAPACITY = 8192;
 47 
 48     private volatile boolean closed;
 49 
 50     private void ensureOpen() throws IOException {
 51         if (closed)
 52             throw new IOException("Stream closed");
 53     }
 54 
 55     // Factories for java.io.OutputStreamWriter
 56     public static StreamEncoder forOutputStreamWriter(OutputStream out,
 57                                                       Object lock,
 58                                                       String charsetName)
 59         throws UnsupportedEncodingException
 60     {
 61         String csn = charsetName;
 62         if (csn == null) {
 63             csn = Charset.defaultCharset().name();
 64         }
 65         try {
 66             return new StreamEncoder(out, lock, Charset.forName(csn));
 67         } catch (IllegalCharsetNameException | UnsupportedCharsetException x) {
 68             throw new UnsupportedEncodingException (csn);
 69         }
 70     }
 71 
 72     public static StreamEncoder forOutputStreamWriter(OutputStream out,
 73                                                       Object lock,
 74                                                       Charset cs)
 75     {
 76         return new StreamEncoder(out, lock, cs);
 77     }
 78 
 79     public static StreamEncoder forOutputStreamWriter(OutputStream out,
 80                                                       Object lock,
 81                                                       CharsetEncoder enc)
 82     {
 83         return new StreamEncoder(out, lock, enc);
 84     }
 85 
 86 
 87     // Factory for java.nio.channels.Channels.newWriter
 88 
 89     public static StreamEncoder forEncoder(WritableByteChannel ch,
 90                                            CharsetEncoder enc,
 91                                            int minBufferCap)
 92     {
 93         return new StreamEncoder(ch, enc, minBufferCap);
 94     }
 95 
 96 
 97     // -- Public methods corresponding to those in OutputStreamWriter --
 98 
 99     // All synchronization and state/argument checking is done in these public
100     // methods; the concrete stream-encoder subclasses defined below need not
101     // do any such checking.
102 
103     public String getEncoding() {
104         if (isOpen())
105             return encodingName();
106         return null;
107     }
108 
109     public void flushBuffer() throws IOException {
110         if (lock instanceof InternalLock locker) {
111             locker.lock();
112             try {
113                 lockedFlushBuffer();
114             } finally {
115                 locker.unlock();
116             }
117         } else {
118             synchronized (super.lock) {
119                 lockedFlushBuffer();
120             }
121         }
122     }
123 
124     private void lockedFlushBuffer() throws IOException {
125         if (isOpen())
126             implFlushBuffer();
127         else
128             throw new IOException("Stream closed");
129     }
130 
131     public void write(int c) throws IOException {
132         char[] cbuf = new char[1];
133         cbuf[0] = (char) c;
134         write(cbuf, 0, 1);
135     }
136 
137     public void write(char[] cbuf, int off, int len) throws IOException {
138         if (lock instanceof InternalLock locker) {
139             locker.lock();
140             try {
141                 lockedWrite(cbuf, off, len);
142             } finally {
143                 locker.unlock();
144             }
145         } else {
146             synchronized (super.lock) {
147                 lockedWrite(cbuf, off, len);
148             }
149         }
150     }
151 
152     private void lockedWrite(char[] cbuf, int off, int len) throws IOException {
153         ensureOpen();
154         if ((off < 0) || (off > cbuf.length) || (len < 0) ||
155                 ((off + len) > cbuf.length) || ((off + len) < 0)) {
156             throw new IndexOutOfBoundsException();
157         } else if (len == 0) {
158             return;
159         }
160         implWrite(cbuf, off, len);
161     }
162 
163     public void write(String str, int off, int len) throws IOException {
164         /* Check the len before creating a char buffer */
165         if (len < 0)
166             throw new IndexOutOfBoundsException();
167         char[] cbuf = new char[len];
168         str.getChars(off, off + len, cbuf, 0);
169         write(cbuf, 0, len);
170     }
171 
172     public void write(CharBuffer cb) throws IOException {
173         int position = cb.position();
174         try {
175             if (lock instanceof InternalLock locker) {
176                 locker.lock();
177                 try {
178                     lockedWrite(cb);
179                 } finally {
180                     locker.unlock();
181                 }
182             } else {
183                 synchronized (super.lock) {
184                     lockedWrite(cb);
185                 }
186             }
187         } finally {
188             cb.position(position);
189         }
190     }
191 
192     private void lockedWrite(CharBuffer cb) throws IOException {
193         ensureOpen();
194         implWrite(cb);
195     }
196 
197     public void flush() throws IOException {
198         if (lock instanceof InternalLock locker) {
199             locker.lock();
200             try {
201                 lockedFlush();
202             } finally {
203                 locker.unlock();
204             }
205         } else {
206             synchronized (lock) {
207                 lockedFlush();
208             }
209         }
210     }
211 
212     private void lockedFlush() throws IOException {
213         ensureOpen();
214         implFlush();
215     }
216 
217     public void close() throws IOException {
218         if (lock instanceof InternalLock locker) {
219             locker.lock();
220             try {
221                 lockedClose();
222             } finally {
223                 locker.unlock();
224             }
225         } else {
226             synchronized (lock) {
227                 lockedClose();
228             }
229         }
230     }
231 
232     private void lockedClose() throws IOException {
233         if (closed)
234             return;
235         try {
236             implClose();
237         } finally {
238             closed = true;
239         }
240     }
241 
242     private boolean isOpen() {
243         return !closed;
244     }
245 
246 
247     // -- Charset-based stream encoder impl --
248 
249     private final Charset cs;
250     private final CharsetEncoder encoder;
251     private ByteBuffer bb;
252     private final int maxBufferCapacity;
253 
254     // Exactly one of these is non-null
255     private final OutputStream out;
256     private final WritableByteChannel ch;
257 
258     // Leftover first char in a surrogate pair
259     private boolean haveLeftoverChar = false;
260     private char leftoverChar;
261     private CharBuffer lcb = null;
262 
263     private StreamEncoder(OutputStream out, Object lock, Charset cs) {
264         this(out, lock,
265             cs.newEncoder()
266                 .onMalformedInput(CodingErrorAction.REPLACE)
267                 .onUnmappableCharacter(CodingErrorAction.REPLACE));
268     }
269 
270     private StreamEncoder(OutputStream out, Object lock, CharsetEncoder enc) {
271         super(lock);
272         this.out = out;
273         this.ch = null;
274         this.cs = enc.charset();
275         this.encoder = enc;
276 
277         this.bb = ByteBuffer.allocate(INITIAL_BYTE_BUFFER_CAPACITY);
278         this.maxBufferCapacity = MAX_BYTE_BUFFER_CAPACITY;
279     }
280 
281     private StreamEncoder(WritableByteChannel ch, CharsetEncoder enc, int mbc) {
282         this.out = null;
283         this.ch = ch;
284         this.cs = enc.charset();
285         this.encoder = enc;
286 
287         if (mbc > 0) {
288             this.bb = ByteBuffer.allocate(mbc);
289             this.maxBufferCapacity = mbc;
290         } else {
291             this.bb = ByteBuffer.allocate(INITIAL_BYTE_BUFFER_CAPACITY);
292             this.maxBufferCapacity = MAX_BYTE_BUFFER_CAPACITY;
293         }
294     }
295 
296     private void writeBytes() throws IOException {
297         bb.flip();
298         int lim = bb.limit();
299         int pos = bb.position();
300         assert (pos <= lim);
301         int rem = (pos <= lim ? lim - pos : 0);
302 
303         if (rem > 0) {
304             if (ch != null) {
305                 int wc = ch.write(bb);
306                 assert wc == rem : rem;
307             } else {
308                 out.write(bb.array(), bb.arrayOffset() + pos, rem);
309             }
310         }
311         bb.clear();
312     }
313 
314     private void flushLeftoverChar(CharBuffer cb, boolean endOfInput)
315         throws IOException
316     {
317         if (!haveLeftoverChar && !endOfInput)
318             return;
319         if (lcb == null)
320             lcb = CharBuffer.allocate(2);
321         else
322             lcb.clear();
323         if (haveLeftoverChar)
324             lcb.put(leftoverChar);
325         if ((cb != null) && cb.hasRemaining())
326             lcb.put(cb.get());
327         lcb.flip();
328         while (lcb.hasRemaining() || endOfInput) {
329             CoderResult cr = encoder.encode(lcb, bb, endOfInput);
330             if (cr.isUnderflow()) {
331                 if (lcb.hasRemaining()) {
332                     leftoverChar = lcb.get();
333                     if (cb != null && cb.hasRemaining()) {
334                         lcb.clear();
335                         lcb.put(leftoverChar).put(cb.get()).flip();
336                         continue;
337                     }
338                     return;
339                 }
340                 break;
341             }
342             if (cr.isOverflow()) {
343                 assert bb.position() > 0;
344                 writeBytes();
345                 continue;
346             }
347             cr.throwException();
348         }
349         haveLeftoverChar = false;
350     }
351 
352     void implWrite(char[] cbuf, int off, int len)
353         throws IOException
354     {
355         CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
356         implWrite(cb);
357     }
358 
359     void implWrite(CharBuffer cb)
360         throws IOException
361     {
362         if (haveLeftoverChar) {
363             flushLeftoverChar(cb, false);
364         }
365 
366         growByteBufferIfNeeded(cb.remaining());
367 
368         while (cb.hasRemaining()) {
369             CoderResult cr = encoder.encode(cb, bb, false);
370             if (cr.isUnderflow()) {
371                 assert (cb.remaining() <= 1) : cb.remaining();
372                 if (cb.remaining() == 1) {
373                     haveLeftoverChar = true;
374                     leftoverChar = cb.get();
375                 }
376                 break;
377             }
378             if (cr.isOverflow()) {
379                 assert bb.position() > 0;
380                 writeBytes();
381                 continue;
382             }
383             cr.throwException();
384         }
385     }
386 
387     /**
388      * Grows bb to a capacity to allow len characters be encoded.
389      */
390     void growByteBufferIfNeeded(int len) throws IOException {
391         int cap = bb.capacity();
392         if (cap < maxBufferCapacity) {
393             int maxBytes = len * Math.round(encoder.maxBytesPerChar());
394             int newCap = Math.min(maxBytes, maxBufferCapacity);
395             if (newCap > cap) {
396                 implFlushBuffer();
397                 bb = ByteBuffer.allocate(newCap);
398             }
399         }
400     }
401 
402     void implFlushBuffer() throws IOException {
403         if (bb.position() > 0) {
404             writeBytes();
405         }
406     }
407 
408     void implFlush() throws IOException {
409         implFlushBuffer();
410         if (out != null) {
411             out.flush();
412         }
413     }
414 
415     void implClose() throws IOException {
416         flushLeftoverChar(null, true);
417         try {
418             for (;;) {
419                 CoderResult cr = encoder.flush(bb);
420                 if (cr.isUnderflow())
421                     break;
422                 if (cr.isOverflow()) {
423                     assert bb.position() > 0;
424                     writeBytes();
425                     continue;
426                 }
427                 cr.throwException();
428             }
429 
430             if (bb.position() > 0)
431                 writeBytes();
432             if (ch != null)
433                 ch.close();
434             else {
435                 try {
436                     out.flush();
437                 } finally {
438                     out.close();
439                 }
440             }
441         } catch (IOException x) {
442             encoder.reset();
443             throw x;
444         }
445     }
446 
447     String encodingName() {
448         return ((cs instanceof HistoricallyNamedCharset)
449             ? ((HistoricallyNamedCharset)cs).historicalName()
450             : cs.name());
451     }
452 }