/**
* 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.soybeanMilk.web.bean;
import java.lang.reflect.Array;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.soybeanMilk.SoybeanMilkUtils;
import org.soybeanMilk.core.bean.ConvertException;
import org.soybeanMilk.core.bean.GenericConvertException;
import org.soybeanMilk.core.bean.DefaultGenericConverter;
import org.soybeanMilk.core.bean.PropertyInfo;
import org.soybeanMilk.web.os.WebObjectSource.ParamFilterAwareMap;
/**
* WEB{@link Map Map<String, Object>}JavaBeanJavaBeanJavaBeanListSet<br>
*
* <pre>
* "id" -> "1" ["1"]
* "name" -> "jack" ["jack"]
* "listChildren.id" -> ["11", "12"]
* "listChildren.name" -> ["tom", "mary"]
* "setChildren.id" -> ["11", "12"]
* "setChildren.name" -> ["tom", "mary"]
* "arrayChildren.id" -> ["11", "12"]
* "arrayChildren.name" -> ["tom", "mary"]
* "ignored" -> "this value will be ignored"
* </pre>
*
* <pre>
* class User{
* private Integer id;
* private String name;
* private List<User> listChildren;
* private Set<User> setChildren;
* private User[] arrayChildren;
* ...
* }
*
*
* </pre>
* @author earthAngry@gmail.com
* @date 2010-10-8
*/
public class WebGenericConverter extends DefaultGenericConverter
{
private static Log log = LogFactory.getLog(WebGenericConverter.class);
public WebGenericConverter()
{
super();
}
@SuppressWarnings("unchecked")
//@Override
protected Object convertWhenNoSupportConverter(Object sourceObj, Type targetType)
{
//1
if(sourceObj.getClass().isArray()
&& Array.getLength(sourceObj)==1
&& !SoybeanMilkUtils.isClassTypeArray(targetType))
{
if(log.isDebugEnabled())
log.debug("the src '"+getStringDesc(sourceObj)+"' is an array with length 1, it's first element will be used for converting");
sourceObj=Array.get(sourceObj, 0);
return convert(sourceObj, targetType);
}
else if(SoybeanMilkUtils.isInstanceOf(sourceObj, Map.class))
{
return convertMap(FilterAwareMap.wrap((Map<String, ?>)sourceObj), targetType);
}
else
{
return super.convertWhenNoSupportConverter(sourceObj, targetType);
}
}
/**
* <code>targetType</code>
* @param sourceMap <code>targetType</code>
* @param targetType
* @return
*/
protected Object convertMap(FilterAwareMap<String, ?> sourceMap, Type targetType)
{
if(log.isDebugEnabled())
log.debug("start converting 'Map<String, Object>' object to type '"+targetType+"'");
Object result = null;
//
boolean isParamMap= (sourceMap instanceof ParamFilterAwareMap<?, ?>);
if(sourceMap==null || sourceMap.isEmpty())//null
{
result=convert(null, targetType);
}
else if(sourceMap.isExplicitKey())
{
if(isParamMap)
{
try
{
result=convert(sourceMap.get(FilterAwareMap.EXPLICIT_KEY), targetType);
}
catch(ConvertException e)
{
handleParamConvertException((ParamFilterAwareMap<String, ?>)sourceMap, FilterAwareMap.EXPLICIT_KEY, e);
}
}
else
result=convert(sourceMap.get(FilterAwareMap.EXPLICIT_KEY), targetType);
}
else
{
Class<?>[] actualTypes=SoybeanMilkUtils.getActualClassTypeInfo(targetType);
if(SoybeanMilkUtils.isArray(actualTypes[0]))//array
{
result=convertMapToJavaBeanArray(sourceMap, actualTypes[0].getComponentType());
}
else if(SoybeanMilkUtils.isAncestorClass(List.class, actualTypes[0]))//List
{
if(actualTypes.length != 2)
throw new GenericConvertException("'"+targetType+"' is invalid, only generic List converting is supported");
result=convertArrayToList(convertMapToJavaBeanArray(sourceMap, actualTypes[1]), actualTypes[0]);
}
else if(SoybeanMilkUtils.isAncestorClass(Set.class, actualTypes[0]))//Set
{
if(actualTypes.length != 2)
throw new GenericConvertException("'"+targetType+"' is invalid, only generic Set converting is supported");
result=convertArrayToSet(convertMapToJavaBeanArray(sourceMap, actualTypes[1]), actualTypes[0]);
}
else if(SoybeanMilkUtils.isAncestorClass(Collection.class, actualTypes[0]))//
{
throw new GenericConvertException("converting 'Map<String,Object>' to '"+targetType+"' is not supported");
}
else//JavaBean
{
PropertyInfo beanInfo=PropertyInfo.getPropertyInfo(actualTypes[0]);
if(!beanInfo.hasSubPropertyInfo())
throw new GenericConvertException("the target javaBean Class '"+actualTypes[0]+"' is not valid, it has no javaBean property");
Map<String, Boolean> collectionPropertyProcessed=new HashMap<String, Boolean>();
Set<String> keys=sourceMap.keySet();
for(String propertyKey : keys)
{
String[] propExpressionArray=splitPropertyExpression(propertyKey);
//
if(beanInfo.getSubPropertyInfo(propExpressionArray[0])==null)
{
//
if(sourceMap.isFiltered())
throw new GenericConvertException("can not find property '"+propExpressionArray[0]+"' in class '"+beanInfo.getType().getName()+"'");
}
else
{
//
if(result == null)
result = instance(beanInfo.getType(), -1);
String collectionPropertyExp=detectCollectionProperty(beanInfo, propExpressionArray);
if(collectionPropertyExp == null)
{
if(isParamMap)
{
try
{
setProperty(result, beanInfo, propExpressionArray, 0, sourceMap.get(propertyKey));
}
catch(ConvertException e)
{
handleParamConvertException((ParamFilterAwareMap<String, ?>)sourceMap, propertyKey, e);
}
}
else
setProperty(result, beanInfo, propExpressionArray, 0, sourceMap.get(propertyKey));
}
//
else
{
if(collectionPropertyProcessed.get(collectionPropertyExp) == null)
{
if(isParamMap)
{
FilterAwareMap<String, ?> collectionPropertyValueMap=new ParamFilterAwareMap<String, Object>(
sourceMap, collectionPropertyExp+ACCESSOR, false);
try
{
setProperty(result, collectionPropertyExp, collectionPropertyValueMap);
}
catch(ConvertException e)
{
handleParamConvertException((ParamFilterAwareMap<String, ?>)sourceMap, collectionPropertyExp, e);
}
}
else
{
FilterAwareMap<String, ?> collectionPropertyValueMap=new FilterAwareMap<String, Object>(
sourceMap, collectionPropertyExp+ACCESSOR, false);
setProperty(result, collectionPropertyExp, collectionPropertyValueMap);
}
//
collectionPropertyProcessed.put(collectionPropertyExp, true);
}
}
}
}
}
}
return result;
}
/**
* <code>propExpressionArray</code><br>
* <code>null</code>
* @param beanInfo
* @param propExpressionArray
* @return
* @date 2011-1-4
*/
protected String detectCollectionProperty(PropertyInfo beanInfo, String[] propExpressionArray)
{
String re=null;
int i=0;
PropertyInfo tmpPropInfo=null;
for(;i<propExpressionArray.length;i++)
{
tmpPropInfo=beanInfo.getSubPropertyInfo(propExpressionArray[i]);
if(tmpPropInfo == null)
throw new GenericConvertException("can not find property '"+propExpressionArray[i]+"' in class '"+beanInfo.getType().getName()+"'");
else
beanInfo=tmpPropInfo;
if(isArrayOrCollection(beanInfo.getType()))
break;
}
if(i < propExpressionArray.length-1)
re=assemblePropertyExpression(propExpressionArray, 0, i+1);
return re;
}
/**
* JavaBean<code>valueMap</code><code>null</code><code>javaBeanClass</code>
* <br>
* <code>elementClass</code>
* @param sourceMap
* @param javaBeanClass
* @return <code>javaBeanClass</code><code>valueMap</code>
* @date 2010-12-31
*/
protected Object[] convertMapToJavaBeanArray(FilterAwareMap<String, ?> sourceMap, Class<?> javaBeanClass)
{
if(sourceMap==null || sourceMap.size()==0)
return null;
Object[] re=null;
int len=-1;
//
boolean isParamMap= (sourceMap instanceof ParamFilterAwareMap<?, ?>);
PropertyInfo beanInfo=PropertyInfo.getPropertyInfo(javaBeanClass);
if(!beanInfo.hasSubPropertyInfo())
throw new GenericConvertException("the target javaBean Class '"+javaBeanClass+"' is not valid, it has no javaBean property");
Set<String> keys=sourceMap.keySet();
for(String propertyKey : keys)
{
Object value=sourceMap.get(propertyKey);
if(value == null)
continue;
if(!value.getClass().isArray())
throw new GenericConvertException("the element in the source map must be array");
int l=Array.getLength(value);
if(len == -1)
len=l;
else
if(l != len)
throw new GenericConvertException("the array element in the source map must be the same length");
String[] propertyExpArray=splitPropertyExpression(propertyKey);
//
if(beanInfo.getSubPropertyInfo(propertyExpArray[0])==null)
{
//
if(sourceMap.isFiltered())
throw new GenericConvertException("can not find property '"+propertyExpArray[0]+"' in class '"+beanInfo.getType().getName()+"'");
}
else
{
//
if(re == null)
{
re=(Object[])instance(javaBeanClass, len);
for(int i=0;i<len;i++)
re[i]=instance(javaBeanClass, -1);
}
for(int i=0;i<len;i++)
{
if(isParamMap)
{
try
{
setProperty(re[i], beanInfo, propertyExpArray, 0, Array.get(value, i));
}
catch(ConvertException e)
{
handleParamConvertException((ParamFilterAwareMap<String, ?>)sourceMap, propertyKey, e);
}
}
else
setProperty(re[i], beanInfo, propertyExpArray, 0, Array.get(value, i));
}
}
}
return re;
}
/**
*
* @param paramMap
* @param key
* @param e
* @date 2011-4-12
*/
protected void handleParamConvertException(ParamFilterAwareMap<String, ?> paramMap, String key, ConvertException e)
{
if(e instanceof ParamConvertException)
throw e;
else
throw new ParamConvertException(paramMap.getKeyInRoot(key), e.getSourceObject(), e.getTargetType(), e.getCause());
}
/**
*
* @param clazz
* @return
* @date 2011-1-3
*/
protected boolean isArrayOrCollection(Class<?> clazz)
{
if(clazz.isArray())
return true;
else if(SoybeanMilkUtils.isAncestorClass(Collection.class, clazz))
return true;
else
return false;
}
}
|