+++ /dev/null
-/*--------------------------------------------------------------------------\r
- * Copyright 2007 Taro L. Saito\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- *--------------------------------------------------------------------------*/\r
-//--------------------------------------\r
-// XerialJ\r
-//\r
-// BeanBindingProcess.java\r
-// Since: Dec 18, 2007 5:09:59 PM\r
-//\r
-// $URL: http://www.xerial.org/svn/project/XerialJ/trunk/xerial-core/src/main/java/org/xerial/util/bean/impl/BeanBindingProcess.java $\r
-// $Author: leo $\r
-//--------------------------------------\r
-package org.xerial.util.bean.impl;\r
-\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.Map;\r
-import java.util.TreeMap;\r
-\r
-import org.xerial.core.XerialErrorCode;\r
-import org.xerial.core.XerialException;\r
-import org.xerial.util.ArrayDeque;\r
-import org.xerial.util.Deque;\r
-import org.xerial.util.Pair;\r
-import org.xerial.util.bean.BeanBinder;\r
-import org.xerial.util.bean.BeanBinderSet;\r
-import org.xerial.util.bean.BeanUpdator;\r
-import org.xerial.util.bean.BeanUpdatorType;\r
-import org.xerial.util.bean.BeanUtil;\r
-import org.xerial.util.bean.TypeConverter;\r
-import org.xerial.util.bean.TypeInfo;\r
-import org.xerial.util.log.Logger;\r
-import org.xerial.util.tree.TreeVisitor;\r
-import org.xerial.util.tree.TreeWalker;\r
-\r
-/**\r
- * \r
- * Tree-structured data to object binding process implementation\r
- * \r
- * @author leo\r
- * \r
- */\r
-public class BeanBindingProcess implements TreeVisitor\r
-{\r
- private static Logger _logger = Logger.getLogger(BeanBindingProcess.class);\r
-\r
- // private final ArrayList<Object> beanStack = new ArrayList<Object>();\r
-\r
- /*\r
- * <pre>\r
- * class A\r
- * {\r
- * public void setB(C value);\r
- * public void putD(E key, F value); \r
- * public void addG(H value);\r
- * }\r
- *\r
- * XML\r
- * \r
- * <A>\r
- * <B>c-value</B>\r
- * <D><key>k1</key><value>v1</value></D>\r
- * <D><key>k2</key><value>v2</value></D>\r
- * <G>1</G>\r
- * <G>2</G>\r
- * </A>\r
- * \r
- * JSON\r
- * { "A" : { "B":"c-value", "D":[{"key":"k1", "value":"v1"}, {"key":"k2", "value":"v2"}], G:[1, 2] } }\r
- *\r
- *\r
- * level, event\r
- * [0] visit(A)\r
- * [1] visit(B)\r
- * [1] leave(B) \r
- * [1] visit(D) \r
- * [2] visit(key) "k1"\r
- * [2] leave(key) \r
- * \r
- * [2] visit(value) "v1"\r
- * [2] leave(value) \r
- * [1] leave(D) A.putD(KeyValue("k1", "v1"))\r
- * \r
- * [1] visit(D)\r
- * [2] visit(key) "k2"\r
- * [2] leave(key) \r
- * [2] visit(value) "v2"\r
- * [2] leave(value)\r
- * [1] leave(D) A.putD(KeyValue("k2", "v2"))\r
- * \r
- * [1] visit(G) 1\r
- * [1] leave(G) \r
- * [1] visit(G) 2\r
- * [1] leave(G)\r
- * [0] leave(A)\r
- * \r
- * \r
- * bean stack\r
- * [0] new A() (given by constructor)\r
- * [1] new B()\r
- * [1] D (new KeyValuePair())\r
- * [2] key <- "k1"\r
- * [2] value <- "v1" \r
- * [1] D (new KeyValuePair())\r
- * [2] key <- "k2"\r
- * [2] value <- "v2"\r
- * [1] new G() <- 1 \r
- * [1] new G() <- 2\r
- * \r
- * </pre>\r
- * \r
- */\r
-\r
- /*\r
- * class T extends TreeMap<Integer, String> {}\r
- * \r
- * \r
- * <T>\r
- * <elem><key>k1</key><value>v1</value></elem>\r
- * <elem><key>k2</key><value>v2</value></elem>\r
- * </T>\r
- * \r
- * {"elem":[{"key":"k1", "value":v2}, {"key":"k2", "value":"v2"}]}\r
- * \r
- * \r
- */\r
-\r
- private final TreeMap<Integer, Object> contextBeanOfEachLevel = new TreeMap<Integer, Object>();\r
- private final TreeMap<Integer, Map< ? , ? >> mapAssociatedWithBean = new TreeMap<Integer, Map< ? , ? >>();\r
- private final Deque<StringBuilder> textStack = new ArrayDeque<StringBuilder>();\r
- private final static StringBuilder EMPTY_TEXT_BUILDER = new StringBuilder(0);\r
-\r
- private int currentLevel = 0;\r
- private BindRuleGenerator bindRuleGenerator = new BindRuleGeneratorImpl();\r
-\r
- class BindRuleGeneratorImpl implements BindRuleGenerator\r
- {\r
- public <T> BeanBinderSet getBeanBinderSet(Class<T> beanClass) throws XerialException {\r
- return BeanUtil.getBeanLoadRule(beanClass);\r
- }\r
- }\r
-\r
- public BeanBindingProcess(Class< ? > beanClass) throws XerialException {\r
- this(BeanUtil.createInstance(beanClass));\r
- }\r
-\r
- public BeanBindingProcess(Object bean) {\r
- setContextBean(0, bean);\r
- }\r
-\r
- public static BeanBindingProcess newBinderWithRootContext(Object rootNode) {\r
- BeanBindingProcess proc = new BeanBindingProcess(rootNode);\r
- proc.currentLevel = 1;\r
- return proc;\r
- }\r
-\r
- /**\r
- * \r
- * \r
- * @param bean\r
- * @param bindRuleGenerator\r
- * set the context bean corresponding to the root node\r
- */\r
- public BeanBindingProcess(Object bean, BindRuleGenerator bindRuleGenerator) {\r
- setContextBean(0, bean);\r
- this.bindRuleGenerator = bindRuleGenerator;\r
- }\r
-\r
- public Object getResultBean() {\r
- return getContextBean(0);\r
- }\r
-\r
- public Object getContextBean(int level) {\r
- if (level < 0 || level >= contextBeanOfEachLevel.size())\r
- return null;\r
- else\r
- return contextBeanOfEachLevel.get(level);\r
- }\r
-\r
- private void setContextBean(int level, Object bean) {\r
- contextBeanOfEachLevel.put(level, bean);\r
- }\r
-\r
- private void removeContextBean(int level) {\r
- contextBeanOfEachLevel.remove(level);\r
- }\r
-\r
- public void finish(TreeWalker walker) throws XerialException {\r
- // do nothing\r
- }\r
-\r
- public void init(TreeWalker walker) throws XerialException {\r
- // do nothing\r
- }\r
-\r
- protected <T> BeanBinderSet getBindRuleSet(Class<T> beanClass) throws XerialException {\r
- return bindRuleGenerator.getBeanBinderSet(beanClass);\r
- }\r
-\r
- public void visitNode(String nodeName, String nodeValue, TreeWalker walker) throws XerialException {\r
- textStack.addLast(EMPTY_TEXT_BUILDER);\r
-\r
- int nodeLevel = currentLevel++;\r
- //_logger.trace("visit[" + nodeLevel + "] " + nodeName);\r
-\r
- Object parentBean = getContextBean(nodeLevel - 1);\r
- if (parentBean == null)\r
- return;\r
-\r
- // prepare the context bean for this depth\r
- Object nodeValueBean = getContextBean(nodeLevel);\r
- if (nodeValueBean == null) {\r
- assert (currentLevel > 0); // the bean cannot be null when level is 0\r
-\r
- BeanBinderSet bindRuleSet = getBindRuleSet(parentBean.getClass());\r
- BeanUpdator updator = getUpdator(bindRuleSet, nodeName);\r
- if (updator != null) {\r
- int targetArgIndex = 0;\r
- Method updateMethod = updator.getMethod();\r
- // We have to instantiate a bean class of the node name\r
- switch (updator.getType()) {\r
- case SETTER:\r
- case COLLECTION_ADDER:\r
- case APPENDER:\r
- Class< ? > elementType = updator.getInputType();\r
- if (parentBean instanceof KeyValuePair) {\r
- // for map elment\r
- KeyValuePair keyValuePair = KeyValuePair.class.cast(parentBean);\r
- updateMethod = keyValuePair.putter();\r
- if (nodeName.equalsIgnoreCase("key")) {\r
- elementType = keyValuePair.keyType();\r
- }\r
- else if (nodeName.equalsIgnoreCase("value")) {\r
- elementType = keyValuePair.valueType();\r
- targetArgIndex = 1;\r
- }\r
- }\r
-\r
- if (TypeInfo.isBasicType(elementType)) {\r
- // this bean can be generated directly from the element text value, so\r
- // there is no need to instantiate the object here.\r
- bindValue(parentBean, nodeName, nodeValue, nodeLevel);\r
- break;\r
- }\r
- else if (TypeInfo.isMap(elementType)) {\r
- // when input of setter/ or K, V of putSomthing(K, V) is map\r
- Pair<Class< ? >, Class< ? >> keyValueClassPair = BeanUtil.getGenericMapTypesOfMethodArgument(\r
- updateMethod, targetArgIndex);\r
-\r
- BeanBinderSet mapBindRuleSet = getBindRuleSet(elementType);\r
- MapPutter putter = mapBindRuleSet.getStandardMapPutter();\r
- if (keyValueClassPair != null) {\r
- setContextBean(nodeLevel, new KeyValuePair(putter, keyValueClassPair.getFirst(),\r
- keyValueClassPair.getSecond()));\r
- }\r
- else {\r
- // no need to put this sub tree since no putter is found\r
- //walker.skipDescendants();\r
- setContextBean(nodeLevel, new KeyValuePair(putter));\r
-\r
- }\r
- }\r
- else if (elementType != Object.class) {\r
- Object newBean = BeanUtil.createInstance(elementType);\r
- setContextBean(nodeLevel, newBean);\r
- }\r
- else\r
- bindValue(parentBean, nodeName, nodeValue, nodeLevel);\r
-\r
- break;\r
- case MAP_PUTTER:\r
- MapPutter mapPutter = MapPutter.class.cast(updator);\r
- // prepare the key and value pair instance\r
- KeyValuePair keyVal = new KeyValuePair(mapPutter);\r
- if (nodeValue != null)\r
- keyVal.setValue(nodeValue);\r
- setContextBean(nodeLevel, keyVal);\r
- break;\r
- default:\r
- throw new XerialException(XerialErrorCode.UnknownBeanUpdator);\r
- }\r
-\r
- }\r
- else {\r
- // We can safely skip the descendants of this node, since there is\r
- // no descendant nodes to bind\r
- walker.skipDescendants();\r
- }\r
-\r
- }\r
-\r
- }\r
-\r
- private void bindValue(Object parentBean, String nodeName, String nodeValue, int nodeLevel) throws XerialException {\r
- if (parentBean == null)\r
- return;\r
-\r
- // bind immediate text value\r
- BeanBinderSet bindRuleSet = getBindRuleSet(parentBean.getClass());\r
- BeanUpdator updator = getUpdator(bindRuleSet, nodeName);\r
- if (updator != null) {\r
- Object valueBean = getContextBean(nodeLevel);\r
- if (valueBean == null) {\r
- if (nodeValue != null && nodeValue.length() > 0) {\r
- valueBean = nodeValue;\r
- }\r
- }\r
-\r
- // no value to bind\r
- if (valueBean == null)\r
- return;\r
-\r
- switch (updator.getType()) {\r
- case SETTER:\r
- case COLLECTION_ADDER:\r
- case APPENDER:\r
- try {\r
- if (parentBean instanceof KeyValuePair) {\r
- KeyValuePair keyValuePair = KeyValuePair.class.cast(parentBean);\r
- if (nodeName.equalsIgnoreCase("key"))\r
- bindValue(parentBean, updator, keyValuePair.keyType(), valueBean);\r
- else if (nodeName.equalsIgnoreCase("value"))\r
- bindValue(parentBean, updator, keyValuePair.valueType(), valueBean);\r
- }\r
- else {\r
- bindValue(parentBean, updator, valueBean);\r
- }\r
-\r
- }\r
- catch (XerialException e) {\r
- _logger.error(e);\r
- }\r
- break;\r
- case MAP_PUTTER:\r
- // at key, value data depth\r
- try {\r
- KeyValuePair keyValuePair = KeyValuePair.class.cast(valueBean);\r
- if (keyValuePair.getValue() == null)\r
- keyValuePair.setValue(nodeValue);\r
- if (keyValuePair.hasKeyAndValue()) {\r
- bindMapElement(parentBean, MapPutter.class.cast(updator), keyValuePair);\r
- }\r
- mapAssociatedWithBean.remove(parentBean.hashCode());\r
- }\r
- catch (ClassCastException e) {\r
- _logger.error(e);\r
- // skip this element\r
- }\r
- break;\r
- default:\r
- throw new XerialException(XerialErrorCode.UnknownBeanUpdator);\r
- }\r
- }\r
-\r
- }\r
-\r
- public void text(String nodeName, String nodeValue, TreeWalker walker) throws XerialException {\r
- Object parentBean = getContextBean(currentLevel - 2);\r
- if (parentBean != null) {\r
- BeanBinderSet bindRuleSet = getBindRuleSet(parentBean.getClass());\r
- BeanUpdator updator = getUpdator(bindRuleSet, nodeName);\r
- if (updator != null && updator.getType() == BeanUpdatorType.APPENDER) {\r
- // use appender\r
- bindValue(parentBean, updator, nodeValue);\r
- }\r
- else {\r
- // use internal string buffer\r
- StringBuilder textBuffer = textStack.peekLast();\r
- if (textBuffer == EMPTY_TEXT_BUILDER) {\r
- textStack.removeLast();\r
- textBuffer = new StringBuilder();\r
- textStack.addLast(textBuffer);\r
- }\r
- textBuffer.append(nodeValue);\r
- }\r
-\r
- }\r
-\r
- }\r
-\r
- public void leaveNode(String nodeName, TreeWalker walker) throws XerialException {\r
- int nodeLevel = --currentLevel;\r
- //_logger.trace("leave[" + nodeLevel + "] " + nodeName + " value = " + nodeValue);\r
-\r
- StringBuilder textBuffer = textStack.removeLast();\r
- Object parentBean = getContextBean(nodeLevel - 1);\r
- if (parentBean == null)\r
- return;\r
-\r
- // if (textBuffer != EMPTY_TEXT_BUILDER)\r
- bindValue(parentBean, nodeName, textBuffer.toString(), nodeLevel);\r
-\r
- // clear the bean stack\r
- removeContextBean(nodeLevel);\r
-\r
- }\r
-\r
- public static BeanUpdator getUpdator(BeanBinderSet bindRuleSet, String ruleName) throws XerialException {\r
- BeanBinder binder = bindRuleSet.findRule(ruleName);\r
- if (binder == null)\r
- return null;\r
- else {\r
- if (!BeanUpdator.class.isAssignableFrom(binder.getClass())) {\r
- _logger.warn(binder.getClass().getName() + " cannot be used to bind data");\r
- return null;\r
- }\r
- else\r
- return (BeanUpdator) binder;\r
- }\r
- }\r
-\r
- private void bindMapElement(Object bean, MapPutter mapPutter, KeyValuePair keyValuePair) throws XerialException {\r
- try {\r
- mapPutter.getMethod().invoke(bean, convertType(mapPutter.getKeyType(), keyValuePair.getKey()),\r
- convertType(mapPutter.getValueType(), keyValuePair.getValue()));\r
- }\r
- catch (IllegalArgumentException e) {\r
- throw new XerialException(XerialErrorCode.IllegalArgument, e);\r
- }\r
- catch (IllegalAccessException e) {\r
- throw new XerialException(XerialErrorCode.IllegalAccess, e);\r
- }\r
- catch (InvocationTargetException e) {\r
- throw new XerialException(XerialErrorCode.InvocationTargetException, e);\r
- }\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- private void bindValue(Object bean, BeanUpdator updator, Class targetType, Object value) throws XerialException {\r
- try {\r
- if (value.getClass() == KeyValuePair.class && TypeInfo.isMap(targetType)) {\r
- Map map = null;\r
- if (mapAssociatedWithBean.containsKey(bean.hashCode())) {\r
- map = mapAssociatedWithBean.get(bean.hashCode());\r
- }\r
- else {\r
- map = Map.class.cast(BeanUtil.createInstance(targetType));\r
- mapAssociatedWithBean.put(bean.hashCode(), map);\r
- }\r
- KeyValuePair keyValuePair = KeyValuePair.class.cast(value);\r
- map.put(keyValuePair.getKey(), keyValuePair.getValue());\r
-\r
- updator.getMethod().invoke(bean, map);\r
- }\r
- else\r
- updator.getMethod().invoke(bean, convertType(targetType, value));\r
- }\r
- catch (IllegalArgumentException e) {\r
- throw new XerialException(XerialErrorCode.IllegalArgument, e);\r
- }\r
- catch (IllegalAccessException e) {\r
- throw new XerialException(XerialErrorCode.IllegalAccess, e);\r
- }\r
- catch (InvocationTargetException e) {\r
- Throwable cause = e.getCause();\r
- if (cause == null)\r
- throw new XerialException(XerialErrorCode.InvocationTargetException, String.format(\r
- "value=%s, updator=%s", value, updator.getMethod().getName()));\r
- else\r
- throw new XerialException(XerialErrorCode.InvocationTargetException, cause);\r
- }\r
-\r
- }\r
-\r
- private void bindValue(Object bean, BeanUpdator updator, Object value) throws XerialException {\r
- bindValue(bean, updator, updator.getInputType(), value);\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- public static Object convertType(Class targetType, Object value) throws XerialException {\r
- return TypeConverter.convertType(targetType, value);\r
- }\r
-\r
-}\r