/**
* Copyright (C) 2010-2011 J.W.Marsden
*
* 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 cc.plural.jsonij.marshal;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import cc.plural.jsonij.marshal.InspectorProperty.TYPE;
import cc.plural.jsonij.marshal.annotation.JSONAccessor;
import cc.plural.jsonij.marshal.annotation.JSONIgnore;
import cc.plural.jsonij.marshal.annotation.JSONMutator;
import cc.plural.jsonij.marshal.annotation.JSONName;
/**
*
* @author openecho
*/
public class Inspector {
public static final String IS_PREFIX;
public static final String SET_PREFIX;
public static final String GET_PREFIX;
Object o;
Class<?> c;
InspectorProperty[] properties;
InspectorFilter filter;
boolean innerArray;
boolean innerObject;
static {
IS_PREFIX = "is";
SET_PREFIX = "set";
GET_PREFIX = "get";
}
public Inspector(Class<?> c) {
this.c = c;
this.o = null;
properties = null;
filter = InspectorFilter.getDefaultFilters();
innerArray = false;
innerObject = false;
}
public Inspector(Object o) {
this.o = o;
properties = null;
filter = InspectorFilter.getDefaultFilters();
innerArray = false;
innerObject = false;
}
public Object getC() {
return c;
}
public Object getO() {
return o;
}
public boolean hasInnerArray() {
return innerArray;
}
public boolean hasInnerObject() {
return innerObject;
}
public boolean hasProperty(String name) {
for(InspectorProperty property: properties) {
if(property.getPropertyName().equals(name)) {
return true;
}
}
return false;
}
public InspectorProperty getProperty(String name) {
for(InspectorProperty property: properties) {
if(property.getPropertyName().equals(name)) {
return property;
}
}
return null;
}
public void inspect() {
if (o == null && c == null) {
return;
}
if (o != null) {
c = o.getClass();
}
Class<?> objectClass = c;
// Inspect Properties
InspectorProperty[] propertyList = getMethodProperties(objectClass);
if (propertyList != null) {
InspectorProperty[] fieldPropertyList = getAttributeProperties(objectClass);
if (fieldPropertyList != null) {
HashMap<String, InspectorProperty> propertiesMap = new HashMap<String, InspectorProperty>();
for (InspectorProperty prop : propertyList) {
propertiesMap.put(prop.getPropertyName(), prop);
}
for (InspectorProperty prop : fieldPropertyList) {
if (propertiesMap.containsKey(prop.getPropertyName())) {
InspectorProperty existingProp = propertiesMap.get(prop.getPropertyName());
if (!existingProp.hasAccessor()) {
existingProp.setAccessName(prop.getAccessName());
existingProp.setAccessPropertyType(TYPE.FIELD);
}
if (!existingProp.hasMutator()) {
existingProp.setMutateName(prop.getMutateName());
existingProp.setMutatePropertyType(TYPE.FIELD);
}
} else {
propertiesMap.put(prop.getPropertyName(), prop);
}
}
propertyList = new InspectorProperty[propertiesMap.size()];
propertiesMap.values().toArray(propertyList);
}
} else {
propertyList = getAttributeProperties(objectClass);
}
if (propertyList == null) {
propertyList = new InspectorProperty[0];
}
properties = propertyList;
// Inspect inner array (List type) and Inspect inner object (Map type)
Class<?>[] interfaces = null;
Class<?> parent = objectClass.getSuperclass();
if (parent != null) {
do {
interfaces = parent.getInterfaces();
for (int i = 0; i < Array.getLength(interfaces); i++) {
if (interfaces[i] == List.class) {
innerArray = true;
}
if (interfaces[i] == Map.class) {
innerObject = true;
}
if (innerArray && innerObject) {
break;
}
}
if (innerArray && innerObject) {
break;
}
} while (( parent = parent.getSuperclass() ) != null);
}
}
public InspectorProperty[] getAttributeProperties(Class<?> objectClass) {
InspectorProperty[] result = null;
HashMap<String, InspectorProperty> attributeProperties = new HashMap<String, InspectorProperty>();
Field[] objectFields = objectClass.getFields();
for (Field field : objectFields) {
if (isJSONIgnored(field) || filter.isFiltered(field.getDeclaringClass())) {
continue;
}
InspectionData fieldInspectionData = getFieldInspectionData(field);
if (fieldInspectionData == null || !fieldInspectionData.hasPropertyName()) {
continue;
}
String propertyName = fieldInspectionData.getPropertyName();
InspectorProperty property = null;
if (!attributeProperties.containsKey(propertyName)) {
property = new InspectorProperty(propertyName, TYPE.FIELD);
attributeProperties.put(propertyName, property);
property.setPropertyName(propertyName);
property.setAccessName(fieldInspectionData.getName());
property.setMutateName(fieldInspectionData.getName());
property.setAccessReturnType(fieldInspectionData.getReturnType());
property.setMutateInputType(fieldInspectionData.getArgumentType());
}
}
if (!attributeProperties.isEmpty()) {
result = new InspectorProperty[attributeProperties.size()];
attributeProperties.values().toArray(result);
}
return result;
}
public InspectorProperty[] getMethodProperties(Class<?> objectClass) {
InspectorProperty[] result = null;
HashMap<String, InspectorProperty> methodProperties = new HashMap<String, InspectorProperty>();
Method[] objectMethods = objectClass.getMethods();
for (Method method : objectMethods) {
if (isJSONIgnored(method) || filter.isFiltered(method.getDeclaringClass())) {
continue;
}
InspectionData methodInspectionData = getMethodInspectionData(method);
if (methodInspectionData == null || !methodInspectionData.hasPropertyName()) {
continue;
}
String propertyName = methodInspectionData.getPropertyName();
InspectorProperty property = null;
if (methodProperties.containsKey(propertyName)) {
property = methodProperties.get(propertyName);
} else {
property = new InspectorProperty(propertyName, TYPE.METHOD);
methodProperties.put(propertyName, property);
}
if (methodInspectionData.getAccessType() == ACCESS_TYPE.ACCESS && !property.hasAccessor()) {
property.setAccessName(methodInspectionData.getName());
property.setAccessReturnType(methodInspectionData.getReturnType());
} else if (methodInspectionData.getAccessType() == ACCESS_TYPE.MUTATE && !property.hasMutator()) {
property.setMutateName(methodInspectionData.getName());
property.setMutateInputType(methodInspectionData.getArgumentType());
}
}
if (!methodProperties.isEmpty()) {
result = new InspectorProperty[methodProperties.size()];
methodProperties.values().toArray(result);
}
return result;
}
public InspectionData getFieldInspectionData(Field field) {
InspectionData fieldInspectionData = new InspectionData();
fieldInspectionData.setName(field.getName());
fieldInspectionData.setAccessType(ACCESS_TYPE.BOTH);
String jsonName = Inspector.getJSONName(field);
if (jsonName != null) {
fieldInspectionData.setPropertyName(jsonName);
}
if (!fieldInspectionData.hasPropertyName()) {
fieldInspectionData.setPropertyName(fieldInspectionData.getName());
}
fieldInspectionData.setArgumentType(field.getType());
fieldInspectionData.setReturnType(field.getType());
return fieldInspectionData;
}
public InspectionData getMethodInspectionData(Method method) {
InspectionData methodInspectionData = new InspectionData();
methodInspectionData.setName(method.getName());
String jsonName = Inspector.getJSONName(method);
if (jsonName != null) {
methodInspectionData.setPropertyName(jsonName);
}
if (!methodInspectionData.hasPropertyName()) {
String accessorName = Inspector.getJSONAccessor(method);
if (accessorName != null) {
methodInspectionData.setPropertyName(accessorName);
methodInspectionData.setAccessType(ACCESS_TYPE.ACCESS);
}
}
if (!methodInspectionData.hasPropertyName()) {
String mutatorName = Inspector.getJSONMutator(method);
if (mutatorName != null) {
methodInspectionData.setPropertyName(mutatorName);
methodInspectionData.setAccessType(ACCESS_TYPE.MUTATE);
}
}
methodInspectionData.setReturnType(method.getReturnType());
if (!methodInspectionData.hasPropertyName()) {
char ch;
String propertyName = null;
String methodName = methodInspectionData.getName();
Class<?> returnType = methodInspectionData.getReturnType();
if (methodName.length() > IS_PREFIX.length() && methodName.startsWith(IS_PREFIX) && returnType == boolean.class && Character.isUpperCase(ch = methodName.charAt(IS_PREFIX.length()))) {
propertyName = Character.toLowerCase(ch) + methodName.substring(IS_PREFIX.length() + 1, methodName.length());
methodInspectionData.setPropertyName(propertyName);
methodInspectionData.setAccessType(ACCESS_TYPE.ACCESS);
} else if (methodName.length() > SET_PREFIX.length() && methodName.startsWith(SET_PREFIX) && Character.isUpperCase(ch = methodName.charAt(SET_PREFIX.length()))) {
propertyName = Character.toLowerCase(ch) + methodName.substring(SET_PREFIX.length() + 1, methodName.length());
methodInspectionData.setPropertyName(propertyName);
methodInspectionData.setAccessType(ACCESS_TYPE.MUTATE);
} else if (methodName.length() > GET_PREFIX.length() && methodName.startsWith(GET_PREFIX) && Character.isUpperCase(ch = methodName.charAt(GET_PREFIX.length())) && returnType != null) {
propertyName = Character.toLowerCase(ch) + methodName.substring(GET_PREFIX.length() + 1, methodName.length());
methodInspectionData.setPropertyName(propertyName);
methodInspectionData.setAccessType(ACCESS_TYPE.ACCESS);
}
}
if (methodInspectionData.getAccessType() == ACCESS_TYPE.ACCESS) {
Class<?> returnType = method.getReturnType();
if (returnType != null) {
methodInspectionData.setReturnType(returnType);
} else {
return null;
}
} else if (methodInspectionData.getAccessType() == ACCESS_TYPE.MUTATE) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (Array.getLength(parameterTypes) == 1) {
Class<?> parameterType = parameterTypes[0];
methodInspectionData.setArgumentType(parameterType);
} else {
return null;
}
}
return methodInspectionData;
}
public InspectorProperty[] getProperties() {
if (o != null && properties == null) {
inspect();
}
return properties;
}
public void setProperties(InspectorProperty[] properties) {
this.properties = properties;
}
public boolean isJSONIgnored(AccessibleObject object) {
return object.getAnnotation(JSONIgnore.class) != null;
}
public static String getAnnotatedName(AccessibleObject object) {
String name = getJSONName(object);
if (name == null) {
name = getJSONMutator(object);
}
if (name == null) {
name = getJSONAccessor(object);
}
return name;
}
public static String getJSONName(AccessibleObject object) {
String name = null;
JSONName jsonNameAnnotation = object.getAnnotation(JSONName.class);
if (jsonNameAnnotation != null) {
name = jsonNameAnnotation.value();
}
return name;
}
public static String getJSONMutator(AccessibleObject object) {
String name = null;
JSONMutator jsonMutatorAnnotation = object.getAnnotation(JSONMutator.class);
if (jsonMutatorAnnotation != null) {
name = jsonMutatorAnnotation.value();
}
return name;
}
public static String getJSONAccessor(AccessibleObject object) {
String name = null;
JSONAccessor jsonAccessorAnnotation = object.getAnnotation(JSONAccessor.class);
if (jsonAccessorAnnotation != null) {
name = jsonAccessorAnnotation.value();
}
return name;
}
public enum ACCESS_TYPE {
ACCESS, MUTATE, BOTH
}
public static class InspectionData {
protected String name;
protected String propertyName;
protected Class<?> returnType;
protected Class<?> argumentType;
protected ACCESS_TYPE accessType;
public InspectionData() {
name = null;
propertyName = null;
returnType = null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPropertyName() {
return propertyName;
}
public void setPropertyName(String propertyName) {
this.propertyName = propertyName;
}
public Class<?> getReturnType() {
return returnType;
}
public void setReturnType(Class<?> returnType) {
this.returnType = returnType;
}
public Class<?> getArgumentType() {
return argumentType;
}
public void setArgumentType(Class<?> argumentType) {
this.argumentType = argumentType;
}
public boolean hasPropertyName() {
return propertyName != null;
}
public ACCESS_TYPE getAccessType() {
return accessType;
}
public void setAccessType(ACCESS_TYPE accessType) {
this.accessType = accessType;
}
}
}