001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package java.io;
019:
020: import java.nio.ByteBuffer;
021: import java.nio.CharBuffer;
022: import java.nio.charset.Charset;
023: import java.nio.charset.CharsetEncoder;
024: import java.nio.charset.CoderResult;
025: import java.nio.charset.CodingErrorAction;
026: import java.security.AccessController;
027:
028: import org.apache.harmony.luni.util.Msg;
029: import org.apache.harmony.luni.util.PriviAction;
030:
031: /**
032: * OutputStreamWriter is a class for turning a character output stream into a
033: * byte output stream. The conversion of Unicode characters to their byte
034: * equivalents is determined by the converter used. By default, the encoding is
035: * ISO8859_1 (ISO-Latin-1) but can be changed by calling the constructor which
036: * takes an encoding.
037: *
038: * @see InputStreamReader
039: */
040:
041: public class OutputStreamWriter extends Writer {
042:
043: private OutputStream out;
044:
045: private CharsetEncoder encoder;
046:
047: private ByteBuffer bytes = ByteBuffer.allocate(8192);
048:
049: /**
050: * Constructs a new OutputStreamWriter using <code>out</code> as the
051: * OutputStream to write converted characters to. The default character
052: * encoding is used (see class description).
053: *
054: * @param out
055: * the non-null OutputStream to write converted bytes to.
056: */
057: public OutputStreamWriter(OutputStream out) {
058: super (out);
059: this .out = out;
060: String encoding = AccessController
061: .doPrivileged(new PriviAction<String>(
062: "file.encoding", "ISO8859_1")); //$NON-NLS-1$ //$NON-NLS-2$
063: encoder = Charset.forName(encoding).newEncoder();
064: encoder.onMalformedInput(CodingErrorAction.REPLACE);
065: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
066: }
067:
068: /**
069: * Constructs a new OutputStreamWriter using <code>out</code> as the
070: * OutputStream to write converted characters to and <code>enc</code> as
071: * the character encoding. If the encoding cannot be found, an
072: * UnsupportedEncodingException error is thrown.
073: *
074: * @param out
075: * the non-null OutputStream to write converted bytes to.
076: * @param enc
077: * the non-null String describing the desired character encoding.
078: *
079: * @throws UnsupportedEncodingException
080: * if the encoding cannot be found.
081: */
082: public OutputStreamWriter(OutputStream out, final String enc)
083: throws UnsupportedEncodingException {
084: super (out);
085: if (enc == null) {
086: throw new NullPointerException();
087: }
088: this .out = out;
089: try {
090: encoder = Charset.forName(enc).newEncoder();
091: } catch (Exception e) {
092: throw new UnsupportedEncodingException(enc);
093: }
094: encoder.onMalformedInput(CodingErrorAction.REPLACE);
095: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
096: }
097:
098: /**
099: * Constructs a new OutputStreamWriter using <code>out</code> as the
100: * OutputStream to write converted characters to and <code>cs</code> as
101: * the character encoding.
102: *
103: *
104: * @param out
105: * the non-null OutputStream to write converted bytes to.
106: * @param cs
107: * the non-null Charset which specify the character encoding.
108: */
109: public OutputStreamWriter(OutputStream out, Charset cs) {
110: super (out);
111: this .out = out;
112: encoder = cs.newEncoder();
113: encoder.onMalformedInput(CodingErrorAction.REPLACE);
114: encoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
115: }
116:
117: /**
118: * Constructs a new OutputStreamWriter using <code>out</code> as the
119: * OutputStream to write converted characters to and <code>enc</code> as
120: * the character encoding.
121: *
122: * @param out
123: * the non-null OutputStream to write converted bytes to.
124: * @param enc
125: * the non-null CharsetEncoder which used to character encoding.
126: */
127: public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {
128: super (out);
129: enc.charset();
130: this .out = out;
131: encoder = enc;
132: }
133:
134: /**
135: * Close this OutputStreamWriter. This implementation first flushes the
136: * buffer and the target OutputStream. The OutputStream is then closed and
137: * the resources for the buffer and converter are freed.
138: * <p>
139: * Only the first invocation of this method has any effect. Subsequent calls
140: * do no work.
141: *
142: * @throws IOException
143: * If an error occurs attempting to close this
144: * OutputStreamWriter.
145: */
146: @Override
147: public void close() throws IOException {
148: synchronized (lock) {
149: if (encoder != null) {
150: encoder.flush(bytes);
151: flush();
152: out.flush();
153: out.close();
154: encoder = null;
155: bytes = null;
156: }
157: }
158: }
159:
160: /**
161: * Flush this OutputStreamWriter. This implementation ensures all buffered
162: * bytes are written to the target OutputStream. After writing the bytes,
163: * the target OutputStream is then flushed.
164: *
165: * @throws IOException
166: * If an error occurs attempting to flush this
167: * OutputStreamWriter.
168: */
169: @Override
170: public void flush() throws IOException {
171: synchronized (lock) {
172: checkStatus();
173: int position;
174: if ((position = bytes.position()) > 0) {
175: bytes.flip();
176: out.write(bytes.array(), 0, position);
177: bytes.clear();
178: }
179: out.flush();
180: }
181: }
182:
183: private void checkStatus() throws IOException {
184: if (encoder == null) {
185: // K005d=Writer is closed.
186: throw new IOException(Msg.getString("K005d")); //$NON-NLS-1$
187: }
188: }
189:
190: /**
191: * Answer the String which identifies the encoding used to convert
192: * characters to bytes. The value <code>null</code> is returned if this
193: * Writer has been closed.
194: *
195: * @return the String describing the converter or null if this Writer is
196: * closed.
197: */
198:
199: public String getEncoding() {
200: if (encoder == null) {
201: return null;
202: }
203: return InputStreamReader.HistoricalNamesUtil
204: .getHistoricalName(encoder.charset().name());
205: }
206:
207: /**
208: * Writes <code>count</code> characters starting at <code>offset</code>
209: * in <code>buf</code> to this Writer. The characters are immediately
210: * converted to bytes by the character converter and stored in a local
211: * buffer. If the buffer becomes full as a result of this write, this Writer
212: * is flushed.
213: *
214: * @param buf
215: * the non-null array containing characters to write.
216: * @param offset
217: * offset in buf to retrieve characters
218: * @param count
219: * maximum number of characters to write
220: *
221: * @throws IOException
222: * If this OutputStreamWriter has already been closed or some
223: * other IOException occurs.
224: * @throws IndexOutOfBoundsException
225: * If offset or count is outside of bounds.
226: */
227: @Override
228: public void write(char[] buf, int offset, int count)
229: throws IOException {
230: synchronized (lock) {
231: checkStatus();
232: if (offset < 0 || offset > buf.length - count || count < 0) {
233: throw new IndexOutOfBoundsException();
234: }
235: CharBuffer chars = CharBuffer.wrap(buf, offset, count);
236: convert(chars);
237: }
238: }
239:
240: private void convert(CharBuffer chars) throws IOException {
241: CoderResult result = encoder.encode(chars, bytes, true);
242: while (true) {
243: if (result.isError()) {
244: throw new IOException(result.toString());
245: } else if (result.isOverflow()) {
246: // flush the output buffer
247: flush();
248: result = encoder.encode(chars, bytes, true);
249: continue;
250: }
251: break;
252: }
253: }
254:
255: /**
256: * Writes out the character <code>oneChar</code> to this Writer. The
257: * low-order 2 bytes are immediately converted to bytes by the character
258: * converter and stored in a local buffer. If the buffer becomes full as a
259: * result of this write, this Writer is flushed.
260: *
261: * @param oneChar
262: * the character to write
263: *
264: * @throws IOException
265: * If this OutputStreamWriter has already been closed or some
266: * other IOException occurs.
267: */
268: @Override
269: public void write(int oneChar) throws IOException {
270: synchronized (lock) {
271: checkStatus();
272: CharBuffer chars = CharBuffer
273: .wrap(new char[] { (char) oneChar });
274: convert(chars);
275: }
276: }
277:
278: /**
279: * Writes <code>count</code> characters starting at <code>offset</code>
280: * in <code>str</code> to this Writer. The characters are immediately
281: * converted to bytes by the character converter and stored in a local
282: * buffer. If the buffer becomes full as a result of this write, this Writer
283: * is flushed.
284: *
285: * @param str
286: * the non-null String containing characters to write.
287: * @param offset
288: * offset in str to retrieve characters
289: * @param count
290: * maximum number of characters to write
291: *
292: * @throws IOException
293: * If this OutputStreamWriter has already been closed or some
294: * other IOException occurs.
295: * @throws IndexOutOfBoundsException
296: * If count is negative
297: * @throws StringIndexOutOfBoundsException
298: * If offset is negative or offset + count is outside of bounds
299: */
300: @Override
301: public void write(String str, int offset, int count)
302: throws IOException {
303: synchronized (lock) {
304: // avoid int overflow
305: if (count < 0) {
306: throw new IndexOutOfBoundsException();
307: }
308: if (offset > str.length() - count || offset < 0) {
309: throw new StringIndexOutOfBoundsException();
310: }
311: checkStatus();
312: CharBuffer chars = CharBuffer.wrap(str, offset, count
313: + offset);
314: convert(chars);
315: }
316: }
317: }
|