1 /*
   2  * Copyright (c) 2015, 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.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.testlibrary;
  25 
  26 import java.lang.reflect.Field;
  27 import java.util.ArrayList;
  28 import java.util.HashMap;
  29 import java.util.IdentityHashMap;
  30 
  31 /**
  32  * Utility functions to check that the static storages are pre-sized
  33  * optimally.
  34  */
  35 public final class OptimalCapacity {
  36 
  37     private OptimalCapacity() {}
  38 
  39     /**
  40      * Checks adequacy of the initial capacity of a static field
  41      * of type {@code ArrayList}.
  42      *
  43      * Having
  44      * <pre>
  45      * class XClass {
  46      *     static ArrayList theList = new ArrayList(N);
  47      * }
  48      * </pre>
  49      *
  50      * you should call from the test
  51      *
  52      * <pre>
  53      * OptimalCapacity.assertProperlySized(XClass.class, "theList", N);
  54      * </pre>
  55      */
  56     public static void ofArrayList(Class<?> clazz, String fieldName,
  57             int initialCapacity)
  58     {
  59         try {
  60             Field field = clazz.getDeclaredField(fieldName);
  61             field.setAccessible(true);
  62             Object obj = field.get(null);
  63             if (!ArrayList.class.equals(obj.getClass())) {
  64                 throw new RuntimeException("'" + field +
  65                     "' expected to be of type ArrayList");
  66             }
  67             ArrayList<?> list = (ArrayList<?>)obj;
  68 
  69             // For ArrayList the optimal capacity is its final size
  70             if (list.size() != initialCapacity) {
  71                 throw new RuntimeException("Size of '" + field +
  72                     "' is " + list.size() +
  73                     ", but expected to be " + initialCapacity);
  74             }
  75             if (internalArraySize(list) != initialCapacity) {
  76                 throw new RuntimeException("Capacity of '" + field +
  77                     "' is " + internalArraySize(list) +
  78                     ", but expected to be " + initialCapacity);
  79             }
  80         } catch (ReflectiveOperationException roe) {
  81             throw new RuntimeException(roe);
  82         }
  83     }
  84 
  85     /**
  86      * Checks adequacy of the initial capacity of a static field
  87      * of type {@code HashMap}.
  88      *
  89      * Having
  90      * <pre>
  91      * class XClass {
  92      *     static HashMap theMap = new HashMap(N);
  93      * }
  94      * </pre>
  95      *
  96      * you should call from the test
  97      *
  98      * <pre>
  99      * OptimalCapacity.ofHashMap(XClass.class, "theMap", N);
 100      * </pre>
 101      */
 102     public static void ofHashMap(Class<?> clazz, String fieldName,
 103             int initialCapacity)
 104     {
 105         try {
 106             Field field = clazz.getDeclaredField(fieldName);
 107             field.setAccessible(true);
 108             Object obj = field.get(null);
 109             if (!HashMap.class.equals(obj.getClass())) {
 110                 throw new RuntimeException(field +
 111                     " expected to be of type HashMap");
 112             }
 113             HashMap<?,?> map = (HashMap<?,?>)obj;
 114 
 115             // Check that the map allocates only necessary amount of space
 116             HashMap<Object, Object> tmp = new HashMap<>(map);
 117             if (internalArraySize(map) != internalArraySize(tmp)) {
 118                 throw new RuntimeException("Final capacity of '" + field +
 119                     "' is " + internalArraySize(map) +
 120                     ", which exceeds necessary minimum " + internalArraySize(tmp));
 121             }
 122 
 123             // Check that map is initially properly sized
 124             tmp = new HashMap<>(initialCapacity);
 125             tmp.put(new Object(), new Object()); // trigger storage init
 126             if (internalArraySize(map) != internalArraySize(tmp)) {
 127                 throw new RuntimeException("Requested capacity of '" + field +
 128                     "' was " + initialCapacity +
 129                     ", which resulted in final capacity " + internalArraySize(tmp) +
 130                     ", which differs from necessary minimum " + internalArraySize(map));
 131             }
 132 
 133         } catch (ReflectiveOperationException roe) {
 134             throw new RuntimeException(roe);
 135         }
 136     }
 137 
 138     /**
 139      * Checks adequacy of the expected maximum size of a static field
 140      * of type {@code IdentityHashMap}.
 141      *
 142      * Having
 143      * <pre>
 144      * class XClass {
 145      *     static IdentityHashMap theMap = new IdentityHashMap(M);
 146      * }
 147      * </pre>
 148      *
 149      * you should call from the test
 150      *
 151      * <pre>
 152      * OptimalCapacity.ofIdentityHashMap(XClass.class, "theMap", M);
 153      * </pre>
 154      */
 155     public static void ofIdentityHashMap(Class<?> clazz, String fieldName,
 156             int expectedMaxSize)
 157     {
 158         try {
 159             Field field = clazz.getDeclaredField(fieldName);
 160             field.setAccessible(true);
 161             Object obj = field.get(null);
 162             if (!IdentityHashMap.class.equals(obj.getClass())) {
 163                 throw new RuntimeException("'" + field +
 164                     "' expected to be of type IdentityHashMap");
 165             }
 166             IdentityHashMap<?,?> map = (IdentityHashMap<?,?>)obj;
 167 
 168             // Check that size of map is what was expected
 169             if (map.size() != expectedMaxSize) {
 170                 throw new RuntimeException("Size of '" + field +
 171                     "' is " + map.size() +
 172                     ", which differs from expected " + expectedMaxSize);
 173             }
 174 
 175             // Check that the map allocated only necessary amount of memory
 176             IdentityHashMap<Object, Object> tmp = new IdentityHashMap<>(map);
 177             if (internalArraySize(map) != internalArraySize(tmp)) {
 178                 throw new RuntimeException("Final capacity of '" + field +
 179                     "' is " + internalArraySize(map) +
 180                     ", which exceeds necessary minimum " + internalArraySize(tmp));
 181             }
 182 
 183             // Check that map was initially properly sized
 184             tmp = new IdentityHashMap<>(expectedMaxSize);
 185             tmp.put(new Object(), new Object()); // trigger storage init
 186             if (internalArraySize(map) != internalArraySize(tmp)) {
 187                 throw new RuntimeException("Requested number of elements in '" + field +
 188                     "' was " + expectedMaxSize +
 189                     ", which resulted in final capacity " + internalArraySize(tmp) +
 190                     ", which differs from necessary minimum " + internalArraySize(map));
 191             }
 192         } catch (ReflectiveOperationException roe) {
 193             throw new RuntimeException(roe);
 194         }
 195     }
 196 
 197     /**
 198      * Returns size of the internal storage.
 199      */
 200     private static int internalArraySize(Object container)
 201             throws ReflectiveOperationException {
 202         Field field;
 203         if (ArrayList.class.equals(container.getClass())) {
 204             field = ArrayList.class.getDeclaredField("elementData");
 205         } else if (HashMap.class.equals(container.getClass())) {
 206             field = HashMap.class.getDeclaredField("table");
 207         } else if (IdentityHashMap.class.equals(container.getClass())) {
 208             field = IdentityHashMap.class.getDeclaredField("table");
 209         } else {
 210             throw new RuntimeException("Unexpected class " +
 211                     container.getClass());
 212         }
 213         field.setAccessible(true);
 214         return ((Object[])field.get(container)).length;
 215     }
 216 }