All Downloads are FREE. Search and download functionalities are using the official Maven repository.

co.elastic.apm.agent.sdk.internal.util.IOUtils Maven / Gradle / Ivy

There is a newer version: 1.52.1
Show newest version
/*
 * Licensed to Elasticsearch B.V. under one or more contributor
 * license agreements. See the NOTICE file distributed with
 * this work for additional information regarding copyright
 * ownership. Elasticsearch B.V. licenses this file to you 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 co.elastic.apm.agent.sdk.internal.util;


import co.elastic.apm.agent.sdk.internal.pooling.ObjectHandle;
import co.elastic.apm.agent.sdk.internal.pooling.ObjectPool;
import co.elastic.apm.agent.sdk.internal.pooling.ObjectPooling;

import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.Callable;

public class IOUtils {

    protected static final int BYTE_BUFFER_CAPACITY = 2048;

    private static class DecoderWithBuffer {
        final ByteBuffer byteBuffer = java.nio.ByteBuffer.allocate(BYTE_BUFFER_CAPACITY);
        final CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
    }

    private static final ObjectPool> POOL = ObjectPooling.createWithDefaultFactory(new Callable() {
        @Override
        public DecoderWithBuffer call() throws Exception {
            return new DecoderWithBuffer();
        }
    });


    /**
     * Reads the provided {@link InputStream} into the {@link CharBuffer} without causing allocations.
     * 

* The {@link InputStream} is assumed to yield an UTF-8 encoded string. *

*

* If the {@link InputStream} yields more chars than the {@link CharBuffer#limit()} of the provided {@link CharBuffer}, * the rest of the input is silently ignored. *

* * @param is the source {@link InputStream}, which should be encoded with UTF-8. * @param charBuffer the {@link CharBuffer} the {@link InputStream} should be written into * @return {@code true}, if the input stream could be decoded with the UTF-8 charset, {@code false} otherwise. * @throws IOException in case of errors reading from the provided {@link InputStream} */ public static boolean readUtf8Stream(final InputStream is, final CharBuffer charBuffer) throws IOException { // to be compatible with Java 8, we have to cast to buffer because of different return types try (ObjectHandle pooled = POOL.createInstance()) { final ByteBuffer buffer = pooled.get().byteBuffer; final CharsetDecoder charsetDecoder = pooled.get().decoder; try { final byte[] bufferArray = buffer.array(); for (int read = is.read(bufferArray); read != -1; read = is.read(bufferArray)) { ((Buffer) buffer).limit(read); final CoderResult coderResult = charsetDecoder.decode(buffer, charBuffer, true); ((Buffer) buffer).clear(); if (coderResult.isError()) { // this is not UTF-8 ((Buffer) charBuffer).clear(); return false; } else if (coderResult.isOverflow()) { // stream yields more chars than the charBuffer can hold break; } } charsetDecoder.flush(charBuffer); return true; } finally { ((Buffer) charBuffer).flip(); ((Buffer) buffer).clear(); charsetDecoder.reset(); is.close(); } } } /** * Decodes a UTF-8 encoded byte array into a char buffer, without allocating memory. *

* The {@code byte[]} is assumed to yield an UTF-8 encoded string. * If this is not true, the returned {@link CoderResult} will have the {@link CoderResult#isError()} flag set to true. *

*

* If the {@code byte[]} yields more chars than the {@link CharBuffer#limit()} of the provided {@link CharBuffer}, * the returned {@link CoderResult} will have the {@link CoderResult#isOverflow()} flag set to true. *

*

* NOTE: This method does not {@link CharBuffer#flip()} the provided {@link CharBuffer} so that this method can be called multiple times * with the same {@link CharBuffer}. * If you are done with appending to the {@link CharBuffer}, you have to call {@link CharBuffer#flip()} manually. *

* * @param bytes the source byte[], which should be encoded with UTF-8. * @param charBuffer the {@link CharBuffer} the {@link InputStream} should be written into * @return a {@link CoderResult}, indicating the success or failure of the decoding */ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final CharBuffer charBuffer) { return decodeUtf8Bytes(bytes, 0, bytes.length, charBuffer); } /** * Decodes a UTF-8 encoded byte array into a char buffer, without allocating memory. *

* The {@code byte[]} is assumed to yield an UTF-8 encoded string. * If this is not true, the returned {@link CoderResult} will have the {@link CoderResult#isError()} flag set to true. *

*

* If the {@code byte[]} yields more chars than the {@link CharBuffer#limit()} of the provided {@link CharBuffer}, * the returned {@link CoderResult} will have the {@link CoderResult#isOverflow()} flag set to true. *

*

* NOTE: This method does not {@link CharBuffer#flip()} the provided {@link CharBuffer} so that this method can be called multiple times * with the same {@link CharBuffer}. * If you are done with appending to the {@link CharBuffer}, you have to call {@link CharBuffer#flip()} manually. *

* * @param bytes the source byte[], which should be encoded with UTF-8 * @param charBuffer the {@link CharBuffer} the {@link InputStream} should be written into * @param offset the start offset in array bytes at which the data is read * @param length the maximum number of bytes to read * @return a {@link CoderResult}, indicating the success or failure of the decoding */ public static CoderResult decodeUtf8Bytes(final byte[] bytes, final int offset, final int length, final CharBuffer charBuffer) { try (ObjectHandle pooled = POOL.createInstance()) { final ByteBuffer pooledBuffer = pooled.get().byteBuffer; final CharsetDecoder charsetDecoder = pooled.get().decoder; // to be compatible with Java 8, we have to cast to buffer because of different return types final ByteBuffer buffer; if (pooledBuffer.capacity() < length) { // allocates a ByteBuffer wrapper object, the underlying byte[] is not copied buffer = ByteBuffer.wrap(bytes, offset, length); } else { buffer = pooledBuffer; buffer.put(bytes, offset, length); ((Buffer) buffer).position(0); ((Buffer) buffer).limit(length); } return decode(charBuffer, buffer, charsetDecoder); } } /** * Decodes a single UTF-8 encoded byte into a char buffer, without allocating memory. *

* The {@code byte} is assumed to yield an UTF-8 encoded string. * If this is not true, the returned {@link CoderResult} will have the {@link CoderResult#isError()} flag set to true. *

*

* If the provided {@link CharBuffer} has already reached its {@link CharBuffer#limit()}, * the returned {@link CoderResult} will have the {@link CoderResult#isOverflow()} flag set to true. *

*

* NOTE: This method does not {@link CharBuffer#flip()} the provided {@link CharBuffer} so that this method can be called multiple times * with the same {@link CharBuffer}. * If you are done with appending to the {@link CharBuffer}, you have to call {@link CharBuffer#flip()} manually. *

* * @param b the source byte[], which should be encoded with UTF-8 * @param charBuffer the {@link CharBuffer} the {@link InputStream} should be written into * @return a {@link CoderResult}, indicating the success or failure of the decoding */ public static CoderResult decodeUtf8Byte(final byte b, final CharBuffer charBuffer) { // to be compatible with Java 8, we have to cast to buffer because of different return types try (ObjectHandle pooled = POOL.createInstance()) { final ByteBuffer buffer = pooled.get().byteBuffer; final CharsetDecoder charsetDecoder = pooled.get().decoder; buffer.put(b); ((Buffer) buffer).position(0); ((Buffer) buffer).limit(1); return decode(charBuffer, buffer, charsetDecoder); } } public static CoderResult decodeUtf8BytesFromSource(ByteSourceReader reader, T src, final CharBuffer dest) { // to be compatible with Java 8, we have to cast to buffer because of different return types try (ObjectHandle pooled = POOL.createInstance()) { final ByteBuffer buffer = pooled.get().byteBuffer; final CharsetDecoder charsetDecoder = pooled.get().decoder; int readableBytes = reader.availableBytes(src); CoderResult result = null; while (readableBytes > 0) { int length = Math.min(readableBytes, BYTE_BUFFER_CAPACITY); ((Buffer) buffer).limit(length); ((Buffer) buffer).position(0); reader.readInto(src, buffer); ((Buffer) buffer).position(0); result = decode(dest, buffer, charsetDecoder); if (result.isError() || result.isOverflow()) { return result; } readableBytes = reader.availableBytes(src); } return result == null ? CoderResult.OVERFLOW : result; } } public interface ByteSourceReader { int availableBytes(S source); void readInto(S source, ByteBuffer into); } private static CoderResult decode(CharBuffer charBuffer, ByteBuffer buffer, CharsetDecoder charsetDecoder) { try { final CoderResult coderResult = charsetDecoder.decode(buffer, charBuffer, true); charsetDecoder.flush(charBuffer); return coderResult; } finally { ((Buffer) buffer).clear(); charsetDecoder.reset(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy