001 package railo.commons.lang; 002 003 import java.lang.reflect.Array; 004 import java.lang.reflect.Field; 005 import java.lang.reflect.Modifier; 006 import java.util.ArrayList; 007 import java.util.HashMap; 008 import java.util.HashSet; 009 import java.util.IdentityHashMap; 010 import java.util.Iterator; 011 import java.util.List; 012 import java.util.Map; 013 import java.util.Map.Entry; 014 import java.util.Set; 015 016 import railo.runtime.exp.ExpressionException; 017 import railo.runtime.exp.PageRuntimeException; 018 import railo.runtime.op.Caster; 019 import railo.runtime.type.Sizeable; 020 021 /** 022 * Calculation of object size. 023 */ 024 public class SizeOf { 025 public static final int OBJECT_GRANULARITY_IN_BYTES = 8; 026 public static final int WORD_SIZE = Architecture.getVMArchitecture().getWordSize(); 027 public static final int HEADER_SIZE = 2 * WORD_SIZE; 028 029 public static final int DOUBLE_SIZE = 8; 030 public static final int FLOAT_SIZE = 4; 031 public static final int LONG_SIZE = 8; 032 public static final int INT_SIZE = 4; 033 public static final int SHORT_SIZE = 2; 034 public static final int BYTE_SIZE = 1; 035 public static final int BOOLEAN_SIZE = 1; 036 public static final int CHAR_SIZE = 2; 037 public static final int REF_SIZE = WORD_SIZE; 038 039 private static ThreadLocal _inside=new ThreadLocal(); 040 private static ThreadLocal map=new ThreadLocal(); 041 042 043 private static ThreadLocal<Set<Integer>> done=new ThreadLocal<Set<Integer>>(); 044 045 046 private static boolean inside(boolean inside) { 047 Boolean was=(Boolean) _inside.get(); 048 _inside.set(inside?Boolean.TRUE:Boolean.FALSE); 049 return was!=null && was.booleanValue(); 050 } 051 052 private static Map get(boolean clear) { 053 Map m=(Map) map.get(); 054 if(m==null){ 055 m=new IdentityHashMap(); 056 map.set(m); 057 } 058 else if(clear) m.clear(); 059 060 return m; 061 } 062 063 064 /** 065 * Calculates the size of an object. 066 * @param object the object that we want to have the size calculated. 067 * @return the size of the object or 0 if null. 068 */ 069 070 public static long size2(Object o) { 071 Set<Integer> d = done.get(); 072 boolean inside=true; 073 if(d==null){ 074 inside=false; 075 d=new HashSet<Integer>(); 076 done.set(d); 077 } 078 try{ 079 return size(o,d); 080 } 081 finally{ 082 if(!inside) done.set(null); 083 } 084 } 085 086 private static long size(Object o,Set<Integer> done) { 087 if(o == null) return 0; 088 if(done.contains(o.hashCode())) return 0; 089 done.add(o.hashCode()); 090 091 if(o instanceof Sizeable){ 092 return ((Sizeable)o).sizeOf(); 093 } 094 Class clazz = o.getClass(); 095 long size=0; 096 097 // Native ARRAY 098 // TODO how big is the array itself 099 if (clazz.isArray()) { 100 Class ct = clazz.getComponentType(); 101 102 if(ct.isPrimitive())return primSize(ct)*Array.getLength(o); 103 104 size=REF_SIZE*Array.getLength(o); 105 for (int i=Array.getLength(o)-1; i>=0;i--) { 106 size += size(Array.get(o, i),done); 107 } 108 return size; 109 } 110 111 if(o instanceof Boolean) return REF_SIZE+BOOLEAN_SIZE; 112 if(o instanceof Character) return REF_SIZE+CHAR_SIZE; 113 114 if(o instanceof Number){ 115 if(o instanceof Double || o instanceof Long) return REF_SIZE+LONG_SIZE; 116 if(o instanceof Byte) return REF_SIZE+BYTE_SIZE; 117 if(o instanceof Short) return REF_SIZE+SHORT_SIZE; 118 return REF_SIZE+INT_SIZE;// float,int 119 } 120 if(o instanceof String){ 121 int len=((String)o).length(); 122 return (REF_SIZE*len)+(REF_SIZE*CHAR_SIZE); 123 } 124 125 126 throw new PageRuntimeException(new ExpressionException("can not terminate the size of a object of type ["+Caster.toTypeName(o)+":"+o.getClass().getName()+"]")); 127 } 128 129 130 private static int primSize(Class ct) { 131 if(ct==double.class) return LONG_SIZE; 132 if(ct==long.class) return LONG_SIZE; 133 if(ct==float.class) return INT_SIZE; 134 if(ct==short.class) return SHORT_SIZE; 135 if(ct==int.class) return INT_SIZE; 136 if(ct==byte.class) return BYTE_SIZE; 137 if(ct==boolean.class) return BOOLEAN_SIZE; 138 return CHAR_SIZE; 139 } 140 141 public static long size(Object object) { 142 return size(object, Integer.MAX_VALUE); 143 } 144 145 public static long size(Object object,int maxDepth) { 146 if (object==null)return 0; 147 boolean wasInside=inside(true); 148 Map instances=get(!wasInside); 149 //IdentityHashMap instances = new IdentityHashMap(); 150 Map dictionary = new HashMap(); 151 long size = _size(object, instances, dictionary, maxDepth, Long.MAX_VALUE); 152 inside(wasInside); 153 return size; 154 } 155 156 157 private static long _size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { 158 try { 159 return __size(object, instances, dictionary, maxDepth, maxSize); 160 } 161 catch(Throwable t){ 162 t.printStackTrace(); 163 return 0; 164 } 165 } 166 167 private static long __size(Object object, Map instances,Map dictionary, int maxDepth,long maxSize) { 168 if (object==null || instances.containsKey(object) || maxDepth==0 || maxSize < 0) return 0; 169 170 instances.put(object, object); 171 172 if(object instanceof Sizeable)return ((Sizeable)object).sizeOf(); 173 174 if(object instanceof String){ 175 return (SizeOf.CHAR_SIZE*((String)object).length())+SizeOf.REF_SIZE; 176 } 177 178 if(object instanceof Number){ 179 if(object instanceof Double) return SizeOf.DOUBLE_SIZE+SizeOf.REF_SIZE; 180 if(object instanceof Float) return SizeOf.FLOAT_SIZE+SizeOf.REF_SIZE; 181 if(object instanceof Long) return SizeOf.LONG_SIZE+SizeOf.REF_SIZE; 182 if(object instanceof Integer) return SizeOf.INT_SIZE+SizeOf.REF_SIZE; 183 if(object instanceof Short) return SizeOf.SHORT_SIZE+SizeOf.REF_SIZE; 184 if(object instanceof Byte) return SizeOf.BYTE_SIZE+SizeOf.REF_SIZE; 185 } 186 187 188 if(object instanceof Object[]) { 189 Object[] arr=(Object[]) object; 190 long size=SizeOf.REF_SIZE; 191 for(int i=0;i<arr.length;i++){ 192 size+=_size(arr[i], instances, dictionary, maxDepth - 1, maxSize); 193 } 194 return size; 195 } 196 197 if(object instanceof Map) { 198 long size=SizeOf.REF_SIZE; 199 Map.Entry entry; 200 Map map=(Map) object; 201 Iterator it = map.entrySet().iterator(); 202 while(it.hasNext()){ 203 entry=(Entry) it.next(); 204 size+=SizeOf.REF_SIZE; 205 size+=_size(entry.getKey(), instances, dictionary, maxDepth - 1, maxSize); 206 size+=_size(entry.getValue(), instances, dictionary, maxDepth - 1, maxSize); 207 } 208 return size; 209 } 210 if(object instanceof List) { 211 long size=SizeOf.REF_SIZE; 212 List list=(List) object; 213 Iterator it = list.iterator(); 214 while(it.hasNext()){ 215 size+=_size(it.next(), instances, dictionary, maxDepth - 1, maxSize); 216 } 217 return size; 218 } 219 220 221 Class clazz = object.getClass(); 222 223 Meta cmd = Meta.getMetaData(clazz, dictionary); 224 long shallowSize = cmd.calcInstanceSize(object); 225 long size = shallowSize; 226 if (clazz.isArray() && !cmd.getComponentClass().isPrimitive()) { 227 for (int i=Array.getLength(object)-1; i>=0;i--) { 228 size += _size(Array.get(object, i), instances, dictionary, maxDepth - 1, maxSize - size); 229 } 230 } 231 else { 232 List values = cmd.getReferenceFieldValues(object); 233 Iterator it = values.iterator(); 234 while(it.hasNext()) { 235 size += _size(it.next(), instances, dictionary, maxDepth - 1, maxSize - size); 236 } 237 } 238 return size; 239 } 240 241 public static long size(long value) { 242 return SizeOf.LONG_SIZE; 243 } 244 245 246 public static long size(boolean value) { 247 return SizeOf.BOOLEAN_SIZE; 248 } 249 } 250 251 252 class Meta { 253 254 255 /** Class for which this metadata applies */ 256 private Class aClass; 257 258 /** Parent class's metadata */ 259 private Meta superMetaData; 260 261 private boolean isArray; 262 263 /** Only filled if this is an array */ 264 private int componentSize; 265 266 /** Only filled if this class is an array */ 267 private Class componentClass; 268 269 /** Number of bytes reserved for the fields of this class and its parents, 270 * aligned to a word boundary. */ 271 private int fieldSize; 272 273 /** this.fieldSize + super.totalFieldSize */ 274 private int totalFieldSize; 275 276 /** Number of bytes reserved for instances of this class, aligned to a 277 * 8 bytes boundary */ 278 private int instanceSize; 279 280 private List referenceFields = new ArrayList(); 281 282 public static Meta getMetaData(Class aClass) { 283 return getMetaData(aClass, new HashMap()); 284 } 285 286 public static Meta getMetaData(Class aClass, Map dictionary) { 287 if (aClass == null) { 288 throw new IllegalArgumentException("aClass argument cannot be null"); 289 } 290 291 if (aClass.isPrimitive()) { 292 throw new IllegalArgumentException("ClassMetaData not supported for primitive types: " + aClass.getName()); 293 } 294 295 if (dictionary.containsKey(aClass)) { 296 return (Meta) dictionary.get(aClass); 297 } 298 Meta result = new Meta(aClass, dictionary); 299 dictionary.put(aClass, result); 300 return result; 301 } 302 303 private Meta(Class aClass, Map dictionary) { 304 305 Class parentClass = aClass.getSuperclass(); 306 if (parentClass != null) { 307 superMetaData = getMetaData(parentClass, dictionary); 308 } 309 310 this.aClass = aClass; 311 312 if (aClass.isArray()) { 313 processArrayClass(aClass); 314 } else { 315 processRegularClass(aClass); 316 processFields(aClass); 317 } 318 } 319 320 private static int getComponentSize(Class componentClass) { 321 if (componentClass.equals(Double.TYPE) || 322 componentClass.equals(Long.TYPE)) { 323 return SizeOf.LONG_SIZE; 324 } 325 326 if (componentClass.equals(Integer.TYPE) || 327 componentClass.equals(Float.TYPE)) { 328 return SizeOf.INT_SIZE; 329 } 330 331 if (componentClass.equals(Character.TYPE) || 332 componentClass.equals(Short.TYPE)) { 333 return SizeOf.SHORT_SIZE; 334 } 335 336 if (componentClass.equals(Byte.TYPE) || 337 componentClass.equals(Boolean.TYPE)) { 338 return SizeOf.BYTE_SIZE; 339 } 340 341 return SizeOf.REF_SIZE; 342 } 343 344 public int getFieldSize() { 345 return fieldSize; 346 } 347 348 private void processArrayClass(Class aClass) { 349 isArray = true; 350 componentClass = aClass.getComponentType(); 351 componentSize = getComponentSize(componentClass); 352 instanceSize = SizeOf.HEADER_SIZE + SizeOf.WORD_SIZE; 353 } 354 355 private void processFields(Class aClass) { 356 Field[] fields = aClass.getDeclaredFields(); 357 for (int i = 0; i < fields.length; i++) { 358 Field field = fields[i]; 359 int fieldModifier = field.getModifiers(); 360 if (Modifier.isStatic(fieldModifier)) { 361 continue; 362 } 363 Class fieldType = field.getType(); 364 if (fieldType.isPrimitive()) { 365 continue; 366 } 367 field.setAccessible(true); 368 referenceFields.add(field); 369 } 370 } 371 372 private void processRegularClass(Class aClass) { 373 Field[] fields = aClass.getDeclaredFields(); 374 375 int longCount = 0; 376 int intCount = 0; 377 int shortCount = 0; 378 int byteCount = 0; 379 int refCount = 0; 380 381 // Calculate how many fields of each type we have. 382 for (int i = 0; i < fields.length; i++) { 383 Field field = fields[i]; 384 int fieldModifiers = field.getModifiers(); 385 if (Modifier.isStatic(fieldModifiers)) { 386 continue; 387 } 388 389 Class fieldClass = field.getType(); 390 391 if (fieldClass.equals(Double.TYPE) || 392 fieldClass.equals(Long.TYPE)) { 393 longCount++; 394 } else if (fieldClass.equals(Integer.TYPE) || 395 fieldClass.equals(Float.TYPE)) { 396 intCount++; 397 } else if (fieldClass.equals(Character.TYPE) || 398 fieldClass.equals(Short.TYPE)) { 399 shortCount++; 400 }else if (fieldClass.equals(Byte.TYPE) || 401 fieldClass.equals(Boolean.TYPE)) { 402 byteCount++; 403 } else { 404 refCount++; 405 } 406 } 407 408 int localIntCount = intCount; 409 int localShortCount = shortCount; 410 int localByteCount = byteCount; 411 int localRefCount = refCount; 412 413 int parentTotalFieldSize = superMetaData == null ? 0 : superMetaData.getFieldSize(); 414 415 int alignedParentSize = align(parentTotalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 416 int paddingSpace = alignedParentSize - parentTotalFieldSize; 417 // we try to pad with ints, and then shorts, and then bytes, and then refs. 418 while (localIntCount > 0 && paddingSpace >= SizeOf.INT_SIZE) { 419 paddingSpace -= SizeOf.INT_SIZE; 420 localIntCount--; 421 } 422 423 while(localShortCount > 0 && paddingSpace >= SizeOf.SHORT_SIZE) { 424 paddingSpace -= SizeOf.SHORT_SIZE; 425 localShortCount--; 426 } 427 428 while (localByteCount > 0 && paddingSpace >= SizeOf.BYTE_SIZE) { 429 paddingSpace -= SizeOf.BYTE_SIZE; 430 localByteCount--; 431 } 432 433 while (localRefCount > 0 && paddingSpace >= SizeOf.REF_SIZE) { 434 paddingSpace -= SizeOf.REF_SIZE; 435 localRefCount--; 436 } 437 438 int preFieldSize = paddingSpace + 439 longCount * SizeOf.LONG_SIZE + 440 intCount * SizeOf.INT_SIZE + 441 shortCount * SizeOf.SHORT_SIZE + 442 byteCount * SizeOf.BYTE_SIZE + 443 refCount * SizeOf.REF_SIZE; 444 445 fieldSize = align(preFieldSize, SizeOf.REF_SIZE); 446 447 totalFieldSize = parentTotalFieldSize + fieldSize; 448 449 instanceSize = align(SizeOf.HEADER_SIZE + totalFieldSize, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 450 } 451 452 public int calcArraySize(int length) { 453 return align(instanceSize + componentSize * length, SizeOf.OBJECT_GRANULARITY_IN_BYTES); 454 } 455 456 public int calcInstanceSize(Object instance) { 457 if (instance == null) { 458 throw new IllegalArgumentException("Parameter cannot be null"); 459 } 460 if (!instance.getClass().equals(aClass)) { 461 throw new IllegalArgumentException("Parameter not of proper class. Was: " + instance.getClass() + " expected: " + aClass); 462 } 463 464 if (isArray) { 465 int length = Array.getLength(instance); 466 return calcArraySize(length); 467 } 468 return instanceSize; 469 } 470 471 public List getReferenceFieldValues(Object instance) { 472 if (instance == null) { 473 throw new IllegalArgumentException("Parameter cannot be null."); 474 } 475 476 List results; 477 if (superMetaData == null) { 478 results = new ArrayList(); 479 } else { 480 results = superMetaData.getReferenceFieldValues(instance); 481 } 482 Iterator it = referenceFields.iterator(); 483 Field field; 484 while(it.hasNext()) { 485 field=(Field) it.next(); 486 Object value; 487 try { 488 value = field.get(instance); 489 } catch (IllegalAccessException ex) { 490 // Should never happen in practice. 491 throw new IllegalStateException("Unexpected exeption: "+ ex.getMessage()); 492 } 493 if (value != null) { 494 results.add(value); 495 } 496 } 497 return results; 498 } 499 500 public Meta getSuperMetaData() { 501 return superMetaData; 502 } 503 504 public int getInstanceSize() { 505 return instanceSize; 506 } 507 508 public int getTotalFieldSize() { 509 return totalFieldSize; 510 } 511 512 public Class getTheClass() { 513 return aClass; 514 } 515 516 public Class getComponentClass() { 517 return componentClass; 518 } 519 520 public static int align(int size, int granularity) { 521 return size + (granularity - size % granularity) % granularity; 522 } 523 } 524 525 526 class Architecture { 527 528 private static final Architecture ARCH_32_BITS=new Architecture(32, 4); 529 private static final Architecture ARCH_64_BITS=new Architecture(64, 8); 530 private static final Architecture ARCH_UNKNOWN=new Architecture(32, 4); 531 532 private int bits; 533 private int wordSize; 534 535 private Architecture(int bits, int wordSize) { 536 this.bits = bits; 537 this.wordSize = wordSize; 538 } 539 540 public int getBits() { 541 return bits; 542 } 543 544 public int getWordSize() { 545 return wordSize; 546 } 547 548 public static Architecture getVMArchitecture() { 549 String archString = System.getProperty("sun.arch.data.model"); 550 if (archString != null) { 551 if (archString.equals("32")) { 552 return ARCH_32_BITS; 553 } else if (archString.equals("64")) { 554 return ARCH_64_BITS; 555 } 556 } 557 return ARCH_UNKNOWN; 558 } 559 }