blob: 29295a684a4873b1833916e0a1ea05d9d1c6c9a7 [file] [log] [blame]
/*
* 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 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 it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet;
import org.apache.geode.annotations.Immutable;
import org.apache.geode.annotations.internal.MakeNotStatic;
import org.apache.geode.internal.util.concurrent.CopyOnWriteWeakHashMap;
public class ObjectTraverser {
@MakeNotStatic
private static final Map<Class, FieldSet> FIELD_CACHE =
new CopyOnWriteWeakHashMap<Class, FieldSet>();
@Immutable
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.
*/
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[0]),
nonPrimativeFields.toArray(new Field[0]));
}
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;
}
}
}