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 {@value #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 {@value #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        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 {@value #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}