001/*
002 * Copyright (c) 2004-2010, Kohsuke Kawaguchi
003 * All rights reserved.
004 *
005 * Redistribution and use in source and binary forms, with or without modification, are permitted provided
006 * that the following conditions are met:
007 *
008 *     * Redistributions of source code must retain the above copyright notice, this list of
009 *       conditions and the following disclaimer.
010 *     * Redistributions in binary form must reproduce the above copyright notice, this list of
011 *       conditions and the following disclaimer in the documentation and/or other materials
012 *       provided with the distribution.
013 *
014 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS
015 * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
016 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
017 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
018 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
019 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
020 * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
021 * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
022 */
023
024package org.kohsuke.stapler.framework.io;
025
026import java.io.OutputStream;
027import java.io.Writer;
028import java.io.IOException;
029import java.nio.charset.CharsetDecoder;
030import java.nio.charset.CodingErrorAction;
031import java.nio.charset.CoderResult;
032import java.nio.charset.Charset;
033import java.nio.charset.UnsupportedCharsetException;
034import java.nio.*;
035import java.nio.ByteBuffer;
036
037/**
038 * {@link OutputStream} that writes to {@link Writer}
039 * by assuming the platform default encoding.
040 *
041 * @author Kohsuke Kawaguchi
042 */
043public class WriterOutputStream extends OutputStream {
044    private final Writer writer;
045    private final CharsetDecoder decoder;
046
047    private java.nio.ByteBuffer buf = ByteBuffer.allocate(1024);
048    private CharBuffer out = CharBuffer.allocate(1024);
049
050    public WriterOutputStream(Writer out, Charset charset) {
051        this.writer = out;
052        decoder = charset.newDecoder();
053        decoder.onMalformedInput(CodingErrorAction.REPLACE);
054        decoder.onUnmappableCharacter(CodingErrorAction.REPLACE);
055    }
056
057    public WriterOutputStream(Writer out) {
058        this(out,DEFAULT_CHARSET);
059    }
060
061    public void write(int b) throws IOException {
062        if(buf.remaining()==0)
063            decode(false);
064        buf.put((byte)b);
065    }
066
067    public void write(byte b[], int off, int len) throws IOException {
068        while(len>0) {
069            if(buf.remaining()==0)
070                decode(false);
071            int sz = Math.min(buf.remaining(),len);
072            buf.put(b,off,sz);
073            off += sz;
074            len -= sz;
075        }
076    }
077
078    public void flush() throws IOException {
079        decode(false);
080        flushOutput();
081        writer.flush();
082    }
083
084    private void flushOutput() throws IOException {
085        writer.write(out.array(),0,out.position());
086        out.clear();
087    }
088
089    public void close() throws IOException {
090        decode(true);
091        flushOutput();
092        writer.close();
093
094        buf.rewind();
095    }
096
097    /**
098     * Decodes the contents of {@link #buf} as much as possible to {@link #out}.
099     * If necessary {@link #out} is further sent to {@link #writer}.
100     *
101     * <p>
102     * When this method returns, the {@link #buf} is back to the 'accumulation'
103     * mode.
104     *
105     * @param last
106     *      if true, tell the decoder that all the input bytes are ready.
107     */
108    private void decode(boolean last) throws IOException {
109        buf.flip();
110        while(true) {
111            CoderResult r = decoder.decode(buf, out, last);
112            if(r==CoderResult.OVERFLOW) {
113                flushOutput();
114                continue;
115            }
116            if(r==CoderResult.UNDERFLOW) {
117                buf.compact();
118                return;
119            }
120            // otherwise treat it as an error
121            r.throwException();
122        }
123    }
124
125    private static final Charset DEFAULT_CHARSET = getDefaultCharset();
126
127    private static Charset getDefaultCharset() {
128        try {
129            String encoding = System.getProperty("file.encoding");
130            return Charset.forName(encoding);
131        } catch (UnsupportedCharsetException e) {
132            return Charset.forName("UTF-8");
133        }
134    }
135}