org.xerial.lens.ObjectLens Maven / Gradle / Ivy
/*--------------------------------------------------------------------------
* Copyright 2009 Taro L. Saito
*
* 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.
*--------------------------------------------------------------------------*/
//--------------------------------------
// XerialJ
//
// ObjectLens.java
// Since: 2009/05/12 19:52:38
//
// $URL$
// $Author$
//--------------------------------------
package org.xerial.lens;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.xerial.core.XerialException;
import org.xerial.json.JSONString;
import org.xerial.json.JSONWriter;
import org.xerial.lens.impl.ParameterGetter;
import org.xerial.lens.impl.ParameterSetter;
import org.xerial.lens.impl.RelationSetter;
import org.xerial.silk.SilkWriter;
import org.xerial.util.Pair;
import org.xerial.util.StringUtil;
import org.xerial.util.bean.BeanUtil;
import org.xerial.util.bean.TypeInfo;
import org.xerial.util.log.Logger;
import org.xerial.util.reflect.ReflectionUtil;
/**
* Tree to Object lens
*
* @author leo
*
*/
public class ObjectLens {
private static Logger _logger = Logger.getLogger(ObjectLens.class);
private static HashMap, ObjectLens> cache = new HashMap, ObjectLens>();
/**
* Get the lens of the target type
*
* @param target
* @return lens of the target type
*/
public static ObjectLens getObjectLens(Class< ? > target) {
if (cache.containsKey(target))
return cache.get(target);
else {
cache.put(target, new ObjectLens(target));
return getObjectLens(target);
}
}
private final Class< ? > targetType;
private final List getterContainer = new ArrayList();
private final List setterContainer = new ArrayList();
private final HashMap getterIndex = new HashMap();
private final HashMap setterIndex = new HashMap();
public static String getCanonicalParameterName(String paramName) {
return StringUtil.varNameToCanonicalName(paramName);
}
private final List relationSetterContainer = new ArrayList();
private ParameterSetter valueSetter = null;
private RelationSetter propertySetter = null;
private ParameterGetter propertyGetter = null;
public Object getParameter(Object target, String parameterName) throws XerialException {
ParameterGetter getter = getterIndex.get(getCanonicalParameterName(parameterName));
if (getter == null)
return null;
return getter.get(target);
}
public Object getParameter(Object target, String parameterName, Object key)
throws XerialException {
ParameterGetter getter = getterIndex.get(getCanonicalParameterName(parameterName));
if (getter == null)
return getProperty(target, key != null ? key.toString() : null);
if (getter.returnsMapType())
return getter.get(target, key.toString());
else
return getter.get(target);
}
public void setParameter(Object target, String parameterName, Object key, Object value)
throws XerialException {
ParameterSetter setter = setterIndex.get(getCanonicalParameterName(parameterName));
if (setter == null) {
setProperty(target, key, value);
}
else {
if (setter.acceptKeyAndValue())
setter.bind(target, key, value);
else
setter.bind(target, value);
}
}
public void setParameter(Object target, String parameterName, Object value)
throws XerialException {
ParameterSetter setter = setterIndex.get(getCanonicalParameterName(parameterName));
if (setter == null)
return;
setter.bind(target, value);
}
public boolean hasPropertySetter() {
return propertySetter != null;
}
RelationSetter getPropertySetter() {
return propertySetter;
}
public Object getProperty(Object target, String key) throws XerialException {
if (propertyGetter == null)
return null;
return propertyGetter.get(target, key);
}
/**
* Invoke property setter put(key, value) of the target object
*
* @param target
* @param key
* @param value
* @throws XerialException
*/
public void setProperty(Object target, Object key, Object value) throws XerialException {
if (propertySetter == null)
return;
propertySetter.bind(target, key, value);
}
public List getSetterList() {
return Collections.unmodifiableList(setterContainer);
}
public List getRelationSetterList() {
return Collections.unmodifiableList(relationSetterContainer);
}
public List getGetterContainer() {
return Collections.unmodifiableList(getterContainer);
}
public ParameterSetter getValueSetter() {
return valueSetter;
}
public boolean hasAttributes() {
return !getterContainer.isEmpty();
}
protected ObjectLens(Class< ? > targetType) {
this.targetType = targetType;
prepareBindRules(targetType);
}
public Class< ? > getTargetType() {
return targetType;
}
@Override
public String toString() {
return String.format("(%s, %s)", setterContainer, relationSetterContainer);
}
/**
* Scans the field and getter/setter definitions in the given class, then
* generates the mapping rules in the form of {@link ParameterSetter},
* {@link ParameterGetter} and {@link RelationSetter}.
*
* @param targetType
*/
private void prepareBindRules(Class< ? > targetType) {
// search for object parameters including superclass's ones
// scan public fields
for (Field eachField : targetType.getFields()) {
// ignore fields defined in the Object class
Class< ? > parentClassOfTheField = eachField.getDeclaringClass();
if (parentClassOfTheField == Object.class)
continue;
// looking for only public fields
int fieldModifier = eachField.getModifiers();
if (Modifier.isPublic(fieldModifier) && !Modifier.isTransient(fieldModifier)
&& !Modifier.isStatic(fieldModifier)) {
Class< ? > fieldType = eachField.getType();
String paramName = eachField.getName();
if (TypeInfo.isArray(fieldType)) {
// ignore the array field except the byte[] type
Class< ? > arrayElementType = TypeInfo.getArrayElementType(fieldType);
if (arrayElementType != null && byte.class == arrayElementType) {
// byte[] getter & setter
getterContainer.add(ParameterGetter.newFieldGetter(eachField, paramName));
setterContainer.add(ParameterSetter.newSetter(fieldType, paramName,
eachField));
}
continue;
}
else if (TypeInfo.isMap(fieldType)) {
Pair keyValueName = pickRelationName(eachField.getName());
if (keyValueName == null) {
// infer key, value names from the class type
Pair, Class< ? >> mapElementType = ReflectionUtil
.getGenericMapElementClasses(eachField);
Class< ? > keyType = mapElementType.getFirst();
Class< ? > valueType = mapElementType.getSecond();
keyValueName = new Pair(keyType.getSimpleName(), valueType
.getSimpleName());
if (isBasicTypeOrObject(keyType) && isBasicTypeOrObject(valueType)) {
// named map class. e.g. Property tag;
setterContainer.add(ParameterSetter.newSetter(fieldType, paramName,
eachField));
getterContainer.add(ParameterGetter.newPropertyFieldGetter(eachField,
paramName));
}
}
else if (keyValueName.getFirst().equals("")
&& keyValueName.getSecond().equals("")) {
// property (key, value) setter
propertySetter = RelationSetter.newMapSetter("key", "value", eachField);
getterContainer.add(ParameterGetter.newFieldGetter(eachField, paramName));
propertyGetter = ParameterGetter.newPropertyFieldGetter(eachField,
paramName);
continue;
}
relationSetterContainer.add(RelationSetter.newMapSetter(
keyValueName.getFirst(), keyValueName.getSecond(), eachField));
}
else if (TypeInfo.isCollection(fieldType)) {
Class< ? > elementType = ReflectionUtil.getRawClass(ReflectionUtil
.getGenericCollectionElementType(eachField));
setterContainer.add(ParameterSetter
.newSetter(elementType, paramName, eachField));
getterContainer.add(ParameterGetter.newFieldGetter(eachField, paramName));
}
else {
if (!paramName.equals("value"))
setterContainer.add(ParameterSetter.newSetter(fieldType, paramName,
eachField));
else
valueSetter = ParameterSetter.newSetter(fieldType, paramName, eachField);
getterContainer.add(ParameterGetter.newFieldGetter(eachField, paramName));
}
}
}
// -- scan methods
// Parameter names used in setter/putter/adder/getter in the class definition
// overrides getter/setter defined for field parameters
for (Method eachMethod : targetType.getMethods()) {
String methodName = eachMethod.getName();
if (methodName.startsWith("add") || methodName.startsWith("set")) {
Class< ? >[] argTypes = eachMethod.getParameterTypes();
switch (argTypes.length) {
case 1: {
String paramName = pickPropertyName(methodName);
Class< ? > parentOfTheSetter = eachMethod.getDeclaringClass();
if ((TypeInfo.isCollection(parentOfTheSetter) || TypeInfo
.isMap(parentOfTheSetter))
&& paramName.equals("all"))
break;
if (paramName.length() <= 0 && TypeInfo.isCollection(parentOfTheSetter)) {
Class< ? > elementType = BeanUtil.resolveActualTypeOfCollectionElement(
targetType, argTypes[0]);
setterContainer.add(ParameterSetter.newSetter(elementType, "entry",
eachMethod));
}
else
addNewSetter(setterContainer, paramName, eachMethod);
break;
}
case 2: {
if (TypeInfo.isCollection(eachMethod.getDeclaringClass())) {
break;
}
// relation adder
Pair relName = pickRelationName(pickPropertyName(methodName,
false));
if (relName == null) {
// infer relation node names
if (TypeInfo.isMap(eachMethod.getDeclaringClass())) {
Class< ? >[] mapElementType = BeanUtil.resolveActualTypeOfMapElement(
targetType, eachMethod.getParameterTypes());
// map.put(Key, Value)
setterContainer.add(ParameterSetter.newMapEntrySetter(
mapElementType[0], mapElementType[1]));
// (entry, key)
setterContainer.add(ParameterSetter.newKeySetter(mapElementType[0]));
// (entry, value)
setterContainer.add(ParameterSetter.newValueSetter(mapElementType[1]));
continue;
}
else {
relName = new Pair(
getCanonicalParameterName(argTypes[0].getSimpleName()),
getCanonicalParameterName(argTypes[1].getSimpleName()));
}
}
relationSetterContainer.add(RelationSetter.newRelationSetter(
relName.getFirst(), relName.getSecond(), eachMethod));
break;
}
}
continue;
}
else if (methodName.startsWith("put")) {
Class< ? >[] argTypes = eachMethod.getParameterTypes();
if (argTypes.length != 2)
continue;
if (TypeInfo.isCollection(eachMethod.getDeclaringClass())) {
continue;
}
// relation adder
Pair relName = pickRelationName(pickPropertyName(methodName, false));
if (relName == null) {
// infer relation node names
if (TypeInfo.isMap(eachMethod.getDeclaringClass())) {
Class< ? >[] mapElementType = BeanUtil.resolveActualTypeOfMapElement(
targetType, eachMethod.getParameterTypes());
if (isBasicTypeOrObject(mapElementType[0])
&& isBasicTypeOrObject(mapElementType[1])) {
propertySetter = RelationSetter.newRelationSetter("key", "value",
eachMethod);
}
// map.put(Key, Value)
setterContainer.add(ParameterSetter.newMapEntrySetter(mapElementType[0],
mapElementType[1]));
// (entry, key)
setterContainer.add(ParameterSetter.newKeySetter(mapElementType[0]));
// (entry, value)
setterContainer.add(ParameterSetter.newValueSetter(mapElementType[1]));
continue;
}
else {
propertySetter = RelationSetter.newRelationSetter("key", "value",
eachMethod);
continue;
}
}
relationSetterContainer.add(RelationSetter.newRelationSetter(relName.getFirst(),
relName.getSecond(), eachMethod));
continue;
}
else if (methodName.startsWith("append")) {
String paramName = pickPropertyName(methodName);
addNewSetter(setterContainer, paramName, eachMethod);
continue;
}
else if (methodName.startsWith("get")) {
int argLength = eachMethod.getParameterTypes().length;
String paramName = pickPropertyName(methodName);
if (argLength == 0) {
// ignore getters defined in the Object.class
if (Object.class == eachMethod.getDeclaringClass())
continue;
getterContainer.add(ParameterGetter.newGetter(eachMethod, paramName));
}
else if (argLength == 1 && TypeInfo.isMap(targetType)) {
propertyGetter = ParameterGetter.newMapEntryGetter(eachMethod);
}
}
}
// create indexes
for (ParameterSetter each : setterContainer)
setterIndex.put(each.getCanonicalParameterName(), each);
for (ParameterGetter each : getterContainer)
getterIndex.put(each.getCanonicalParamName(), each);
}
private static boolean isBasicTypeOrObject(Class< ? > type) {
return Object.class == type || TypeInfo.isBasicType(type);
}
private static void addNewSetter(List setterContainer, String paramPart,
Method m) {
Class< ? >[] argTypes = m.getParameterTypes();
if (argTypes.length != 1)
return;
assert (argTypes.length == 1);
String paramName = getCanonicalParameterName(paramPart);
if (paramName.length() <= 0) {
// infer parameter name from argument type
paramName = getCanonicalParameterName(argTypes[0].getSimpleName());
}
setterContainer.add(ParameterSetter.newSetter(argTypes[0], paramName, m));
return;
}
static private Pattern propertyNamePattern = Pattern
.compile("^(set|get|add|put|append)((\\S)(\\S*))?");
static private Pattern pairedNamePattern = Pattern.compile("([A-Za-z0-9]*)_([A-Za-z0-9]*)");
static String pickPropertyName(String methodName, boolean canonicalize) {
Matcher m = null;
m = propertyNamePattern.matcher(methodName);
if (!m.matches())
return null;
else {
if (m.group(2) != null)
return canonicalize ? getCanonicalParameterName(m.group(2)) : m.group(2);
else
return "";
}
}
static String pickPropertyName(String methodName) {
Matcher m = null;
m = propertyNamePattern.matcher(methodName);
if (!m.matches())
return null;
else {
if (m.group(2) != null)
return getCanonicalParameterName(m.group(2));
else
return "";
}
}
static Pair pickRelationName(String pairedName) {
Matcher m = null;
m = pairedNamePattern.matcher(pairedName);
if (!m.matches())
return null;
else
return new Pair(getCanonicalParameterName(m.group(1)),
getCanonicalParameterName(m.group(2)));
}
public static String toSilk(Object obj) {
StringWriter buf = new StringWriter();
SilkWriter writer = new SilkWriter(buf);
writer.toSilk(obj);
writer.flush();
return buf.toString();
}
public static String toJSON(Object obj) {
if (obj == null)
return "null";
Class< ? > c = obj.getClass();
if (TypeInfo.isBasicType(c)) {
if (c == String.class)
return JSONString.toJSONString(obj.toString());
else
return obj.toString();
}
StringWriter buf = new StringWriter();
JSONWriter json = new JSONWriter(buf);
toJSON(json, obj);
json.flush();
return buf.toString();
}
private static void toJSON(JSONWriter json, Object obj) {
Class< ? > c = obj.getClass();
if (TypeInfo.isBasicType(c)) {
json.addObject(obj);
return;
}
ObjectLens lens = getObjectLens(obj.getClass());
if (TypeInfo.isCollection(c)) {
Collection< ? > collection = (Collection< ? >) obj;
boolean hasAttributes = lens.hasAttributes();
boolean bracketIsOpen = false;
if (hasAttributes) {
json.startObject();
outputParemters(json, obj);
if (!collection.isEmpty()) {
json.startArray("entry");
bracketIsOpen = true;
}
}
else {
json.startArray();
bracketIsOpen = true;
}
for (Object elem : collection) {
toJSON(json, elem);
}
if (bracketIsOpen)
json.endArray();
if (hasAttributes)
json.endObject();
}
else if (TypeInfo.isMap(c)) {
Map< ? , ? > map = (Map< ? , ? >) obj;
boolean hasAttributes = lens.hasAttributes();
if (hasAttributes) {
json.startObject();
outputParemters(json, obj);
if (!map.isEmpty())
json.startArray("entry");
}
else if (!map.isEmpty())
json.startArray();
for (Entry< ? , ? > each : map.entrySet()) {
json.startObject();
json.putObject("key", each.getKey());
json.putObject("value", each.getValue());
json.endObject();
}
if (!map.isEmpty())
json.endArray();
if (hasAttributes)
json.endObject();
}
else {
if (!lens.getterContainer.isEmpty()) {
json.startObject();
outputParemters(json, obj);
json.endObject();
}
else {
// empty getter object. try toString()
json.startString();
json.append(obj.toString());
json.endString();
}
}
}
private static void outputParemters(JSONWriter json, Object obj) {
ObjectLens lens = getObjectLens(obj.getClass());
for (ParameterGetter getter : lens.getGetterContainer()) {
json.putObject(getter.getCanonicalParamName(), getter.get(obj));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy