170 lines
5.6 KiB
Java
170 lines
5.6 KiB
Java
/*
|
|
* Copyright (C) 2008 Google Inc.
|
|
*
|
|
* Licensed 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.mcteam.factions.gson;
|
|
|
|
import java.lang.reflect.AccessibleObject;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Type;
|
|
|
|
/**
|
|
* Provides ability to apply a visitor to an object and all of its fields
|
|
* recursively.
|
|
*
|
|
* @author Inderjeet Singh
|
|
* @author Joel Leitch
|
|
*/
|
|
final class ObjectNavigator {
|
|
|
|
public interface Visitor {
|
|
public void start(ObjectTypePair node);
|
|
|
|
public void end(ObjectTypePair node);
|
|
|
|
/**
|
|
* This is called before the object navigator starts visiting the current
|
|
* object
|
|
*/
|
|
void startVisitingObject(Object node);
|
|
|
|
/**
|
|
* This is called to visit the current object if it is an array
|
|
*/
|
|
void visitArray(Object array, Type componentType);
|
|
|
|
/**
|
|
* This is called to visit an object field of the current object
|
|
*/
|
|
void visitObjectField(FieldAttributes f, Type typeOfF, Object obj);
|
|
|
|
/**
|
|
* This is called to visit an array field of the current object
|
|
*/
|
|
void visitArrayField(FieldAttributes f, Type typeOfF, Object obj);
|
|
|
|
/**
|
|
* This is called to visit an object using a custom handler
|
|
*
|
|
* @return true if a custom handler exists, false otherwise
|
|
*/
|
|
public boolean visitUsingCustomHandler(ObjectTypePair objTypePair);
|
|
|
|
/**
|
|
* This is called to visit a field of the current object using a custom
|
|
* handler
|
|
*/
|
|
public boolean visitFieldUsingCustomHandler(FieldAttributes f, Type actualTypeOfField,
|
|
Object parent);
|
|
|
|
/**
|
|
* Retrieve the current target
|
|
*/
|
|
Object getTarget();
|
|
|
|
void visitPrimitive(Object primitive);
|
|
}
|
|
|
|
private final ExclusionStrategy exclusionStrategy;
|
|
private final ObjectTypePair objTypePair;
|
|
|
|
/**
|
|
* @param objTypePair
|
|
* The object,type (fully genericized) being navigated
|
|
* @param exclusionStrategy
|
|
* the concrete strategy object to be used to filter out fields of an
|
|
* object.
|
|
*/
|
|
ObjectNavigator(ObjectTypePair objTypePair, ExclusionStrategy exclusionStrategy) {
|
|
Preconditions.checkNotNull(exclusionStrategy);
|
|
|
|
this.objTypePair = objTypePair;
|
|
this.exclusionStrategy = exclusionStrategy;
|
|
}
|
|
|
|
/**
|
|
* Navigate all the fields of the specified object. If a field is null, it
|
|
* does not get visited.
|
|
*/
|
|
public void accept(Visitor visitor) {
|
|
TypeInfo objTypeInfo = new TypeInfo(objTypePair.type);
|
|
if (exclusionStrategy.shouldSkipClass(objTypeInfo.getRawClass())) {
|
|
return;
|
|
}
|
|
boolean visitedWithCustomHandler = visitor.visitUsingCustomHandler(objTypePair);
|
|
if (!visitedWithCustomHandler) {
|
|
Object obj = objTypePair.getObject();
|
|
Object objectToVisit = (obj == null) ? visitor.getTarget() : obj;
|
|
if (objectToVisit == null) {
|
|
return;
|
|
}
|
|
objTypePair.setObject(objectToVisit);
|
|
visitor.start(objTypePair);
|
|
try {
|
|
if (objTypeInfo.isArray()) {
|
|
visitor.visitArray(objectToVisit, objTypePair.type);
|
|
} else if (objTypeInfo.getActualType() == Object.class
|
|
&& isPrimitiveOrString(objectToVisit)) {
|
|
// TODO(Joel): this is only used for deserialization of "primitives"
|
|
// we should rethink this!!!
|
|
visitor.visitPrimitive(objectToVisit);
|
|
objectToVisit = visitor.getTarget();
|
|
} else {
|
|
visitor.startVisitingObject(objectToVisit);
|
|
ObjectTypePair currObjTypePair = objTypePair.toMoreSpecificType();
|
|
Class<?> topLevelClass = new TypeInfo(currObjTypePair.type).getRawClass();
|
|
for (Class<?> curr = topLevelClass; curr != null && !curr.equals(Object.class); curr =
|
|
curr.getSuperclass()) {
|
|
if (!curr.isSynthetic()) {
|
|
navigateClassFields(objectToVisit, curr, visitor);
|
|
}
|
|
}
|
|
}
|
|
} finally {
|
|
visitor.end(objTypePair);
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean isPrimitiveOrString(Object objectToVisit) {
|
|
Class<?> realClazz = objectToVisit.getClass();
|
|
return realClazz == Object.class || realClazz == String.class
|
|
|| Primitives.unwrap(realClazz).isPrimitive();
|
|
}
|
|
|
|
private void navigateClassFields(Object obj, Class<?> clazz, Visitor visitor) {
|
|
Field[] fields = clazz.getDeclaredFields();
|
|
AccessibleObject.setAccessible(fields, true);
|
|
for (Field f : fields) {
|
|
FieldAttributes fieldAttributes = new FieldAttributes(clazz, f);
|
|
if (exclusionStrategy.shouldSkipField(fieldAttributes)
|
|
|| exclusionStrategy.shouldSkipClass(fieldAttributes.getDeclaredClass())) {
|
|
continue; // skip
|
|
}
|
|
TypeInfo fieldTypeInfo = TypeInfoFactory.getTypeInfoForField(f, objTypePair.type);
|
|
Type declaredTypeOfField = fieldTypeInfo.getActualType();
|
|
boolean visitedWithCustomHandler =
|
|
visitor.visitFieldUsingCustomHandler(fieldAttributes, declaredTypeOfField, obj);
|
|
if (!visitedWithCustomHandler) {
|
|
if (fieldTypeInfo.isArray()) {
|
|
visitor.visitArrayField(fieldAttributes, declaredTypeOfField, obj);
|
|
} else {
|
|
visitor.visitObjectField(fieldAttributes, declaredTypeOfField, obj);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|