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