/* * Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package jdk.incubator.http.internal.websocket; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.CharacterCodingException; import java.nio.charset.CharsetDecoder; import java.nio.charset.CoderResult; import java.nio.charset.CodingErrorAction; import static java.lang.System.Logger.Level.WARNING; import static java.nio.charset.StandardCharsets.UTF_8; import static jdk.incubator.http.internal.common.Utils.EMPTY_BYTEBUFFER; import static jdk.incubator.http.internal.websocket.WebSocketImpl.logger; final class UTF8AccumulatingDecoder { private final CharsetDecoder decoder = UTF_8.newDecoder(); { decoder.onMalformedInput(CodingErrorAction.REPORT); decoder.onUnmappableCharacter(CodingErrorAction.REPORT); } private ByteBuffer leftovers = EMPTY_BYTEBUFFER; CharBuffer decode(ByteBuffer in, boolean endOfInput) throws CharacterCodingException { ByteBuffer b; int rem = leftovers.remaining(); if (rem != 0) { // We won't need this wasteful allocation & copying when JDK-8155222 // has been resolved b = ByteBuffer.allocate(rem + in.remaining()); b.put(leftovers).put(in).flip(); } else { b = in; } CharBuffer out = CharBuffer.allocate(b.remaining()); CoderResult r = decoder.decode(b, out, endOfInput); if (r.isError()) { r.throwException(); } if (b.hasRemaining()) { leftovers = ByteBuffer.allocate(b.remaining()).put(b).flip(); } else { leftovers = EMPTY_BYTEBUFFER; } // Since it's UTF-8, the assumption is leftovers.remaining() < 4 // (i.e. small). Otherwise a shared buffer should be used if (!(leftovers.remaining() < 4)) { logger.log(WARNING, "The size of decoding leftovers is greater than expected: {0}", leftovers.remaining()); } b.position(b.limit()); // As if we always read to the end // Decoder promises that in the case of endOfInput == true: // "...any remaining undecoded input will be treated as being // malformed" assert !(endOfInput && leftovers.hasRemaining()) : endOfInput + ", " + leftovers; if (endOfInput) { r = decoder.flush(out); decoder.reset(); if (r.isOverflow()) { // FIXME: for now I know flush() does nothing. But the // implementation of UTF8 decoder might change. And if now // flush() is a no-op, it is not guaranteed to remain so in // the future throw new InternalError("Not yet implemented"); } } return out.flip(); } }