/*
* 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.tuscany.sca.databinding.xml;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.namespace.QName;
import org.apache.tuscany.sca.common.xml.stax.reader.SimpleXmlNodeImpl;
import org.apache.tuscany.sca.common.xml.stax.reader.XmlNode;
import org.apache.tuscany.sca.common.xml.stax.reader.XmlTreeStreamReaderImpl;
import org.apache.tuscany.sca.databinding.SimpleTypeMapper;
import org.apache.tuscany.sca.databinding.impl.SimpleTypeMapperImpl;
import org.apache.tuscany.sca.interfacedef.util.TypeInfo;
/**
* @version $Rev$ $Date$
*/
public class BeanXMLStreamReaderImpl extends XmlTreeStreamReaderImpl {
private static final Comparator<Accessor> COMPARATOR = new Comparator<Accessor>() {
public int compare(Accessor o1, Accessor o2) {
return o1.getName().compareTo(o2.getName());
}
};
public static class BeanXmlNodeImpl extends SimpleXmlNodeImpl implements XmlNode {
private static final Object[] NULL = null;
private static final SimpleTypeMapper MAPPER = new SimpleTypeMapperImpl();
public BeanXmlNodeImpl(Object bean) {
super(getName(bean == null ? null : bean.getClass()), bean);
}
public BeanXmlNodeImpl(QName name, Object bean) {
super(name, bean);
}
private static boolean isSimpleType(Class<?> javaType) {
return MAPPER.getXMLType(javaType) != null;
}
private static String getStringValue(Object o) {
if (o == null) {
return null;
}
TypeInfo info = MAPPER.getXMLType(o.getClass());
if (info != null) {
return MAPPER.toXMLLiteral(info.getQName(), o, null);
} else {
return String.valueOf(o);
}
}
@Override
public Iterator<XmlNode> children() {
if (name == null) {
return null;
}
if (value == null) {
return super.children();
}
if (isSimpleType(value.getClass())) {
XmlNode textNode = new BeanXmlNodeImpl(null, value);
return Arrays.asList(textNode).iterator();
}
if (Map.class.isAssignableFrom(value.getClass())) {
List<XmlNode> entries = new ArrayList<XmlNode>();
QName entryName = new QName(name.getNamespaceURI(), "entry");
Map map = (Map)value;
if (map != null) {
for (Object e : map.entrySet()) {
Map.Entry entry = (Map.Entry)e;
entries.add(new BeanXmlNodeImpl(entryName, entry));
}
}
return entries.iterator();
}
try {
Map<String, Accessor> accessorMap = getAccessors(value);
List<Accessor> accessorList = new ArrayList<Accessor>(accessorMap.values());
Collections.sort(accessorList, COMPARATOR);
List<XmlNode> props = new ArrayList<XmlNode>();
for (Accessor accessor : accessorList) {
Class<?> pType = accessor.getType();
QName pName = new QName(name.getNamespaceURI(), accessor.getName());
Object pValue = accessor.getValue();
if (pType.isArray()) {
if (pValue != null) {
int i1 = Array.getLength(pValue);
for (int j = 0; j < i1; j++) {
Object o = Array.get(pValue, j);
props.add(new BeanXmlNodeImpl(pName, o));
}
} else {
// TODO: How to handle null?
}
} else if (Collection.class.isAssignableFrom(pType)) {
Collection objList = (Collection)pValue;
if (objList != null && objList.size() > 0) {
for (Iterator j = objList.iterator(); j.hasNext();) {
Object o = j.next();
props.add(new BeanXmlNodeImpl(pName, o));
}
} else {
// How to handle null
}
} else {
props.add(new BeanXmlNodeImpl(pName, pValue));
}
}
return props.iterator();
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
@Override
public QName getName() {
return name;
}
@Override
public String getValue() {
return getStringValue(value);
}
private static String getPackageName(Class<?> cls) {
String name = cls.getName();
int index = name.lastIndexOf('.');
return index == -1 ? "" : name.substring(0, index);
}
public static QName getName(Class<?> cls) {
if (cls == null) {
return null;
}
String packageName = getPackageName(cls);
if ("".equals(packageName)) {
return new QName("", cls.getSimpleName());
}
StringBuffer ns = new StringBuffer("http://");
String[] names = packageName.split("\\.");
for (int i = names.length - 1; i >= 0; i--) {
ns.append(names[i]);
if (i != 0) {
ns.append('.');
}
}
ns.append('/');
return new QName(ns.toString(), cls.getSimpleName());
}
}
public BeanXMLStreamReaderImpl(QName name, Object bean) {
super(getXmlNode(name, bean));
}
private static BeanXmlNodeImpl getXmlNode(QName name, Object bean) {
BeanXmlNodeImpl root = null;
if (name != null) {
root = new BeanXmlNodeImpl(name, bean);
} else {
root = new BeanXmlNodeImpl(bean);
}
return root;
}
public static interface Accessor {
String getName();
Class<?> getType();
Object getValue() throws Exception;
void setValue(Object value) throws Exception;
}
private static class FieldAccessor implements Accessor {
private Object target;
private Field field;
public FieldAccessor(Object target, Field field) {
super();
this.target = target;
this.field = field;
this.field.setAccessible(true);
}
public String getName() {
return field.getName();
}
public Object getValue() throws Exception {
return field.get(target);
}
public void setValue(Object value) throws Exception {
field.set(target, value);
}
public Class<?> getType() {
return field.getType();
}
}
private static class PropertyAccessor implements Accessor {
private Object target;
private PropertyDescriptor prop;
public PropertyAccessor(Object target, PropertyDescriptor prop) {
super();
this.target = target;
this.prop = prop;
}
public String getName() {
return prop.getName();
}
public Class<?> getType() {
return prop.getPropertyType();
}
public Object getValue() throws Exception {
Method getter = prop.getReadMethod();
if (getter != null) {
getter.setAccessible(true);
return getter.invoke(target);
}
throw new IllegalAccessException("The property cannot be read: " + getName());
}
public void setValue(Object value) throws Exception {
Method setter = prop.getWriteMethod();
if (setter != null) {
setter.setAccessible(true);
setter.invoke(target);
}
throw new IllegalAccessException("The property cannot be written: " + getName());
}
}
private static Map<String, Accessor> getAccessors(Object target) throws Exception {
if (target == null) {
return Collections.emptyMap();
}
Map<String, Accessor> map = new HashMap<String, Accessor>();
Class<?> type = target.getClass();
for (Field f : type.getFields()) {
map.put(f.getName(), new FieldAccessor(target, f));
}
BeanInfo info = Introspector.getBeanInfo(type, Object.class);
for (PropertyDescriptor p : info.getPropertyDescriptors()) {
// if (p.getReadMethod() != null && p.getWriteMethod() != null) {
map.put(p.getName(), new PropertyAccessor(target, p));
// }
}
return map;
}
}