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        }