public class

Utils

extends java.lang.Object

 java.lang.Object

↳androidx.javascriptengine.common.Utils

Gradle dependencies

compile group: 'androidx.javascriptengine', name: 'javascriptengine', version: '1.0.0-beta01'

  • groupId: androidx.javascriptengine
  • artifactId: javascriptengine
  • version: 1.0.0-beta01

Artifact androidx.javascriptengine:javascriptengine:1.0.0-beta01 it located at Google repository (https://maven.google.com/)

Overview

Utility methods for use in both service and client side of JavaScriptEngine.

Summary

Methods
public static voidcheckAssetFileDescriptor(AssetFileDescriptor afd, boolean allowUnknownLength)

Checks if the given AssetFileDescriptor passes certain conditions.

public static voidcloseQuietly(java.io.Closeable closeable)

Close, ignoring exception.

public static java.lang.RuntimeExceptionexceptionToRuntimeException(java.lang.Exception e)

Convert an Exception to a RuntimeException if needed, without needlessly wrapping up an existing RuntimeException.

public static intgetLastUTF8StartingByteIndex(byte[] bytes[])

Returns the index of right-most UTF-8 starting byte.

public static booleanisUTF8ContinuationByte(byte b)

Checks whether a given byte is a UTF8 continuation byte.

public static intreadNBytes(java.io.InputStream inputStream, byte[] b[], int off, int len)

Read a given number of bytes from a given stream into a byte array.

public static java.lang.StringreadToString(AssetFileDescriptor afd, int maxLength, boolean truncate)

Read from a AssetFileDescriptor into a String and closes it in case of both success and failure.

public static voidwriteByteArrayToStream(byte[] inputBytes[], java.io.OutputStream outputStream)

Utility method to write a byte array into a stream.

public static AssetFileDescriptorwriteBytesIntoPipeAsync(byte[] inputBytes[], java.util.concurrent.ExecutorService executorService)

Creates a pipe, writes the given bytes into one end and returns the other end.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static void writeByteArrayToStream(byte[] inputBytes[], java.io.OutputStream outputStream)

Utility method to write a byte array into a stream.

public static void closeQuietly(java.io.Closeable closeable)

Close, ignoring exception.

public static AssetFileDescriptor writeBytesIntoPipeAsync(byte[] inputBytes[], java.util.concurrent.ExecutorService executorService)

Creates a pipe, writes the given bytes into one end and returns the other end.

public static void checkAssetFileDescriptor(AssetFileDescriptor afd, boolean allowUnknownLength)

Checks if the given AssetFileDescriptor passes certain conditions.

public static int readNBytes(java.io.InputStream inputStream, byte[] b[], int off, int len)

Read a given number of bytes from a given stream into a byte array.

This allows us to use this functionality added in API 33.

public static boolean isUTF8ContinuationByte(byte b)

Checks whether a given byte is a UTF8 continuation byte. If a byte can be part of valid UTF-8 and is not a continuation byte, it must be a starting byte.

public static int getLastUTF8StartingByteIndex(byte[] bytes[])

Returns the index of right-most UTF-8 starting byte.

The input must be valid (or truncated) UTF-8 encoded bytes. Returns -1 if there is no starting byte.

public static java.lang.String readToString(AssetFileDescriptor afd, int maxLength, boolean truncate)

Read from a AssetFileDescriptor into a String and closes it in case of both success and failure.

public static java.lang.RuntimeException exceptionToRuntimeException(java.lang.Exception e)

Convert an Exception to a RuntimeException if needed, without needlessly wrapping up an existing RuntimeException.

Compared to new RuntimeException(e), this avoids hiding a specific subclass of RuntimeException from catch blocks if one is available.

Parameters:

e: Exception to potentially wrap.

Returns:

e if e was a RuntimeException, else e wrapped in a RuntimeException.

Source

/*
 * Copyright 2023 The Android Open Source Project
 *
 * 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 androidx.javascriptengine.common;

import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import java.io.Closeable;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;

/**
 * Utility methods for use in both service and client side of JavaScriptEngine.
 */
public class Utils {
    private static final String TAG = "JavaScriptEngineUtils";

    private Utils() {
        throw new AssertionError();
    }

    /**
     * Utility method to write a byte array into a stream.
     */
    public static void writeByteArrayToStream(@NonNull byte[] inputBytes,
            @NonNull OutputStream outputStream) {
        try {
            outputStream.write(inputBytes);
            outputStream.flush();
        } catch (IOException e) {
            Log.e(TAG, "Writing to outputStream failed", e);
        } finally {
            closeQuietly(outputStream);
        }
    }

    /**
     * Close, ignoring exception.
     */
    public static void closeQuietly(@Nullable Closeable closeable) {
        if (closeable == null) return;
        try {
            closeable.close();
        } catch (IOException ex) {
            // Ignore the exception on close.
        }
    }

    /**
     * Creates a pipe, writes the given bytes into one end and returns the other end.
     */
    @NonNull
    public static AssetFileDescriptor writeBytesIntoPipeAsync(@NonNull byte[] inputBytes,
            @NonNull ExecutorService executorService) throws IOException {
        ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
        ParcelFileDescriptor readSide = pipe[0];
        ParcelFileDescriptor writeSide = pipe[1];
        OutputStream outputStream =
                new ParcelFileDescriptor.AutoCloseOutputStream(writeSide);
        executorService.execute(
                () -> Utils.writeByteArrayToStream(inputBytes, outputStream));
        return new AssetFileDescriptor(readSide, 0, inputBytes.length);
    }

    /**
     * Checks if the given AssetFileDescriptor passes certain conditions.
     */

    public static void checkAssetFileDescriptor(@NonNull AssetFileDescriptor afd,
            boolean allowUnknownLength) {
        if (afd.getStartOffset() < 0) {
            throw new IllegalArgumentException(
                    "AssetFileDescriptor offset should be >= 0");
        }
        if (afd.getLength() != AssetFileDescriptor.UNKNOWN_LENGTH && afd.getLength() < 0) {
            throw new IllegalArgumentException(
                    "AssetFileDescriptor should have valid length");
        }
        if (afd.getDeclaredLength() != AssetFileDescriptor.UNKNOWN_LENGTH
                && afd.getDeclaredLength() < 0) {
            throw new IllegalArgumentException(
                    "AssetFileDescriptor should have valid declared length");
        }
        if (afd.getLength() == AssetFileDescriptor.UNKNOWN_LENGTH && afd.getStartOffset() != 0) {
            throw new UnsupportedOperationException(
                    "AssetFileDescriptor offset should be 0 for unknown length");
        }

        if (!allowUnknownLength && afd.getLength() == AssetFileDescriptor.UNKNOWN_LENGTH) {
            throw new UnsupportedOperationException(
                    "AssetFileDescriptor should have known length");
        }
    }

    /**
     * Read a given number of bytes from a given stream into a byte array.
     * <p>
     * This allows us to use
     * <a href=https://developer.android.com/reference/java/io/InputStream#readNBytes(byte[],%20int,%20int)">
     * this </a>
     * functionality added in API 33.
     */
    public static int readNBytes(@NonNull InputStream inputStream, @NonNull byte[] b, int off,
            int len)
            throws IOException {
        int n = 0;
        while (n < len) {
            int count = inputStream.read(b, off + n, len - n);
            if (count < 0) {
                break;
            }
            n += count;
        }
        return n;
    }

    /**
     * Checks whether a given byte is a UTF8 continuation byte. If a byte can be part of valid
     * UTF-8 and is not a continuation byte, it must be a starting byte.
     */
    public static boolean isUTF8ContinuationByte(byte b) {
        final byte maskContinuationByte = (byte) 0b11000000;
        final byte targetContinuationByte = (byte) 0b10000000;
        // Checks whether it looks like "0b10xxxxxx"
        return (b & maskContinuationByte) == targetContinuationByte;
    }

    /**
     * Returns the index of right-most UTF-8 starting byte.
     * <p>
     * The input must be valid (or truncated) UTF-8 encoded bytes.
     * Returns -1 if there is no starting byte.
     */
    public static int getLastUTF8StartingByteIndex(@NonNull byte[] bytes) {
        for (int index = bytes.length - 1; index >= 0; index--) {
            if (!isUTF8ContinuationByte(bytes[index])) {
                return index;
            }
        }
        return -1;
    }

    /**
     * Read from a AssetFileDescriptor into a String and closes it in case of both success and
     * failure.
     */
    @NonNull
    public static String readToString(@NonNull AssetFileDescriptor afd, int maxLength,
            boolean truncate)
            throws IOException, LengthLimitExceededException {
        try {
            Utils.checkAssetFileDescriptor(afd, /*allowUnknownLength=*/ false);
            int lengthToRead = (int) afd.getLength();
            if (afd.getLength() > maxLength) {
                if (truncate) {
                    // If truncate is true, read how much ever you are allowed to read.
                    lengthToRead = maxLength;
                } else {
                    throw new LengthLimitExceededException(
                            "AssetFileDescriptor.getLength() should be"
                                    + " <= " + maxLength);
                }
            }
            byte[] bytes = new byte[lengthToRead];
            // We can use AssetFileDescriptor.createInputStream() to get the InputStream directly
            // but this API is currently broken while fixing another issue regarding multiple
            // AssetFileDescriptor pointing to the same file. (b/263325931)
            // Using ParcelFileDescriptor to read the file is correct as long as the offset is 0.
            try (ParcelFileDescriptor pfd = afd.getParcelFileDescriptor()) {
                InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
                if (Utils.readNBytes(inputStream, bytes, 0, lengthToRead) != lengthToRead) {
                    throw new IOException("Couldn't read " + lengthToRead + " bytes from the "
                            + "AssetFileDescriptor");
                }
            }
            int validUtf8PrefixLength = lengthToRead;
            if (truncate) {
                // Ignoring the last partial/complete codepoint.
                validUtf8PrefixLength = getLastUTF8StartingByteIndex(bytes);
            }
            // This process can be made more memory efficient by converting the UTF-8 encoded
            // bytes to String by reading from the pipe in chunks.
            return new String(bytes, 0, validUtf8PrefixLength, StandardCharsets.UTF_8);
        } finally {
            afd.close();
        }
    }

    /**
     * Convert an Exception to a RuntimeException if needed, without needlessly wrapping up an
     * existing RuntimeException.
     * <p>
     * Compared to {@code new RuntimeException(e)}, this avoids hiding a specific subclass of
     * RuntimeException from catch blocks if one is available.
     *
     * @param e Exception to potentially wrap.
     * @return e if e was a RuntimeException, else e wrapped in a RuntimeException.
     */
    @NonNull
    public static RuntimeException exceptionToRuntimeException(@NonNull Exception e) {
        if (e instanceof RuntimeException) {
            // Don't hide the original RuntimeException type by wrapping it in another
            // RuntimeException. Just return it directly.
            return (RuntimeException) e;
        } else {
            return new RuntimeException(e);
        }
    }
}