Java tutorial
/** * 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; } } }