001    /* OutputStreamWriter.java -- Writer that converts chars to bytes
002       Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005, 2006  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010     
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package java.io;
040    
041    import gnu.gcj.convert.UnicodeToBytes;
042    import gnu.gcj.convert.CharsetToBytesAdaptor;
043    import java.nio.charset.Charset;
044    import java.nio.charset.CharsetEncoder;
045    
046    /**
047     * This class writes characters to an output stream that is byte oriented
048     * It converts the chars that are written to bytes using an encoding layer,
049     * which is specific to a particular encoding standard.  The desired
050     * encoding can either be specified by name, or if no encoding is specified,
051     * the system default encoding will be used.  The system default encoding
052     * name is determined from the system property <code>file.encoding</code>.
053     * The only encodings that are guaranteed to be available are "8859_1"
054     * (the Latin-1 character set) and "UTF8".  Unfortunately, Java does not
055     * provide a mechanism for listing the encodings that are supported in
056     * a given implementation.
057     * <p>
058     * Here is a list of standard encoding names that may be available:
059     * <p>
060     * <ul>
061     * <li>8859_1 (ISO-8859-1/Latin-1)
062     * <li>8859_2 (ISO-8859-2/Latin-2)
063     * <li>8859_3 (ISO-8859-3/Latin-3)
064     * <li>8859_4 (ISO-8859-4/Latin-4)
065     * <li>8859_5 (ISO-8859-5/Latin-5)
066     * <li>8859_6 (ISO-8859-6/Latin-6)
067     * <li>8859_7 (ISO-8859-7/Latin-7)
068     * <li>8859_8 (ISO-8859-8/Latin-8)
069     * <li>8859_9 (ISO-8859-9/Latin-9)
070     * <li>ASCII (7-bit ASCII)
071     * <li>UTF8 (UCS Transformation Format-8)
072     * <li>More Later
073     * </ul>
074     *
075     * @author Aaron M. Renn (arenn@urbanophile.com)
076     * @author Per Bothner (bothner@cygnus.com)
077     * @date April 17, 1998.  
078     */
079    public class OutputStreamWriter extends Writer
080    {
081      BufferedOutputStream out;
082    
083      /**
084       * This is the byte-character encoder class that does the writing and
085       * translation of characters to bytes before writing to the underlying
086       * class.
087       */
088      UnicodeToBytes converter;
089    
090      /* Temporary buffer. */
091      private char[] work;
092      private int wcount;
093    
094      private OutputStreamWriter(OutputStream out, UnicodeToBytes encoder)
095      {
096        this.out = out instanceof BufferedOutputStream 
097                   ? (BufferedOutputStream) out
098                   : new BufferedOutputStream(out, 250);
099        /* Don't need to call super(out) here as long as the lock gets set. */
100        this.lock = out;
101        this.converter = encoder;
102      }
103    
104      /**
105       * This method initializes a new instance of <code>OutputStreamWriter</code>
106       * to write to the specified stream using a caller supplied character
107       * encoding scheme.  Note that due to a deficiency in the Java language
108       * design, there is no way to determine which encodings are supported.
109       *
110       * @param out The <code>OutputStream</code> to write to
111       * @param encoding_scheme The name of the encoding scheme to use for 
112       * character to byte translation
113       *
114       * @exception UnsupportedEncodingException If the named encoding is 
115       * not available.
116       */
117      public OutputStreamWriter (OutputStream out, String encoding_scheme) 
118        throws UnsupportedEncodingException
119      {
120        this(out, UnicodeToBytes.getEncoder(encoding_scheme));
121      }
122    
123      /**
124       * This method initializes a new instance of <code>OutputStreamWriter</code>
125       * to write to the specified stream using the default encoding.
126       *
127       * @param out The <code>OutputStream</code> to write to
128       */
129      public OutputStreamWriter (OutputStream out)
130      {
131        this(out, UnicodeToBytes.getDefaultEncoder());
132      }
133    
134      /**
135       * This method initializes a new instance of <code>OutputStreamWriter</code>
136       * to write to the specified stream using a given <code>Charset</code>.
137       *
138       * @param out The <code>OutputStream</code> to write to
139       * @param cs The <code>Charset</code> of the encoding to use
140       */
141      public OutputStreamWriter(OutputStream out, Charset cs)
142      {
143        this(out, new CharsetToBytesAdaptor(cs));
144      }
145    
146      /**
147       * This method initializes a new instance of <code>OutputStreamWriter</code>
148       * to write to the specified stream using a given
149       * <code>CharsetEncoder</code>.
150       *
151       * @param out The <code>OutputStream</code> to write to
152       * @param enc The <code>CharsetEncoder</code> to encode the output with
153       */
154      public OutputStreamWriter(OutputStream out, CharsetEncoder enc)
155      {
156        this(out, new CharsetToBytesAdaptor(enc));
157      }
158    
159      /**
160       * This method closes this stream, and the underlying 
161       * <code>OutputStream</code>
162       *
163       * @exception IOException If an error occurs
164       */
165      public void close () throws IOException
166      {
167        synchronized (lock)
168          {
169            if (out != null)
170              {
171                converter.setFinished();
172                flush();
173                out.close();
174                out = null;
175              }
176            work = null;
177          }
178      }
179    
180      /**
181       * This method returns the name of the character encoding scheme currently
182       * in use by this stream.  If the stream has been closed, then this method
183       * may return <code>null</code>.
184       *
185       * @return The encoding scheme name
186       */
187      public String getEncoding ()
188      {
189        return out != null ? converter.getName() : null;
190      }
191    
192      /**
193       * This method flushes any buffered bytes to the underlying output sink.
194       *
195       * @exception IOException If an error occurs
196       */
197      public void flush () throws IOException
198      {
199        synchronized (lock)
200          {
201            if (out == null)
202              throw new IOException("Stream closed");
203    
204            // Always write -- if we are close()ing then we want to make
205            // sure the converter is flushed.
206            if (work == null)
207              work = new char[100];
208            writeChars(work, 0, wcount);
209            wcount = 0;
210    
211            out.flush();
212          }
213      }
214    
215      /**
216       * This method writes <code>count</code> characters from the specified
217       * array to the output stream starting at position <code>offset</code>
218       * into the array.
219       *
220       * @param buf The array of character to write from
221       * @param offset The offset into the array to start writing chars from
222       * @param count The number of chars to write.
223       *
224       * @exception IOException If an error occurs
225       */
226      public void write (char[] buf, int offset, int count) throws IOException
227      {
228        synchronized (lock)
229          {
230            if (out == null)
231              throw new IOException("Stream closed");
232    
233            if (wcount > 0)
234              {
235                writeChars(work, 0, wcount);
236                wcount = 0;
237              }
238            writeChars(buf, offset, count);
239          }
240      }
241    
242      /*
243       * Writes characters through to the inferior BufferedOutputStream.
244       * Ignores wcount and the work buffer.
245       */
246      private void writeChars(char[] buf, int offset, int count)
247        throws IOException
248      {
249        do
250          {
251            // We must flush if out.count == out.buf.length.
252            // It is probably a good idea to flush if out.buf is almost full.
253            // This test is an approximation for "almost full".
254            if (out.count + count >= out.buf.length)
255              {
256                out.flush();
257                if (out.count != 0)
258                  throw new IOException("unable to flush output byte buffer");
259              }
260            converter.setOutput(out.buf, out.count);
261            int converted = converter.write(buf, offset, count);
262            // Must set this before we flush the output stream, because
263            // flushing will reset 'out.count'.
264            out.count = converter.count;
265            // Flush if we cannot make progress.
266            if (converted == 0 && out.count == converter.count)
267              {
268                out.flush();
269                if (out.count != 0)
270                  throw new IOException("unable to flush output byte buffer");
271              }
272            offset += converted;
273            count -= converted;
274          }
275        while (count > 0 || converter.havePendingBytes());
276      }
277    
278      /**
279       * This method writes <code>count</code> bytes from the specified 
280       * <code>String</code> starting at position <code>offset</code> into the
281       * <code>String</code>.
282       *
283       * @param str The <code>String</code> to write chars from
284       * @param offset The position in the <code>String</code> to start 
285       * writing chars from
286       * @param count The number of chars to write
287       *
288       * @exception IOException If an error occurs
289       */
290      public void write (String str, int offset, int count) throws IOException
291      {
292        synchronized (lock)
293          {
294            if (out == null)
295              throw new IOException("Stream closed");
296    
297            if (work == null)
298              work = new char[100];
299            int wlength = work.length;
300            while (count > 0)
301              {
302                int size = count;
303                if (wcount + size > wlength)
304                  {
305                    if (2*wcount > wlength)
306                      {
307                        writeChars(work, 0, wcount);
308                        wcount = 0;
309                      }
310                    if (wcount + size > wlength)
311                      size = wlength - wcount;
312                  }
313                str.getChars(offset, offset+size, work, wcount);
314                offset += size;
315                count -= size;
316                wcount += size;
317              }
318          }
319      }
320    
321      /**
322       * This method writes a single character to the output stream.
323       *
324       * @param ch The char to write, passed as an int.
325       *
326       * @exception IOException If an error occurs
327       */
328      public void write (int ch) throws IOException
329      {
330        synchronized (lock)
331          {
332            if (out == null)
333              throw new IOException("Stream closed");
334    
335            if (work == null)
336              work = new char[100];
337            if (wcount >= work.length)
338              {
339                writeChars(work, 0, wcount);
340                wcount = 0;
341              }
342            work[wcount++] = (char) ch;
343          }
344      }
345    
346    } // class OutputStreamWriter
347