    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017
    018package org.apache.commons.imaging.common;
    019
    020import java.math.BigInteger;
    021import java.util.ArrayList;
    022import java.util.function.IntFunction;
    023
    024/**
    025 * Checks inputs for meeting allocation limits and allocates arrays.
    026 */
    027public class Allocator {
    028
    029    private static final String CANONICAL_NAME = Allocator.class.getCanonicalName();
    030
    031    /** One GB. */
    032    private static final int DEFAULT = 1_073_741_824;
    033    private static final int LIMIT;
    034
    035    static {
    036        LIMIT = Integer.getInteger(CANONICAL_NAME, DEFAULT);
    037    }
    038
    039    /**
    040     * Allocates an Object of type T of the requested size.
    041     *
    042     * @param <T>     The return array type
    043     * @param request The requested size.
    044     * @param factory The array factory.
    045     * @return a new byte array.
    046     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    047     * @see #check(int)
    048     */
    049    public static <T> T apply(final int request, final IntFunction<T> factory) {
    050        return factory.apply(check(request));
    051    }
    052
    053    /**
    054     * Allocates an array of type T of the requested size.
    055     *
    056     * @param <T>                The return array type
    057     * @param request            The requested size.
    058     * @param factory            The array factory.
    059     * @param eltShallowByteSize The shallow byte size of an element.
    060     * @return a new byte array.
    061     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    062     * @see #check(int)
    063     */
    064    public static <T> T[] array(final int request, final IntFunction<T[]> factory, final int eltShallowByteSize) {
    065        check(request * eltShallowByteSize);
    066        return factory.apply(request);
    067    }
    068
    069    /**
    070     * Allocates an Object array of type T of the requested size.
    071     *
    072     * @param <T>     The return array type
    073     * @param request The requested size.
    074     * @return a new byte array.
    075     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    076     * @see #check(int)
    077     */
    078    public static <T> ArrayList<T> arrayList(final int request) {
    079        check(24 + request * 4); // 4 bytes per element
    080        return apply(request, ArrayList::new);
    081    }
    082
    083    /**
    084     * Allocates a byte array of the requested size.
    085     *
    086     * @param request The requested size.
    087     * @return a new byte array.
    088     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    089     * @see #check(int, int)
    090     */
    091    public static byte[] byteArray(final int request) {
    092        return new byte[checkByteArray(request)];
    093    }
    094
    095    /**
    096     * Allocates a byte array of the requested size.
    097     *
    098     * @param request The requested size is cast down to an int.
    099     * @return a new byte array.
    100     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    101     * @see #check(int, int)
    102     */
    103    public static byte[] byteArray(final long request) {
    104        return new byte[check(request, Byte.BYTES)];
    105    }
    106
    107    /**
    108     * Allocates a char array of the requested size.
    109     *
    110     * @param request The requested size.
    111     * @return a new char array.
    112     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    113     * @see #check(int, int)
    114     */
    115    public static char[] charArray(final int request) {
    116        return new char[check(request, Character.BYTES)];
    117    }
    118
    119    /**
    120     * Checks a request for meeting allocation limits.
    121     * <p>
    122     * The default limit is {@code #DEFAULT}, override with the system property "org.apache.commons.imaging.common.mylzw.AllocationChecker".
    123     * </p>
    124     *
    125     * @param request an allocation request.
    126     * @return the request.
    127     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    128     */
    129    public static int check(final int request) {
    130        if (request > LIMIT) {
    131            throw new AllocationRequestException(LIMIT, request);
    132        }
    133        return request;
    134    }
    135
    136    /**
    137     * Checks a request for meeting allocation limits.
    138     * <p>
    139     * The default limit is {@code #DEFAULT}, override with the system property "org.apache.commons.imaging.common.mylzw.AllocationChecker".
    140     * </p>
    141     *
    142     * @param request     an allocation request count.
    143     * @param elementSize The element size.
    144     * @return the request.
    145     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    146     */
    147    public static int check(final int request, final int elementSize) {
    148        final int multiplyExact;
    149        try {
    150            multiplyExact = Math.multiplyExact(request, elementSize);
    151        } catch (final ArithmeticException e) {
    152            throw new AllocationRequestException(LIMIT, BigInteger.valueOf(request).multiply(BigInteger.valueOf(elementSize)), e);
    153        }
    154        if (multiplyExact > LIMIT) {
    155            throw new AllocationRequestException(LIMIT, request);
    156        }
    157        return request;
    158    }
    159
    160    /**
    161     * Checks a request for meeting allocation limits.
    162     * <p>
    163     * The default limit is {@code #DEFAULT}, override with the system property "org.apache.commons.imaging.common.mylzw.AllocationChecker".
    164     * </p>
    165     *
    166     * @param request     an allocation request count is cast down to an int.
    167     * @param elementSize The element size.
    168     * @return the request.
    169     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    170     */
    171    public static int check(final long request, final int elementSize) {
    172        try {
    173            return check(Math.toIntExact(request), elementSize);
    174        } catch (final ArithmeticException e) {
    175            throw new AllocationRequestException(LIMIT, request, e);
    176        }
    177    }
    178
    179    /**
    180     * Checks that allocating a byte array of the requested size is within the limit.
    181     *
    182     * @param request The byte array size.
    183     * @return The input request.
    184     */
    185    public static int checkByteArray(final int request) {
    186        return check(request, Byte.BYTES);
    187    }
    188
    189    /**
    190     * Allocates a double array of the requested size.
    191     *
    192     * @param request The requested size.
    193     * @return a new double array.
    194     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    195     * @see #check(int, int)
    196     */
    197    public static double[] doubleArray(final int request) {
    198        return new double[check(request, Double.BYTES)];
    199    }
    200
    201    /**
    202     * Allocates a float array of the requested size.
    203     *
    204     * @param request The requested size.
    205     * @return a new float array.
    206     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    207     * @see #check(int, int)
    208     */
    209    public static float[] floatArray(final int request) {
    210        return new float[check(request, Float.BYTES)];
    211    }
    212
    213    /**
    214     * Allocates a int array of the requested size.
    215     *
    216     * @param request The requested size.
    217     * @return a new int array.
    218     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    219     * @see #check(int, int)
    220     */
    221    public static int[] intArray(final int request) {
    222        return new int[check(request, Integer.BYTES)];
    223    }
    224
    225    /**
    226     * Allocates a long array of the requested size.
    227     *
    228     * @param request The requested size.
    229     * @return a new long array.
    230     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    231     * @see #check(int, int)
    232     */
    233    public static long[] longArray(final int request) {
    234        return new long[check(request, Long.BYTES)];
    235    }
    236
    237    /**
    238     * Allocates a short array of the requested size.
    239     *
    240     * @param request The requested size.
    241     * @return a new short array.
    242     * @throws AllocationRequestException Thrown when the request exceeds the limit.
    243     * @see #check(int, int)
    244     */
    245    public static short[] shortArray(final int request) {
    246        return new short[check(request, Short.BYTES)];
    247    }
    248
    249}