1 /*
2  * Copyright (c) 2015, 2018, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package sun.java2d.marlin;
27 
28 import static sun.java2d.marlin.ArrayCacheConst.ARRAY_SIZES;
29 import static sun.java2d.marlin.ArrayCacheConst.BUCKETS;
30 import static sun.java2d.marlin.ArrayCacheConst.MAX_ARRAY_SIZE;
31 import static sun.java2d.marlin.MarlinUtils.logInfo;
32 import static sun.java2d.marlin.MarlinUtils.logException;
33 
34 import java.lang.ref.WeakReference;
35 import java.util.Arrays;
36 
37 import sun.java2d.marlin.ArrayCacheConst.BucketStats;
38 import sun.java2d.marlin.ArrayCacheConst.CacheStats;
39 
40 /*
41  * Note that the [BYTE/INT/FLOAT/DOUBLE]ArrayCache files are nearly identical except
42  * for a few type and name differences. Typically, the [BYTE]ArrayCache.java file
43  * is edited manually and then [INT/FLOAT/DOUBLE]ArrayCache.java
44  * files are generated with the following command lines:
45  */
46 // % sed -e 's/(b\yte)[ ]*//g' -e 's/b\yte/int/g' -e 's/B\yte/Int/g' < B\yteArrayCache.java > IntArrayCache.java
47 // % sed -e 's/(b\yte)[ ]*0/0.0f/g' -e 's/(b\yte)[ ]*/(float) /g' -e 's/b\yte/float/g' -e 's/B\yte/Float/g' < B\yteArrayCache.java > FloatArrayCache.java
48 // % sed -e 's/(b\yte)[ ]*0/0.0d/g' -e 's/(b\yte)[ ]*/(double) /g' -e 's/b\yte/double/g' -e 's/B\yte/Double/g' < B\yteArrayCache.java > DoubleArrayCache.java
49 
50 final class ByteArrayCache implements MarlinConst {
51 
52     final boolean clean;
53     private final int bucketCapacity;
54     private WeakReference<Bucket[]> refBuckets = null;
55     final CacheStats stats;
56 
ByteArrayCache(final boolean clean, final int bucketCapacity)57     ByteArrayCache(final boolean clean, final int bucketCapacity) {
58         this.clean = clean;
59         this.bucketCapacity = bucketCapacity;
60         this.stats = (DO_STATS) ?
61             new CacheStats(getLogPrefix(clean) + "ByteArrayCache") : null;
62     }
63 
getCacheBucket(final int length)64     Bucket getCacheBucket(final int length) {
65         final int bucket = ArrayCacheConst.getBucket(length);
66         return getBuckets()[bucket];
67     }
68 
getBuckets()69     private Bucket[] getBuckets() {
70         // resolve reference:
71         Bucket[] buckets = (refBuckets != null) ? refBuckets.get() : null;
72 
73         // create a new buckets ?
74         if (buckets == null) {
75             buckets = new Bucket[BUCKETS];
76 
77             for (int i = 0; i < BUCKETS; i++) {
78                 buckets[i] = new Bucket(clean, ARRAY_SIZES[i], bucketCapacity,
79                         (DO_STATS) ? stats.bucketStats[i] : null);
80             }
81 
82             // update weak reference:
83             refBuckets = new WeakReference<Bucket[]>(buckets);
84         }
85         return buckets;
86     }
87 
createRef(final int initialSize)88     Reference createRef(final int initialSize) {
89         return new Reference(this, initialSize);
90     }
91 
92     static final class Reference {
93 
94         // initial array reference (direct access)
95         final byte[] initial;
96         private final boolean clean;
97         private final ByteArrayCache cache;
98 
Reference(final ByteArrayCache cache, final int initialSize)99         Reference(final ByteArrayCache cache, final int initialSize) {
100             this.cache = cache;
101             this.clean = cache.clean;
102             this.initial = createArray(initialSize);
103             if (DO_STATS) {
104                 cache.stats.totalInitial += initialSize;
105             }
106         }
107 
getArray(final int length)108         byte[] getArray(final int length) {
109             if (length <= MAX_ARRAY_SIZE) {
110                 return cache.getCacheBucket(length).getArray();
111             }
112             if (DO_STATS) {
113                 cache.stats.oversize++;
114             }
115             if (DO_LOG_OVERSIZE) {
116                 logInfo(getLogPrefix(clean) + "ByteArrayCache: "
117                         + "getArray[oversize]: length=\t" + length);
118             }
119             return createArray(length);
120         }
121 
widenArray(final byte[] array, final int usedSize, final int needSize)122         byte[] widenArray(final byte[] array, final int usedSize,
123                           final int needSize)
124         {
125             final int length = array.length;
126             if (DO_CHECKS && length >= needSize) {
127                 return array;
128             }
129             if (DO_STATS) {
130                 cache.stats.resize++;
131             }
132 
133             // maybe change bucket:
134             // ensure getNewSize() > newSize:
135             final byte[] res = getArray(ArrayCacheConst.getNewSize(usedSize, needSize));
136 
137             // use wrapper to ensure proper copy:
138             System.arraycopy(array, 0, res, 0, usedSize); // copy only used elements
139 
140             // maybe return current array:
141             putArray(array, 0, usedSize); // ensure array is cleared
142 
143             if (DO_LOG_WIDEN_ARRAY) {
144                 logInfo(getLogPrefix(clean) + "ByteArrayCache: "
145                         + "widenArray[" + res.length
146                         + "]: usedSize=\t" + usedSize + "\tlength=\t" + length
147                         + "\tneeded length=\t" + needSize);
148             }
149             return res;
150         }
151 
putArray(final byte[] array)152         byte[] putArray(final byte[] array)
153         {
154             // dirty array helper:
155             return putArray(array, 0, array.length);
156         }
157 
putArray(final byte[] array, final int fromIndex, final int toIndex)158         byte[] putArray(final byte[] array, final int fromIndex,
159                         final int toIndex)
160         {
161             if (array.length <= MAX_ARRAY_SIZE) {
162                 if ((clean || DO_CLEAN_DIRTY) && (toIndex != 0)) {
163                     // clean-up array of dirty part[fromIndex; toIndex[
164                     fill(array, fromIndex, toIndex, (byte)0);
165                 }
166                 // ensure to never store initial arrays in cache:
167                 if (array != initial) {
168                     cache.getCacheBucket(array.length).putArray(array);
169                 }
170             }
171             return initial;
172         }
173     }
174 
175     static final class Bucket {
176 
177         private int tail = 0;
178         private final int arraySize;
179         private final boolean clean;
180         private final byte[][] arrays;
181         private final BucketStats stats;
182 
Bucket(final boolean clean, final int arraySize, final int capacity, final BucketStats stats)183         Bucket(final boolean clean, final int arraySize,
184                final int capacity, final BucketStats stats)
185         {
186             this.arraySize = arraySize;
187             this.clean = clean;
188             this.stats = stats;
189             this.arrays = new byte[capacity][];
190         }
191 
getArray()192         byte[] getArray() {
193             if (DO_STATS) {
194                 stats.getOp++;
195             }
196             // use cache:
197             if (tail != 0) {
198                 final byte[] array = arrays[--tail];
199                 arrays[tail] = null;
200                 return array;
201             }
202             if (DO_STATS) {
203                 stats.createOp++;
204             }
205             return createArray(arraySize);
206         }
207 
putArray(final byte[] array)208         void putArray(final byte[] array)
209         {
210             if (DO_CHECKS && (array.length != arraySize)) {
211                 logInfo(getLogPrefix(clean) + "ByteArrayCache: "
212                         + "bad length = " + array.length);
213                 return;
214             }
215             if (DO_STATS) {
216                 stats.returnOp++;
217             }
218             // fill cache:
219             if (arrays.length > tail) {
220                 arrays[tail++] = array;
221 
222                 if (DO_STATS) {
223                     stats.updateMaxSize(tail);
224                 }
225             } else if (DO_CHECKS) {
226                 logInfo(getLogPrefix(clean) + "ByteArrayCache: "
227                         + "array capacity exceeded !");
228             }
229         }
230     }
231 
createArray(final int length)232     static byte[] createArray(final int length) {
233         return new byte[length];
234     }
235 
fill(final byte[] array, final int fromIndex, final int toIndex, final byte value)236     static void fill(final byte[] array, final int fromIndex,
237                      final int toIndex, final byte value)
238     {
239         // clear array data:
240         Arrays.fill(array, fromIndex, toIndex, value);
241         if (DO_CHECKS) {
242             check(array, fromIndex, toIndex, value);
243         }
244     }
245 
check(final byte[] array, final int fromIndex, final int toIndex, final byte value)246     static void check(final byte[] array, final int fromIndex,
247                       final int toIndex, final byte value)
248     {
249         if (DO_CHECKS) {
250             // check zero on full array:
251             for (int i = 0; i < array.length; i++) {
252                 if (array[i] != value) {
253                     logException("Invalid value at: " + i + " = " + array[i]
254                             + " from: " + fromIndex + " to: " + toIndex + "\n"
255                             + Arrays.toString(array), new Throwable());
256 
257                     // ensure array is correctly filled:
258                     Arrays.fill(array, value);
259 
260                     return;
261                 }
262             }
263         }
264     }
265 
getLogPrefix(final boolean clean)266     static String getLogPrefix(final boolean clean) {
267         return (clean) ? "Clean" : "Dirty";
268     }
269 }
270