/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance with the License. You may obtain a * copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package org.apache.geode.internal.size; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import java.lang.reflect.Array; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.LinkedList; import java.util.Map; import org.apache.geode.internal.util.concurrent.CopyOnWriteWeakHashMap; public class ObjectTraverser { private static final Map<Class, FieldSet> FIELD_CACHE = new CopyOnWriteWeakHashMap<Class, FieldSet>(); private static final FieldSet NON_PRIMATIVE_ARRAY = new FieldSet(null, null); /** * Visit all objects reachable from a given root object, using a breadth first search. Using this * method requires some heap space - probably between 8 - 30 bytes per reachable object. * * @param root object to traverse from * @param visitor a visitor to visit each node * @param includeStatics if true, the first time we see a new object type, we will visit all of * the static fields. * @throws IllegalArgumentException * @throws IllegalAccessException */ public static void breadthFirstSearch(Object root, Visitor visitor, boolean includeStatics) throws IllegalArgumentException, IllegalAccessException { VisitStack stack = new VisitStack(visitor, includeStatics); stack.add(null, root); while (!stack.isEmpty()) { Object next = stack.next(); doSearch(next, stack); } } private static void doSearch(Object root, VisitStack stack) throws IllegalArgumentException, IllegalAccessException { Class clazz = root.getClass(); boolean includeStatics = stack.shouldIncludeStatics(clazz); FieldSet set = FIELD_CACHE.get(clazz); if (set == null) { set = cacheFieldSet(clazz); } if (set == NON_PRIMATIVE_ARRAY) { Class componentType = clazz.getComponentType(); int length = Array.getLength(root); for (int i = 0; i < length; i++) { Object value = Array.get(root, i); stack.add(root, value); } return; } if (includeStatics) { for (Field field : set.getStaticFields()) { Object value = field.get(root); stack.add(root, value); } } for (Field field : set.getNonPrimativeFields()) { Object value = field.get(root); stack.add(root, value); } } private static FieldSet cacheFieldSet(Class clazz) { FieldSet set = buildFieldSet(clazz); FIELD_CACHE.put(clazz, set); return set; } private static FieldSet buildFieldSet(Class clazz) { ArrayList<Field> staticFields = new ArrayList<Field>(); ArrayList<Field> nonPrimativeFields = new ArrayList<Field>(); while (clazz != null) { if (clazz.isArray()) { Class componentType = clazz.getComponentType(); if (!componentType.isPrimitive()) { return NON_PRIMATIVE_ARRAY; } else { return new FieldSet(new Field[0], new Field[0]); } } Field[] fields = clazz.getDeclaredFields(); for (int i = 0; i < fields.length; i++) { Field field = fields[i]; Class fieldType = field.getType(); // skip static fields if we've already counted them once if (!fieldType.isPrimitive()) { field.setAccessible(true); if (Modifier.isStatic(field.getModifiers())) { staticFields.add(field); } else { nonPrimativeFields.add(field); } } } clazz = clazz.getSuperclass(); } return new FieldSet(staticFields.toArray(new Field[staticFields.size()]), nonPrimativeFields.toArray(new Field[nonPrimativeFields.size()])); } public interface Visitor { /** * Visit an object * * @param parent the parent of the object * @param object the object we are visiting * @return true the search should continue on and visit the children of this object as well */ boolean visit(Object parent, Object object); } private static class VisitStack { private ReferenceOpenHashSet seen = new ReferenceOpenHashSet(); private LinkedList stack = new LinkedList(); private Visitor visitor; private boolean includeStatics; VisitStack(Visitor visitor, boolean includeStatics) { this.visitor = visitor; this.includeStatics = includeStatics; } public void add(Object parent, Object object) { if (object == null) { return; } boolean newObject = !seen.contains(object); if (newObject) { seen.add(object); boolean visitChildren = visitor.visit(parent, object); if (visitChildren) { stack.add(object); } } } public Object next() { return stack.removeFirst(); } public boolean isEmpty() { return stack.isEmpty(); } public boolean shouldIncludeStatics(Class clazz) { if (!includeStatics) { return false; } boolean keyExists = seen.contains(clazz); seen.add(clazz); return !keyExists; } } private ObjectTraverser() { } private static class FieldSet { private final Field[] staticFields; private final Field[] nonPrimativeFields; public FieldSet(Field[] staticFields, Field[] nonPrimativeFields) { this.staticFields = staticFields; this.nonPrimativeFields = nonPrimativeFields; } public Field[] getStaticFields() { return staticFields; } public Field[] getNonPrimativeFields() { return nonPrimativeFields; } } }