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 javax.annotation.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.IllegalCharsetNameException;
import java.nio.charset.StandardCharsets;
import java.nio.charset.UnsupportedCharsetException;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;

public class IOUtils {

    protected static final int BYTE_BUFFER_CAPACITY = 2048;

    private static final ObjectPool> BYTE_BUFFER_POOL = ObjectPooling.createWithDefaultFactory(new Callable() {
        @Override
        public ByteBuffer call() throws Exception {
            return ByteBuffer.allocate(BYTE_BUFFER_CAPACITY);
        }
    });

    private static final Set UNSUPPORTED_CHARSETS = Collections.newSetFromMap(new ConcurrentHashMap());
    private static final Map>> DECODER_POOLS
        = new ConcurrentHashMap>>();

    private static final String UTF8_CHARSET_NAME = StandardCharsets.UTF_8.name().toLowerCase();

    @Nullable
    private static ObjectHandle getPooledCharsetDecoder(String charsetName) {
        if (!isLowerCase(charsetName)) {
            charsetName = charsetName.toLowerCase();
        }
        ObjectPool> decoderPool = DECODER_POOLS.get(charsetName);
        if (decoderPool != null) {
            return decoderPool.createInstance();
        }
        if (UNSUPPORTED_CHARSETS.contains(charsetName)) {
            return null;
        }
        try {
            final Charset charset = Charset.forName(charsetName);
            decoderPool = ObjectPooling.createWithDefaultFactory(new Callable() {
                @Override
                public CharsetDecoder call() throws Exception {
                    return charset.newDecoder();
                }
            });
            DECODER_POOLS.put(charsetName, decoderPool);
            return decoderPool.createInstance();
        } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
            UNSUPPORTED_CHARSETS.add(charsetName);
            return null;
        }
    }

    private static boolean isLowerCase(String charsetName) {
        for (int i = 0; i < charsetName.length(); i++) {
            if (!Character.isLowerCase(charsetName.charAt(i))) {
                return false;
            }
        }
        return true;
    }


    /**
     * 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 bufferHandle = BYTE_BUFFER_POOL.createInstance(); ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); ) { final ByteBuffer buffer = bufferHandle.get(); final CharsetDecoder charsetDecoder = decoderHandle.get(); 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 bufferHandle = BYTE_BUFFER_POOL.createInstance(); ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); ) { final ByteBuffer pooledBuffer = bufferHandle.get(); // 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, decoderHandle.get()); } } /** * 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 bufferHandle = BYTE_BUFFER_POOL.createInstance(); ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); ) { final ByteBuffer buffer = bufferHandle.get(); buffer.put(b); ((Buffer) buffer).position(0); ((Buffer) buffer).limit(1); return decode(charBuffer, buffer, decoderHandle.get()); } } 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 bufferHandle = BYTE_BUFFER_POOL.createInstance(); ObjectHandle decoderHandle = getPooledCharsetDecoder(UTF8_CHARSET_NAME); ) { final ByteBuffer buffer = bufferHandle.get(); 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, decoderHandle.get()); if (result.isError() || result.isOverflow()) { return result; } readableBytes = reader.availableBytes(src); } return result == null ? CoderResult.OVERFLOW : result; } } @Nullable public static byte[] copyToByteArray(@Nullable ByteBuffer buf) { if (buf == null) { return null; } byte[] data = new byte[buf.position()]; buf.position(0); buf.get(data); return data; } public interface ByteSourceReader { int availableBytes(S source); void readInto(S source, ByteBuffer into); } /** * @param input the byte data to decode * @param output the buffer to decode into * @param charsetName the name of the charset * @return null, if the charset is not known/supported. Otherwise the result of the decoding operation. */ @Nullable public static CoderResult decode(ByteBuffer input, CharBuffer output, String charsetName) { try (ObjectHandle decoderHandle = getPooledCharsetDecoder(charsetName)) { if (decoderHandle == null) { return null; //charset is unsupported } CharsetDecoder charsetDecoder = decoderHandle.get(); try { final CoderResult coderResult = charsetDecoder.decode(input, output, true); charsetDecoder.flush(output); return coderResult; } finally { charsetDecoder.reset(); } } } 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