2 * Copyright (c) 2021 PANTHEON.tech, s.r.o. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.restconf.nb.rfc8040;
10 import java.io.ByteArrayOutputStream;
11 import java.nio.ByteBuffer;
12 import java.nio.charset.CharacterCodingException;
13 import java.nio.charset.CharsetDecoder;
14 import java.nio.charset.CodingErrorAction;
15 import java.nio.charset.StandardCharsets;
16 import java.text.ParseException;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.opendaylight.odlparent.logging.markers.Markers;
19 import org.opendaylight.yangtools.concepts.Mutable;
20 import org.slf4j.Logger;
21 import org.slf4j.LoggerFactory;
24 * A buffer of bytes in lazily-allocated array, which can be appended with {@link #appendByte(int)}. Current contents
25 * can be transferred to a {@link StringBuilder} via {@link #flushTo(StringBuilder, int)}, which performs UTF-8
28 final class Utf8Buffer implements Mutable {
29 private static final Logger LOG = LoggerFactory.getLogger(Utf8Buffer.class);
31 private ByteArrayOutputStream bos;
32 private CharsetDecoder decoder;
34 void appendByte(final byte value) {
37 bos = buf = new ByteArrayOutputStream(8);
42 void flushTo(final @NonNull StringBuilder sb, final int errorOffset) throws ParseException {
44 if (buf != null && buf.size() != 0) {
45 flushTo(sb, buf, errorOffset);
49 // Split out to aid inlining
50 private void flushTo(final StringBuilder sb, final ByteArrayOutputStream buf, final int errorOffset)
51 throws ParseException {
52 final var bytes = buf.toByteArray();
55 // Special case for a single ASCII character, side-steps decoder/bytebuf allocation
56 if (bytes.length == 1) {
57 final byte ch = bytes[0];
64 append(sb, ByteBuffer.wrap(bytes));
65 } catch (CharacterCodingException e) {
66 throw report(errorOffset, bytes, e);
70 private void append(final StringBuilder sb, final ByteBuffer bytes) throws CharacterCodingException {
73 decoder = local = StandardCharsets.UTF_8.newDecoder()
74 .onMalformedInput(CodingErrorAction.REPORT)
75 .onUnmappableCharacter(CodingErrorAction.REPORT);
77 sb.append(local.decode(bytes));
80 // Split out to silence checkstyle's failure to understand we cannot propagate the cause
81 private static ParseException report(final int errorOffset, final byte[] bytes,
82 final CharacterCodingException cause) {
83 final String str = new String(bytes, StandardCharsets.UTF_8);
84 LOG.debug(Markers.confidential(), "Rejecting invalid UTF-8 sequence '{}'", str, cause);
85 return new ParseException("Invalid UTF-8 sequence '" + str + "': " + cause.getMessage(), errorOffset);