/* * Copyright 2015 Julien Viet * * 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 io.termd.core.io; import io.termd.core.util.Helper; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.IntBuffer; import java.nio.charset.Charset; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.util.function.Consumer; /** * @author <a href="mailto:julien@julienviet.com">Julien Viet</a> */ public class BinaryDecoder { private static final ByteBuffer EMPTY = ByteBuffer.allocate(0); private CharsetDecoder decoder; private ByteBuffer bBuf; private final CharBuffer cBuf; private final Consumer<int[]> onChar; public BinaryDecoder(Charset charset, Consumer<int[]> onChar) { this(2, charset, onChar); } public BinaryDecoder(int initialSize, Charset charset, Consumer<int[]> onChar) { if (initialSize < 2) { throw new IllegalArgumentException("Initial size must be at least 2"); } decoder = charset.newDecoder(); bBuf = EMPTY; cBuf = CharBuffer.allocate(initialSize); // We need at least 2 this.onChar = onChar; } /** * Set a new charset on the decoder. * * @param charset the new charset */ public void setCharset(Charset charset) { decoder = charset.newDecoder(); } public void write(byte[] data) { write(data, 0, data.length); } public void write(byte[] data, int start, int len) { // Fill the byte buffer int remaining = bBuf.remaining(); if (len > remaining) { // Allocate a new buffer ByteBuffer tmp = bBuf; int length = tmp.position() + len; bBuf = ByteBuffer.allocate(length); tmp.flip(); bBuf.put(tmp); } bBuf.put(data, start, len); bBuf.flip(); // Drain the byte buffer while (true) { IntBuffer iBuf = IntBuffer.allocate(bBuf.remaining()); CoderResult result = decoder.decode(bBuf, cBuf, false); cBuf.flip(); while (cBuf.hasRemaining()) { char c = cBuf.get(); if (Character.isSurrogate(c)) { if (Character.isHighSurrogate(c)) { if (cBuf.hasRemaining()) { char low = cBuf.get(); if (Character.isLowSurrogate(low)) { int codePoint = Character.toCodePoint(c, low); if (Character.isValidCodePoint(codePoint)) { iBuf.put(codePoint); } else { throw new UnsupportedOperationException("Handle me gracefully"); } } else { throw new UnsupportedOperationException("Handle me gracefully"); } } else { throw new UnsupportedOperationException("Handle me gracefully"); } } else { throw new UnsupportedOperationException("Handle me gracefully"); } } else { iBuf.put((int) c); } } iBuf.flip(); int[] codePoints = new int[iBuf.limit()]; iBuf.get(codePoints); onChar.accept(codePoints); cBuf.compact(); if (result.isOverflow()) { // We still have work to do } else if (result.isUnderflow()) { if (bBuf.hasRemaining()) { // We need more input Helper.noop(); } else { // We are done Helper.noop(); } break; } else { throw new UnsupportedOperationException("Handle me gracefully"); } } bBuf.compact(); } }