001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *    http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 *
019 */
020package org.apache.mina.filter.executor;
021
022import java.lang.reflect.Field;
023import java.lang.reflect.Modifier;
024import java.util.HashSet;
025import java.util.Set;
026import java.util.concurrent.ConcurrentHashMap;
027import java.util.concurrent.ConcurrentMap;
028
029import org.apache.mina.core.buffer.IoBuffer;
030import org.apache.mina.core.session.IoEvent;
031import org.apache.mina.core.write.WriteRequest;
032
033/**
034 * A default {@link IoEventSizeEstimator} implementation.
035 * <p>
036 * <a href="http://martin.nobilitas.com/java/sizeof.html">Martin's Java Notes</a>
037 * was used for estimation.  For unknown types, it inspects declaring fields of the
038 * class of the specified event and the parameter of the event.  The size of unknown
039 * declaring fields are approximated to the specified <tt>averageSizePerField</tt>
040 * (default: 64).
041 * <p>
042 * All the estimated sizes of classes are cached for performance improvement.
043 *
044 * @author <a href="http://mina.apache.org">Apache MINA Project</a>
045 */
046public class DefaultIoEventSizeEstimator implements IoEventSizeEstimator {
047    /** A map containing the estimated size of each Java objects we know for */
048    private final ConcurrentMap<Class<?>, Integer> class2size = new ConcurrentHashMap<Class<?>, Integer>();
049
050    /**
051     * Create a new instance of this class, injecting the known size of
052     * basic java types.
053     */
054    public DefaultIoEventSizeEstimator() {
055        class2size.put(boolean.class, 4); // Probably an integer.
056        class2size.put(byte.class, 1);
057        class2size.put(char.class, 2);
058        class2size.put(int.class, 4);
059        class2size.put(short.class, 2);
060        class2size.put(long.class, 8);
061        class2size.put(float.class, 4);
062        class2size.put(double.class, 8);
063        class2size.put(void.class, 0);
064    }
065
066    /**
067     * {@inheritDoc}
068     */
069    public int estimateSize(IoEvent event) {
070        return estimateSize((Object) event) + estimateSize(event.getParameter());
071    }
072
073    /**
074     * Estimate the size of an Object in number of bytes
075     * @param message The object to estimate
076     * @return The estimated size of the object
077     */
078    public int estimateSize(Object message) {
079        if (message == null) {
080            return 8;
081        }
082
083        int answer = 8 + estimateSize(message.getClass(), null);
084
085        if (message instanceof IoBuffer) {
086            answer += ((IoBuffer) message).remaining();
087        } else if (message instanceof WriteRequest) {
088            answer += estimateSize(((WriteRequest) message).getMessage());
089        } else if (message instanceof CharSequence) {
090            answer += ((CharSequence) message).length() << 1;
091        } else if (message instanceof Iterable) {
092            for (Object m : (Iterable<?>) message) {
093                answer += estimateSize(m);
094            }
095        }
096
097        return align(answer);
098    }
099
100    private int estimateSize(Class<?> clazz, Set<Class<?>> visitedClasses) {
101        Integer objectSize = class2size.get(clazz);
102        
103        if (objectSize != null) {
104            return objectSize;
105        }
106
107        if (visitedClasses != null) {
108            if (visitedClasses.contains(clazz)) {
109                return 0;
110            }
111        } else {
112            visitedClasses = new HashSet<Class<?>>();
113        }
114
115        visitedClasses.add(clazz);
116
117        int answer = 8; // Basic overhead.
118        
119        for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
120            Field[] fields = c.getDeclaredFields();
121            
122            for (Field f : fields) {
123                if ((f.getModifiers() & Modifier.STATIC) != 0) {
124                    // Ignore static fields.
125                    continue;
126                }
127
128                answer += estimateSize(f.getType(), visitedClasses);
129            }
130        }
131
132        visitedClasses.remove(clazz);
133
134        // Some alignment.
135        answer = align(answer);
136
137        // Put the final answer.
138        Integer tmpAnswer = class2size.putIfAbsent(clazz, answer);
139
140        if (tmpAnswer != null) {
141            answer = tmpAnswer;
142        }
143
144        return answer;
145    }
146
147    private static int align(int size) {
148        if (size % 8 != 0) {
149            size /= 8;
150            size++;
151            size *= 8;
152        }
153        
154        return size;
155    }
156}