OutputStreamWriter.java 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. /* OutputStreamWriter.java -- Writer that converts chars to bytes
  2. Copyright (C) 1998, 1999, 2000, 2001, 2003, 2005 Free Software Foundation, Inc.
  3. This file is part of GNU Classpath.
  4. GNU Classpath is free software; you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation; either version 2, or (at your option)
  7. any later version.
  8. GNU Classpath is distributed in the hope that it will be useful, but
  9. WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with GNU Classpath; see the file COPYING. If not, write to the
  14. Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  15. 02110-1301 USA.
  16. Linking this library statically or dynamically with other modules is
  17. making a combined work based on this library. Thus, the terms and
  18. conditions of the GNU General Public License cover the whole
  19. combination.
  20. As a special exception, the copyright holders of this library give you
  21. permission to link this library with independent modules to produce an
  22. executable, regardless of the license terms of these independent
  23. modules, and to copy and distribute the resulting executable under
  24. terms of your choice, provided that you also meet, for each linked
  25. independent module, the terms and conditions of the license of that
  26. module. An independent module is a module which is not derived from
  27. or based on this library. If you modify this library, you may extend
  28. this exception to your version of the library, but you are not
  29. obligated to do so. If you do not wish to do so, delete this
  30. exception statement from your version. */
  31. package java.io;
  32. import gnu.java.nio.charset.EncodingHelper;
  33. import java.nio.ByteBuffer;
  34. import java.nio.CharBuffer;
  35. import java.nio.charset.CharacterCodingException;
  36. import java.nio.charset.Charset;
  37. import java.nio.charset.CharsetEncoder;
  38. import java.nio.charset.CodingErrorAction;
  39. import java.nio.charset.MalformedInputException;
  40. /**
  41. * This class writes characters to an output stream that is byte oriented
  42. * It converts the chars that are written to bytes using an encoding layer,
  43. * which is specific to a particular encoding standard. The desired
  44. * encoding can either be specified by name, or if no encoding is specified,
  45. * the system default encoding will be used. The system default encoding
  46. * name is determined from the system property <code>file.encoding</code>.
  47. * The only encodings that are guaranteed to be available are "8859_1"
  48. * (the Latin-1 character set) and "UTF8". Unfortunately, Java does not
  49. * provide a mechanism for listing the encodings that are supported in
  50. * a given implementation.
  51. * <p>
  52. * Here is a list of standard encoding names that may be available:
  53. * <p>
  54. * <ul>
  55. * <li>8859_1 (ISO-8859-1/Latin-1)
  56. * <li>8859_2 (ISO-8859-2/Latin-2)
  57. * <li>8859_3 (ISO-8859-3/Latin-3)
  58. * <li>8859_4 (ISO-8859-4/Latin-4)
  59. * <li>8859_5 (ISO-8859-5/Latin-5)
  60. * <li>8859_6 (ISO-8859-6/Latin-6)
  61. * <li>8859_7 (ISO-8859-7/Latin-7)
  62. * <li>8859_8 (ISO-8859-8/Latin-8)
  63. * <li>8859_9 (ISO-8859-9/Latin-9)
  64. * <li>ASCII (7-bit ASCII)
  65. * <li>UTF8 (UCS Transformation Format-8)
  66. * <li>More Later
  67. * </ul>
  68. *
  69. * @author Aaron M. Renn (arenn@urbanophile.com)
  70. * @author Per Bothner (bothner@cygnus.com)
  71. * @date April 17, 1998.
  72. */
  73. public class OutputStreamWriter extends Writer
  74. {
  75. /**
  76. * The output stream.
  77. */
  78. private OutputStream out;
  79. /**
  80. * The charset encoder.
  81. */
  82. private final CharsetEncoder encoder;
  83. /**
  84. * java.io canonical name of the encoding.
  85. */
  86. private final String encodingName;
  87. /**
  88. * Buffer output before character conversion as it has costly overhead.
  89. */
  90. private final CharBuffer outputBuffer;
  91. private final static int BUFFER_SIZE = 1024;
  92. /**
  93. * This method initializes a new instance of <code>OutputStreamWriter</code>
  94. * to write to the specified stream using a caller supplied character
  95. * encoding scheme. Note that due to a deficiency in the Java language
  96. * design, there is no way to determine which encodings are supported.
  97. *
  98. * @param out The <code>OutputStream</code> to write to
  99. * @param encoding_scheme The name of the encoding scheme to use for
  100. * character to byte translation
  101. *
  102. * @exception UnsupportedEncodingException If the named encoding is
  103. * not available.
  104. */
  105. public OutputStreamWriter (OutputStream out, String encoding_scheme)
  106. throws UnsupportedEncodingException
  107. {
  108. CharsetEncoder encoder;
  109. String encodingName;
  110. this.out = out;
  111. outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
  112. try
  113. {
  114. // Don't use NIO if avoidable
  115. if(EncodingHelper.isISOLatin1(encoding_scheme))
  116. {
  117. encodingName = "ISO8859_1";
  118. encoder = null;
  119. }
  120. else
  121. {
  122. /*
  123. * Workaround for encodings with a byte-order-mark.
  124. * We only want to write it once per stream.
  125. */
  126. try
  127. {
  128. if(encoding_scheme.equalsIgnoreCase("UnicodeBig") ||
  129. encoding_scheme.equalsIgnoreCase("UTF-16") ||
  130. encoding_scheme.equalsIgnoreCase("UTF16"))
  131. {
  132. encoding_scheme = "UTF-16BE";
  133. out.write((byte)0xFE);
  134. out.write((byte)0xFF);
  135. }
  136. else if(encoding_scheme.equalsIgnoreCase("UnicodeLittle"))
  137. {
  138. encoding_scheme = "UTF-16LE";
  139. out.write((byte)0xFF);
  140. out.write((byte)0xFE);
  141. }
  142. }
  143. catch(IOException ioe)
  144. {
  145. }
  146. Charset cs = EncodingHelper.getCharset(encoding_scheme);
  147. if(cs == null)
  148. throw new UnsupportedEncodingException("Encoding "+encoding_scheme+
  149. " unknown");
  150. encoder = cs.newEncoder();
  151. encodingName = EncodingHelper.getOldCanonical(cs.name());
  152. encoder.onMalformedInput(CodingErrorAction.REPLACE);
  153. encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
  154. }
  155. }
  156. catch(RuntimeException e)
  157. {
  158. // Default to ISO Latin-1, will happen if this is called, for instance,
  159. // before the NIO provider is loadable.
  160. encoder = null;
  161. encodingName = "ISO8859_1";
  162. }
  163. this.encoder = encoder;
  164. this.encodingName = encodingName;
  165. }
  166. /**
  167. * This method initializes a new instance of <code>OutputStreamWriter</code>
  168. * to write to the specified stream using the default encoding.
  169. *
  170. * @param out The <code>OutputStream</code> to write to
  171. */
  172. public OutputStreamWriter (OutputStream out)
  173. {
  174. CharsetEncoder encoder;
  175. String encodingName;
  176. this.out = out;
  177. outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
  178. try
  179. {
  180. String encoding = System.getProperty("file.encoding");
  181. Charset cs = Charset.forName(encoding);
  182. encoder = cs.newEncoder();
  183. encodingName = EncodingHelper.getOldCanonical(cs.name());
  184. }
  185. catch(RuntimeException e)
  186. {
  187. encoder = null;
  188. encodingName = "ISO8859_1";
  189. }
  190. if(encoder != null)
  191. {
  192. encoder.onMalformedInput(CodingErrorAction.REPLACE);
  193. encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
  194. }
  195. this.encoder = encoder;
  196. this.encodingName = encodingName;
  197. }
  198. /**
  199. * This method initializes a new instance of <code>OutputStreamWriter</code>
  200. * to write to the specified stream using a given <code>Charset</code>.
  201. *
  202. * @param out The <code>OutputStream</code> to write to
  203. * @param cs The <code>Charset</code> of the encoding to use
  204. *
  205. * @since 1.5
  206. */
  207. public OutputStreamWriter(OutputStream out, Charset cs)
  208. {
  209. this.out = out;
  210. encoder = cs.newEncoder();
  211. encoder.onMalformedInput(CodingErrorAction.REPLACE);
  212. encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
  213. outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
  214. encodingName = EncodingHelper.getOldCanonical(cs.name());
  215. }
  216. /**
  217. * This method initializes a new instance of <code>OutputStreamWriter</code>
  218. * to write to the specified stream using a given
  219. * <code>CharsetEncoder</code>.
  220. *
  221. * @param out The <code>OutputStream</code> to write to
  222. * @param enc The <code>CharsetEncoder</code> to encode the output with
  223. *
  224. * @since 1.5
  225. */
  226. public OutputStreamWriter(OutputStream out, CharsetEncoder enc)
  227. {
  228. this.out = out;
  229. encoder = enc;
  230. outputBuffer = CharBuffer.allocate(BUFFER_SIZE);
  231. Charset cs = enc.charset();
  232. if (cs == null)
  233. encodingName = "US-ASCII";
  234. else
  235. encodingName = EncodingHelper.getOldCanonical(cs.name());
  236. }
  237. /**
  238. * This method closes this stream, and the underlying
  239. * <code>OutputStream</code>
  240. *
  241. * @exception IOException If an error occurs
  242. */
  243. public void close () throws IOException
  244. {
  245. if(out == null)
  246. return;
  247. flush();
  248. out.close ();
  249. out = null;
  250. }
  251. /**
  252. * This method returns the name of the character encoding scheme currently
  253. * in use by this stream. If the stream has been closed, then this method
  254. * may return <code>null</code>.
  255. *
  256. * @return The encoding scheme name
  257. */
  258. public String getEncoding ()
  259. {
  260. return out != null ? encodingName : null;
  261. }
  262. /**
  263. * This method flushes any buffered bytes to the underlying output sink.
  264. *
  265. * @exception IOException If an error occurs
  266. */
  267. public void flush () throws IOException
  268. {
  269. if(out != null){
  270. if(outputBuffer != null){
  271. char[] buf = new char[outputBuffer.position()];
  272. if(buf.length > 0){
  273. outputBuffer.flip();
  274. outputBuffer.get(buf);
  275. writeConvert(buf, 0, buf.length);
  276. outputBuffer.clear();
  277. }
  278. }
  279. out.flush ();
  280. }
  281. }
  282. /**
  283. * This method writes <code>count</code> characters from the specified
  284. * array to the output stream starting at position <code>offset</code>
  285. * into the array.
  286. *
  287. * @param buf The array of character to write from
  288. * @param offset The offset into the array to start writing chars from
  289. * @param count The number of chars to write.
  290. *
  291. * @exception IOException If an error occurs
  292. */
  293. public void write (char[] buf, int offset, int count) throws IOException
  294. {
  295. if(out == null)
  296. throw new IOException("Stream is closed.");
  297. if(buf == null)
  298. throw new IOException("Buffer is null.");
  299. if(outputBuffer != null)
  300. {
  301. if(count >= outputBuffer.remaining())
  302. {
  303. int r = outputBuffer.remaining();
  304. outputBuffer.put(buf, offset, r);
  305. writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
  306. outputBuffer.clear();
  307. offset += r;
  308. count -= r;
  309. // if the remaining bytes is larger than the whole buffer,
  310. // just don't buffer.
  311. if(count >= outputBuffer.remaining()){
  312. writeConvert(buf, offset, count);
  313. return;
  314. }
  315. }
  316. outputBuffer.put(buf, offset, count);
  317. } else writeConvert(buf, offset, count);
  318. }
  319. /**
  320. * Converts and writes characters.
  321. */
  322. private void writeConvert (char[] buf, int offset, int count)
  323. throws IOException
  324. {
  325. if(encoder == null)
  326. {
  327. byte[] b = new byte[count];
  328. for(int i=0;i<count;i++)
  329. b[i] = nullConversion(buf[offset+i]);
  330. out.write(b);
  331. } else {
  332. try {
  333. ByteBuffer output = encoder.encode(CharBuffer.wrap(buf,offset,count));
  334. encoder.reset();
  335. if(output.hasArray())
  336. out.write(output.array());
  337. else
  338. {
  339. byte[] outbytes = new byte[output.remaining()];
  340. output.get(outbytes);
  341. out.write(outbytes);
  342. }
  343. } catch(IllegalStateException e) {
  344. throw new IOException("Internal error.");
  345. } catch(MalformedInputException e) {
  346. throw new IOException("Invalid character sequence.");
  347. } catch(CharacterCodingException e) {
  348. throw new IOException("Unmappable character.");
  349. }
  350. }
  351. }
  352. private byte nullConversion(char c) {
  353. return (byte)((c <= 0xFF)?c:'?');
  354. }
  355. /**
  356. * This method writes <code>count</code> bytes from the specified
  357. * <code>String</code> starting at position <code>offset</code> into the
  358. * <code>String</code>.
  359. *
  360. * @param str The <code>String</code> to write chars from
  361. * @param offset The position in the <code>String</code> to start
  362. * writing chars from
  363. * @param count The number of chars to write
  364. *
  365. * @exception IOException If an error occurs
  366. */
  367. public void write (String str, int offset, int count) throws IOException
  368. {
  369. if(str == null)
  370. throw new IOException("String is null.");
  371. write(str.toCharArray(), offset, count);
  372. }
  373. /**
  374. * This method writes a single character to the output stream.
  375. *
  376. * @param ch The char to write, passed as an int.
  377. *
  378. * @exception IOException If an error occurs
  379. */
  380. public void write (int ch) throws IOException
  381. {
  382. // No buffering, no encoding ... just pass through
  383. if (encoder == null && outputBuffer == null) {
  384. out.write(nullConversion((char)ch));
  385. } else {
  386. if (outputBuffer != null) {
  387. if (outputBuffer.remaining() == 0) {
  388. writeConvert(outputBuffer.array(), 0, BUFFER_SIZE);
  389. outputBuffer.clear();
  390. }
  391. outputBuffer.put((char)ch);
  392. } else {
  393. writeConvert(new char[]{ (char)ch }, 0, 1);
  394. }
  395. }
  396. }
  397. } // class OutputStreamWriter