com.outerspacecat.util.Appendables.java Source code

Java tutorial

Introduction

Here is the source code for com.outerspacecat.util.Appendables.java

Source

/**
 * Copyright 2011 Caleb Richardson
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.outerspacecat.util;

import com.google.common.base.Preconditions;
import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.ClosedChannelException;
import java.nio.channels.WritableByteChannel;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;
import javax.annotation.WillCloseWhenClosed;

/**
 * Defines utility methods for working with instances of {@link Appendable}.
 * 
 * @author Caleb Richardson
 */
public final class Appendables {
    private Appendables() {
    }

    public static Writer tee(final Appendable a1, final Appendable a2) {
        Preconditions.checkNotNull(a1, "a1 required");
        Preconditions.checkNotNull(a2, "a2 required");

        return new Writer() {
            private boolean closed;

            @Override
            public void write(final char[] cbuf, final int off, final int len) throws IOException {
                a1.append(CharBuffer.wrap(cbuf, off, len));
                a2.append(CharBuffer.wrap(cbuf, off, len));
            }

            @Override
            public void flush() throws IOException {
                if (a1 instanceof Flushable)
                    ((Flushable) a1).flush();
                if (a2 instanceof Flushable)
                    ((Flushable) a2).flush();
            }

            @Override
            public void close() throws IOException {
                if (closed)
                    return;

                if (a1 instanceof Closeable)
                    ((Closeable) a1).close();
                if (a2 instanceof Closeable)
                    ((Closeable) a2).close();

                closed = true;
            }
        };
    }

    /**
     * Returns a byte stream that will decode bytes written to it using the
     * charset specified by {@code cs} and write the resulting character stream to
     * {@code dst}. Calling {@link WritableByteChannel#close()} on the returned
     * byte stream will call {@link java.io.Closeable#close()} on {@code dst} if
     * {@code dst} implements the {@link java.io.Closeable} interface.
     * 
     * @param dst the character stream to write to. Must be non {@code null}.
     * @param cs the charset to use for decoding. Must be non {@code null}.
     * @return a byte stream. Never {@code null}.
     */
    public static WritableByteChannel decode(@WillCloseWhenClosed final Appendable dst, final Charset cs) {
        Preconditions.checkNotNull(dst, "dst required");
        Preconditions.checkNotNull(cs, "cs required");

        return new TextDecoder(dst, cs);
    }

    private final static class TextDecoder implements WritableByteChannel, Closeable {
        private final Appendable appendable;
        private final CharsetDecoder dec;
        private final CharBuffer outBuf = ByteBuffer.allocate(8192).asCharBuffer();
        private boolean closed;

        TextDecoder(final Appendable appendable, final Charset cs) {
            Preconditions.checkNotNull(appendable, "appendable required");
            Preconditions.checkNotNull(cs, "cs required");

            this.appendable = appendable;
            dec = cs.newDecoder();
            dec.onMalformedInput(CodingErrorAction.REPORT);
            dec.onUnmappableCharacter(CodingErrorAction.REPORT);
        }

        @Override
        public int write(final ByteBuffer inBuf) throws IOException {
            if (!isOpen())
                throw new ClosedChannelException();

            int op = inBuf.position();

            outer: for (;;) {
                dump();

                for (;;) {
                    CoderResult cr = dec.decode(inBuf, outBuf, false);
                    if (cr.isOverflow()) {
                        break;
                    } else if (cr.isUnderflow()) {
                        break outer;
                    } else {
                        throw new IllegalStateException("unexpected coder result: " + cr);
                    }
                }
            }

            return inBuf.position() - op;
        }

        @Override
        public boolean isOpen() {
            return !closed;
        }

        private void dump() throws IOException {
            outBuf.flip();

            if (outBuf.hasRemaining())
                appendable.append(outBuf);

            outBuf.clear();
        }

        @Override
        public void close() throws IOException {
            if (!isOpen())
                return;

            dump();

            CoderResult cr = dec.decode(ByteBuffer.allocate(0), outBuf, true);
            if (!cr.isUnderflow())
                throw new IllegalStateException("unexpected coder result: " + cr);

            cr = dec.flush(outBuf);
            if (!cr.isUnderflow())
                throw new IllegalStateException("unexpected coder result: " + cr);

            dump();

            if (appendable instanceof Closeable)
                ((Closeable) appendable).close();

            closed = true;
        }
    }
}