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