http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java new file mode 100644 index 0000000..988e463 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/AnnotationXWorkConverter.java @@ -0,0 +1,91 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.conversion.impl; + +/** + * <!-- START SNIPPET: javadoc --> + * <p/> + * Type conversion is great for situations where you need to turn a String in to a more complex object. Because the web + * is type-agnostic (everything is a string in HTTP), XWork's type conversion features are very useful. For instance, + * if you were prompting a user to enter in coordinates in the form of a string (such as "3, 22"), you could have + * XWork do the conversion both from String to Point and from Point to String. + * <p/> + * <p/> Using this "point" example, if your action (or another compound object in which you are setting properties on) + * has a corresponding ClassName-conversion.properties file, XWork will use the configured type converters for + * conversion to and from strings. So turning "3, 22" in to new Point(3, 22) is done by merely adding the following + * entry to <b>ClassName-conversion.properties</b> (Note that the PointConverter should impl the TypeConverter + * interface): + * <p/> + * <p/><b>point = com.acme.PointConverter</b> + * <p/> + * <p/> Your type converter should be sure to check what class type it is being requested to convert. Because it is used + * for both to and from strings, you will need to split the conversion method in to two parts: one that turns Strings in + * to Points, and one that turns Points in to Strings. + * <p/> + * <p/> After this is done, you can now reference your point (using <ww:property value="post"/> in JSP or ${point} + * in FreeMarker) and it will be printed as "3, 22" again. As such, if you submit this back to an action, it will be + * converted back to a Point once again. + * <p/> + * <p/> In some situations you may wish to apply a type converter globally. This can be done by editing the file + * <b>xwork-conversion.properties</b> in the root of your class path (typically WEB-INF/classes) and providing a + * property in the form of the class name of the object you wish to convert on the left hand side and the class name of + * the type converter on the right hand side. For example, providing a type converter for all Point objects would mean + * adding the following entry: + * <p/> + * <p/><b>com.acme.Point = com.acme.PointConverter</b> + * <p/> + * <!-- END SNIPPET: javadoc --> + * <p/> + * <p/> + * <p/> + * <!-- START SNIPPET: i18n-note --> + * <p/> + * Type conversion should not be used as a substitute for i18n. It is not recommended to use this feature to print out + * properly formatted dates. Rather, you should use the i18n features of XWork (and consult the JavaDocs for JDK's + * MessageFormat object) to see how a properly formatted date should be displayed. + * <p/> + * <!-- END SNIPPET: i18n-note --> + * <p/> + * <p/> + * <p/> + * <!-- START SNIPPET: error-reporting --> + * <p/> + * Any error that occurs during type conversion may or may not wish to be reported. For example, reporting that the + * input "abc" could not be converted to a number might be important. On the other hand, reporting that an empty string, + * "", cannot be converted to a number might not be important - especially in a web environment where it is hard to + * distinguish between a user not entering a value vs. entering a blank value. + * <p/> + * <p/> By default, all conversion errors are reported using the generic i18n key <b>xwork.default.invalid.fieldvalue</b>, + * which you can override (the default text is <i>Invalid field value for field "xxx"</i>, where xxx is the field name) + * in your global i18n resource bundle. + * <p/> + * <p/>However, sometimes you may wish to override this message on a per-field basis. You can do this by adding an i18n + * key associated with just your action (Action.properties) using the pattern <b>invalid.fieldvalue.xxx</b>, where xxx + * is the field name. + * <p/> + * <p/>It is important to know that none of these errors are actually reported directly. Rather, they are added to a map + * called <i>conversionErrors</i> in the ActionContext. There are several ways this map can then be accessed and the + * errors can be reported accordingly. + * <p/> + * <!-- END SNIPPET: error-reporting --> + * + * @author <a href="mailto:[email protected]">Pat Lightbody</a> + * @author Rainer Hermanns + * @see com.opensymphony.xwork2.conversion.impl.XWorkConverter + * @deprecated Since XWork 2.0.4, the implementation of XWorkConverter handles the processing of annotations. + */ +@Deprecated public class AnnotationXWorkConverter extends XWorkConverter { +}
http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/ArrayConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/ArrayConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/ArrayConverter.java new file mode 100644 index 0000000..09addae --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/ArrayConverter.java @@ -0,0 +1,36 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.TypeConverter; + +import java.lang.reflect.Array; +import java.lang.reflect.Member; +import java.util.Map; + +public class ArrayConverter extends DefaultTypeConverter { + + @Override + public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { + Object result = null; + Class componentType = toType.getComponentType(); + + if (componentType != null) { + TypeConverter converter = getTypeConverter(context); + + if (value.getClass().isArray()) { + int length = Array.getLength(value); + result = Array.newInstance(componentType, length); + + for (int i = 0; i < length; i++) { + Object valueItem = Array.get(value, i); + Array.set(result, i, converter.convertValue(context, target, member, propertyName, valueItem, componentType)); + } + } else { + result = Array.newInstance(componentType, 1); + Array.set(result, 0, converter.convertValue(context, target, member, propertyName, value, componentType)); + } + } + + return result; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/CollectionConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/CollectionConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/CollectionConverter.java new file mode 100644 index 0000000..dfe4030 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/CollectionConverter.java @@ -0,0 +1,93 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.XWorkList; + +import java.lang.reflect.Member; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; + +public class CollectionConverter extends DefaultTypeConverter { + + private ObjectTypeDeterminer objectTypeDeterminer; + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer determiner) { + this.objectTypeDeterminer = determiner; + } + + public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { + Collection result; + Class memberType = String.class; + + if (target != null) { + memberType = objectTypeDeterminer.getElementClass(target.getClass(), propertyName, null); + + if (memberType == null) { + memberType = String.class; + } + } + + if (toType.isAssignableFrom(value.getClass())) { + // no need to do anything + result = (Collection) value; + } else if (value.getClass().isArray()) { + Object[] objArray = (Object[]) value; + TypeConverter converter = getTypeConverter(context); + result = createCollection(toType, memberType, objArray.length); + + for (Object anObjArray : objArray) { + Object convertedValue = converter.convertValue(context, target, member, propertyName, anObjArray, memberType); + if (!TypeConverter.NO_CONVERSION_POSSIBLE.equals(convertedValue)) { + result.add(convertedValue); + } + } + } else if (Collection.class.isAssignableFrom(value.getClass())) { + Collection col = (Collection) value; + TypeConverter converter = getTypeConverter(context); + result = createCollection(toType, memberType, col.size()); + + for (Object aCol : col) { + Object convertedValue = converter.convertValue(context, target, member, propertyName, aCol, memberType); + if (!TypeConverter.NO_CONVERSION_POSSIBLE.equals(convertedValue)) { + result.add(convertedValue); + } + } + } else { + result = createCollection(toType, memberType, -1); + result.add(value); + } + + return result; + + } + + private Collection createCollection(Class toType, Class memberType, int size) { + Collection result; + + if (toType == Set.class) { + if (size > 0) { + result = new HashSet(size); + } else { + result = new HashSet(); + } + } else if (toType == SortedSet.class) { + result = new TreeSet(); + } else { + if (size > 0) { + result = new XWorkList(memberType, size); + } else { + result = new XWorkList(memberType); + } + } + + return result; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java new file mode 100644 index 0000000..99b1558 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DateConverter.java @@ -0,0 +1,101 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.XWorkException; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Member; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Map; + +public class DateConverter extends DefaultTypeConverter { + + @Override + public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { + Date result = null; + + if (value instanceof String && value != null && ((String) value).length() > 0) { + String sa = (String) value; + Locale locale = getLocale(context); + + DateFormat df = null; + if (java.sql.Time.class == toType) { + df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); + } else if (java.sql.Timestamp.class == toType) { + Date check = null; + SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.MEDIUM, + locale); + SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT, + locale); + + SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, + locale); + + SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt}; + for (SimpleDateFormat fmt : fmts) { + try { + check = fmt.parse(sa); + df = fmt; + if (check != null) { + break; + } + } catch (ParseException ignore) { + } + } + } else if (java.util.Date.class == toType) { + Date check; + DateFormat[] dfs = getDateFormats(locale); + for (DateFormat df1 : dfs) { + try { + check = df1.parse(sa); + df = df1; + if (check != null) { + break; + } + } catch (ParseException ignore) { + } + } + } + //final fallback for dates without time + if (df == null) { + df = DateFormat.getDateInstance(DateFormat.SHORT, locale); + } + try { + df.setLenient(false); // let's use strict parsing (XW-341) + result = df.parse(sa); + if (!(Date.class == toType)) { + try { + Constructor constructor = toType.getConstructor(new Class[]{long.class}); + return constructor.newInstance(new Object[]{Long.valueOf(result.getTime())}); + } catch (Exception e) { + throw new XWorkException("Couldn't create class " + toType + " using default (long) constructor", e); + } + } + } catch (ParseException e) { + throw new XWorkException("Could not parse date", e); + } + } else if (Date.class.isAssignableFrom(value.getClass())) { + result = (Date) value; + } + return result; + } + + private DateFormat[] getDateFormats(Locale locale) { + DateFormat dt1 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale); + DateFormat dt2 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); + DateFormat dt3 = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); + + DateFormat d1 = DateFormat.getDateInstance(DateFormat.SHORT, locale); + DateFormat d2 = DateFormat.getDateInstance(DateFormat.MEDIUM, locale); + DateFormat d3 = DateFormat.getDateInstance(DateFormat.LONG, locale); + + DateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + return new DateFormat[]{dt1, dt2, dt3, rfc3399, d1, d2, d3}; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java new file mode 100644 index 0000000..c3faae3 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionAnnotationProcessor.java @@ -0,0 +1,76 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.ConversionAnnotationProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; +import com.opensymphony.xwork2.conversion.annotations.ConversionRule; +import com.opensymphony.xwork2.conversion.annotations.ConversionType; +import com.opensymphony.xwork2.conversion.annotations.TypeConversion; +import com.opensymphony.xwork2.inject.Inject; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.Map; + +/** + * Default implementation of {@link ConversionAnnotationProcessor} + */ +public class DefaultConversionAnnotationProcessor implements ConversionAnnotationProcessor { + + private static final Logger LOG = LogManager.getLogger(DefaultConversionAnnotationProcessor.class); + + private TypeConverterCreator converterCreator; + private TypeConverterHolder converterHolder; + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + @Inject + public void setTypeConverterHolder(TypeConverterHolder converterHolder) { + this.converterHolder = converterHolder; + } + + public void process(Map<String, Object> mapping, TypeConversion tc, String key) { + LOG.debug("TypeConversion [{}] with key: [{}]", tc.converter(), key); + if (key == null) { + return; + } + try { + if (tc.type() == ConversionType.APPLICATION) { + converterHolder.addDefaultMapping(key, converterCreator.createTypeConverter(tc.converter())); + } else { + if (tc.rule() == ConversionRule.KEY_PROPERTY || tc.rule() == ConversionRule.CREATE_IF_NULL) { + mapping.put(key, tc.value()); + } + //for properties of classes + else if (tc.rule() != ConversionRule.ELEMENT || tc.rule() == ConversionRule.KEY || tc.rule() == ConversionRule.COLLECTION) { + mapping.put(key, converterCreator.createTypeConverter(tc.converter())); + } + //for keys of Maps + else if (tc.rule() == ConversionRule.KEY) { + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass(tc.converter()); + LOG.debug("Converter class: [{}]", converterClass); + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + mapping.put(key, converterCreator.createTypeConverter(tc.converter())); + } else { + mapping.put(key, converterClass); + LOG.debug("Object placed in mapping for key [{}] is [{}]", key, mapping.get(key)); + } + } + //elements(values) of maps / lists + else { + mapping.put(key, Thread.currentThread().getContextClassLoader().loadClass(tc.converter())); + } + } + } catch (Exception e) { + LOG.debug("Got exception for {}", key, e); + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java new file mode 100644 index 0000000..488d9ce8 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionFileProcessor.java @@ -0,0 +1,98 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.FileManager; +import com.opensymphony.xwork2.FileManagerFactory; +import com.opensymphony.xwork2.conversion.ConversionFileProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.io.InputStream; +import java.util.Map; +import java.util.Properties; + +/** + * Default implementation of {@link ConversionFileProcessor} + */ +public class DefaultConversionFileProcessor implements ConversionFileProcessor { + + private static final Logger LOG = LogManager.getLogger(DefaultConversionFileProcessor.class); + + private FileManager fileManager; + private TypeConverterCreator converterCreator; + + @Inject + public void setFileManagerFactory(FileManagerFactory factory) { + fileManager = factory.getFileManager(); + } + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + public void process(Map<String, Object> mapping, Class clazz, String converterFilename) { + try { + InputStream is = fileManager.loadFile(ClassLoaderUtil.getResource(converterFilename, clazz)); + + if (is != null) { + LOG.debug("Processing conversion file [{}] for class [{}]", converterFilename, clazz); + + Properties prop = new Properties(); + prop.load(is); + + for (Map.Entry<Object, Object> entry : prop.entrySet()) { + String key = (String) entry.getKey(); + + if (mapping.containsKey(key)) { + break; + } + // for keyProperty of Set + if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PROPERTY_PREFIX) + || key.startsWith(DefaultObjectTypeDeterminer.CREATE_IF_NULL_PREFIX)) { + LOG.debug("\t{}:{} [treated as String]", key, entry.getValue()); + mapping.put(key, entry.getValue()); + } + //for properties of classes + else if (!(key.startsWith(DefaultObjectTypeDeterminer.ELEMENT_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX) || + key.startsWith(DefaultObjectTypeDeterminer.DEPRECATED_ELEMENT_PREFIX)) + ) { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + LOG.debug("\t{}:{} [treated as TypeConverter {}]", key, entry.getValue(), _typeConverter); + mapping.put(key, _typeConverter); + } + //for keys of Maps + else if (key.startsWith(DefaultObjectTypeDeterminer.KEY_PREFIX)) { + + Class converterClass = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + + //check if the converter is a type converter if it is one + //then just put it in the map as is. Otherwise + //put a value in for the type converter of the class + if (converterClass.isAssignableFrom(TypeConverter.class)) { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + LOG.debug("\t{}:{} [treated as TypeConverter {}]", key, entry.getValue(), _typeConverter); + mapping.put(key, _typeConverter); + } else { + LOG.debug("\t{}:{} [treated as Class {}]", key, entry.getValue(), converterClass); + mapping.put(key, converterClass); + } + } + //elements(values) of maps / lists + else { + Class _c = Thread.currentThread().getContextClassLoader().loadClass((String) entry.getValue()); + LOG.debug("\t{}:{} [treated as Class {}]", key, entry.getValue(), _c); + mapping.put(key, _c); + } + } + } + } catch (Exception ex) { + LOG.error("Problem loading properties for {}", clazz.getName(), ex); + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java new file mode 100644 index 0000000..762496f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultConversionPropertiesProcessor.java @@ -0,0 +1,81 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.XWorkException; +import com.opensymphony.xwork2.conversion.ConversionPropertiesProcessor; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.ClassLoaderUtil; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.LogManager; + +import java.io.IOException; +import java.net.URL; +import java.util.Iterator; +import java.util.Map; +import java.util.Properties; + +/** + * TODO lukaszlenart: add a comment + */ +public class DefaultConversionPropertiesProcessor implements ConversionPropertiesProcessor { + + private static final Logger LOG = LogManager.getLogger(DefaultConversionPropertiesProcessor.class); + + private TypeConverterCreator converterCreator; + private TypeConverterHolder converterHolder; + + @Inject + public void setTypeConverterCreator(TypeConverterCreator converterCreator) { + this.converterCreator = converterCreator; + } + + @Inject + public void setTypeConverterHolder(TypeConverterHolder converterHolder) { + this.converterHolder = converterHolder; + } + + public void process(String propsName) { + loadConversionProperties(propsName, false); + } + + public void processRequired(String propsName) { + loadConversionProperties(propsName, true); + } + + public void loadConversionProperties(String propsName, boolean require) { + try { + Iterator<URL> resources = ClassLoaderUtil.getResources(propsName, getClass(), true); + while (resources.hasNext()) { + URL url = resources.next(); + Properties props = new Properties(); + props.load(url.openStream()); + + LOG.debug("Processing conversion file [{}]", propsName); + + for (Object o : props.entrySet()) { + Map.Entry entry = (Map.Entry) o; + String key = (String) entry.getKey(); + + try { + TypeConverter _typeConverter = converterCreator.createTypeConverter((String) entry.getValue()); + if (LOG.isDebugEnabled()) { + LOG.debug("\t{}:{} [treated as TypeConverter {}]", key, entry.getValue(), _typeConverter); + } + converterHolder.addDefaultMapping(key, _typeConverter); + } catch (Exception e) { + LOG.error("Conversion registration error", e); + } + } + } + } catch (IOException ex) { + if (require) { + throw new XWorkException("Cannot load conversion properties file: "+propsName, ex); + } else { + LOG.debug("Cannot load conversion properties file: {}", propsName, ex); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java new file mode 100644 index 0000000..3b35102 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultObjectTypeDeterminer.java @@ -0,0 +1,294 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.CreateIfNull; +import com.opensymphony.xwork2.util.Element; +import com.opensymphony.xwork2.util.Key; +import com.opensymphony.xwork2.util.KeyProperty; +import com.opensymphony.xwork2.util.reflection.ReflectionException; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import org.apache.commons.lang3.BooleanUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.beans.IntrospectionException; +import java.lang.annotation.Annotation; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +/** + * <!-- START SNIPPET: javadoc --> + * + * This {@link ObjectTypeDeterminer} looks at the <b>Class-conversion.properties</b> for entries that indicated what + * objects are contained within Maps and Collections. For Collections, such as Lists, the element is specified using the + * pattern <b>Element_xxx</b>, where xxx is the field name of the collection property in your action or object. For + * Maps, both the key and the value may be specified by using the pattern <b>Key_xxx</b> and <b>Element_xxx</b>, + * respectively. + * + * <p/> From WebWork 2.1.x, the <b>Collection_xxx</b> format is still supported and honored, although it is deprecated + * and will be removed eventually. + * + * <!-- END SNIPPET: javadoc --> + * + * @author Gabriel Zimmerman + */ +public class DefaultObjectTypeDeterminer implements ObjectTypeDeterminer { + + protected static final Logger LOG = LogManager.getLogger(DefaultObjectTypeDeterminer.class); + + public static final String KEY_PREFIX = "Key_"; + public static final String ELEMENT_PREFIX = "Element_"; + public static final String KEY_PROPERTY_PREFIX = "KeyProperty_"; + public static final String CREATE_IF_NULL_PREFIX = "CreateIfNull_"; + public static final String DEPRECATED_ELEMENT_PREFIX = "Collection_"; + + private ReflectionProvider reflectionProvider; + private XWorkConverter xworkConverter; + + @Inject + public DefaultObjectTypeDeterminer(@Inject XWorkConverter converter, @Inject ReflectionProvider provider) { + this.reflectionProvider = provider; + this.xworkConverter = converter; + } + + /** + * Determines the key class by looking for the value of @Key annotation for the given class. + * If no annotation is found, the key class is determined by using the generic parametrics. + * + * As fallback, it determines the key class by looking for the value of Key_${property} in the properties + * file for the given class. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getKeyClass(Class, String) + */ + public Class getKeyClass(Class parentClass, String property) { + Key annotation = getAnnotation(parentClass, property, Key.class); + if (annotation != null) { + return annotation.value(); + } + Class clazz = getClass(parentClass, property, false); + if (clazz != null) { + return clazz; + } + return (Class) xworkConverter.getConverter(parentClass, KEY_PREFIX + property); + } + + /** + * Determines the element class by looking for the value of @Element annotation for the given + * class. + * If no annotation is found, the element class is determined by using the generic parametrics. + * + * As fallback, it determines the key class by looking for the value of Element_${property} in the properties + * file for the given class. Also looks for the deprecated Collection_${property} + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getElementClass(Class, String, Object) + */ + public Class getElementClass(Class parentClass, String property, Object key) { + Element annotation = getAnnotation(parentClass, property, Element.class); + if (annotation != null) { + return annotation.value(); + } + Class clazz = getClass(parentClass, property, true); + if (clazz != null) { + return clazz; + } + clazz = (Class) xworkConverter.getConverter(parentClass, ELEMENT_PREFIX + property); + if (clazz == null) { + clazz = (Class) xworkConverter.getConverter(parentClass, DEPRECATED_ELEMENT_PREFIX + property); + if (clazz != null) { + LOG.info("The Collection_xxx pattern for collection type conversion is deprecated. Please use Element_xxx!"); + } + } + return clazz; + } + + /** + * Determines the key property for a Collection by getting it from the @KeyProperty annotation. + * + * As fallback, it determines the String key property for a Collection by getting it from the conversion properties + * file using the KeyProperty_ prefix. KeyProperty_${property}=somePropertyOfBeansInTheSet + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @see com.opensymphony.xwork2.conversion.ObjectTypeDeterminer#getKeyProperty(Class, String) + */ + public String getKeyProperty(Class parentClass, String property) { + KeyProperty annotation = getAnnotation(parentClass, property, KeyProperty.class); + if (annotation != null) { + return annotation.value(); + } + return (String) xworkConverter.getConverter(parentClass, KEY_PROPERTY_PREFIX + property); + } + + /** + * Determines the createIfNull property for a Collection or Map by getting it from the @CreateIfNull annotation. + * + * As fallback, it determines the boolean CreateIfNull property for a Collection or Map by getting it from the + * conversion properties file using the CreateIfNull_ prefix. CreateIfNull_${property}=true|false + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param target the target object + * @param keyProperty the keyProperty value + * @param isIndexAccessed <tt>true</tt>, if the collection or map is accessed via index, <tt>false</tt> otherwise. + * @return <tt>true</tt>, if the Collection or Map should be created, <tt>false</tt> otherwise. + * @see ObjectTypeDeterminer#getKeyProperty(Class, String) + */ + public boolean shouldCreateIfNew(Class parentClass, String property, Object target, String keyProperty, boolean isIndexAccessed) { + CreateIfNull annotation = getAnnotation(parentClass, property, CreateIfNull.class); + if (annotation != null) { + return annotation.value(); + } + String configValue = (String) xworkConverter.getConverter(parentClass, CREATE_IF_NULL_PREFIX + property); + //check if a value is in the config + if (configValue != null) { + return BooleanUtils.toBoolean(configValue); + } + + //default values depend on target type + //and whether this is accessed by an index + //in the case of List + return (target instanceof Map) || isIndexAccessed; + } + + /** + * Retrieves an annotation for the specified property of field, setter or getter. + * + * @param <T> the annotation type to be retrieved + * @param parentClass the class + * @param property the property + * @param annotationClass the annotation + * @return the field or setter/getter annotation or <code>null</code> if not found + */ + protected <T extends Annotation> T getAnnotation(Class parentClass, String property, Class<T> annotationClass) { + T annotation = null; + Field field = reflectionProvider.getField(parentClass, property); + + if (field != null) { + annotation = field.getAnnotation(annotationClass); + } + if (annotation == null) { // HINT: try with setter + annotation = getAnnotationFromSetter(parentClass, property, annotationClass); + } + if (annotation == null) { // HINT: try with getter + annotation = getAnnotationFromGetter(parentClass, property, annotationClass); + } + + return annotation; + } + + /** + * Retrieves an annotation for the specified field of getter. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param annotationClass The annotation + * @return concrete Annotation instance or <tt>null</tt> if none could be retrieved. + */ + private <T extends Annotation> T getAnnotationFromGetter(Class parentClass, String property, Class<T> annotationClass) { + try { + Method getter = reflectionProvider.getGetMethod(parentClass, property); + + if (getter != null) { + return getter.getAnnotation(annotationClass); + } + } catch (ReflectionException | IntrospectionException e) { + // ignore + } + return null; + } + + /** + * Retrieves an annotation for the specified field of setter. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param annotationClass The annotation + * @return concrete Annotation instance or <tt>null</tt> if none could be retrieved. + */ + private <T extends Annotation> T getAnnotationFromSetter(Class parentClass, String property, Class<T> annotationClass) { + try { + Method setter = reflectionProvider.getSetMethod(parentClass, property); + + if (setter != null) { + return setter.getAnnotation(annotationClass); + } + } catch (ReflectionException | IntrospectionException e) { + // ignore + } + return null; + } + + /** + * Returns the class for the given field via generic type check. + * + * @param parentClass the Class which contains as a property the Map or Collection we are finding the key for. + * @param property the property of the Map or Collection for the given parent class + * @param element <tt>true</tt> for indexed types and Maps. + * @return Class of the specified field. + */ + private Class getClass(Class parentClass, String property, boolean element) { + try { + Field field = reflectionProvider.getField(parentClass, property); + Type genericType = null; + // Check fields first + if (field != null) { + genericType = field.getGenericType(); + } + // Try to get ParameterType from setter method + if (genericType == null || !(genericType instanceof ParameterizedType)) { + try { + Method setter = reflectionProvider.getSetMethod(parentClass, property); + genericType = setter != null ? setter.getGenericParameterTypes()[0] : null; + } catch (ReflectionException | IntrospectionException e) { + // ignore + } + } + + // Try to get ReturnType from getter method + if (genericType == null || !(genericType instanceof ParameterizedType)) { + try { + Method getter = reflectionProvider.getGetMethod(parentClass, property); + genericType = getter.getGenericReturnType(); + } catch (ReflectionException | IntrospectionException e) { + // ignore + } + } + + if (genericType instanceof ParameterizedType) { + ParameterizedType type = (ParameterizedType) genericType; + int index = (element && type.getRawType().toString().contains(Map.class.getName())) ? 1 : 0; + Type resultType = type.getActualTypeArguments()[index]; + if (resultType instanceof ParameterizedType) { + return (Class) ((ParameterizedType) resultType).getRawType(); + } + return (Class) resultType; + } + } catch (Exception e) { + LOG.debug("Error while retrieving generic property class for property: {}", property, e); + } + return null; + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java new file mode 100644 index 0000000..47bcd1b --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverter.java @@ -0,0 +1,355 @@ +//-------------------------------------------------------------------------- +// Copyright (c) 1998-2004, Drew Davidson and Luke Blanshard +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// Neither the name of the Drew Davidson nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS +// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED +// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH +// DAMAGE. +//-------------------------------------------------------------------------- +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.ActionContext; +import com.opensymphony.xwork2.LocaleProvider; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.inject.Container; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; + +import java.lang.reflect.Array; +import java.lang.reflect.Member; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.util.Collections; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * Default type conversion. Converts among numeric types and also strings. Contains the basic + * type mapping code from OGNL. + * + * @author Luke Blanshard ([email protected]) + * @author Drew Davidson ([email protected]) + */ +public abstract class DefaultTypeConverter implements TypeConverter { + + protected static String MILLISECOND_FORMAT = ".SSS"; + + private static final String NULL_STRING = "null"; + + private static final Map<Class, Object> primitiveDefaults; + + private Container container; + + static { + Map<Class, Object> map = new HashMap<>(); + map.put(Boolean.TYPE, Boolean.FALSE); + map.put(Byte.TYPE, Byte.valueOf((byte) 0)); + map.put(Short.TYPE, Short.valueOf((short) 0)); + map.put(Character.TYPE, new Character((char) 0)); + map.put(Integer.TYPE, Integer.valueOf(0)); + map.put(Long.TYPE, Long.valueOf(0L)); + map.put(Float.TYPE, new Float(0.0f)); + map.put(Double.TYPE, new Double(0.0)); + map.put(BigInteger.class, new BigInteger("0")); + map.put(BigDecimal.class, new BigDecimal(0.0)); + primitiveDefaults = Collections.unmodifiableMap(map); + } + + @Inject + public void setContainer(Container container) { + this.container = container; + } + + public Object convertValue(Map<String, Object> context, Object value, Class toType) { + return convertValue(value, toType); + } + + public Object convertValue(Map<String, Object> context, Object target, Member member, + String propertyName, Object value, Class toType) { + return convertValue(context, value, toType); + } + + public TypeConverter getTypeConverter( Map<String, Object> context ) + { + Object obj = context.get(TypeConverter.TYPE_CONVERTER_CONTEXT_KEY); + if (obj instanceof TypeConverter) { + return (TypeConverter) obj; + + // for backwards-compatibility + } else if (obj instanceof ognl.TypeConverter) { + return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); + } + return null; + } + + /** + * Returns the value converted numerically to the given class type + * + * This method also detects when arrays are being converted and converts the + * components of one array to the type of the other. + * + * @param value + * an object to be converted to the given type + * @param toType + * class type to be converted to + * @return converted value of the type given, or value if the value cannot + * be converted to the given type. + */ + public Object convertValue(Object value, Class toType) { + Object result = null; + + if (value != null) { + /* If array -> array then convert components of array individually */ + if (value.getClass().isArray() && toType.isArray()) { + Class componentType = toType.getComponentType(); + + result = Array.newInstance(componentType, Array + .getLength(value)); + for (int i = 0, icount = Array.getLength(value); i < icount; i++) { + Array.set(result, i, convertValue(Array.get(value, i), + componentType)); + } + } else { + if ((toType == Integer.class) || (toType == Integer.TYPE)) + result = (int) longValue(value); + if ((toType == Double.class) || (toType == Double.TYPE)) + result = doubleValue(value); + if ((toType == Boolean.class) || (toType == Boolean.TYPE)) + result = booleanValue(value) ? Boolean.TRUE : Boolean.FALSE; + if ((toType == Byte.class) || (toType == Byte.TYPE)) + result = (byte) longValue(value); + if ((toType == Character.class) || (toType == Character.TYPE)) + result = (char) longValue(value); + if ((toType == Short.class) || (toType == Short.TYPE)) + result = (short) longValue(value); + if ((toType == Long.class) || (toType == Long.TYPE)) + result = longValue(value); + if ((toType == Float.class) || (toType == Float.TYPE)) + result = new Float(doubleValue(value)); + if (toType == BigInteger.class) + result = bigIntValue(value); + if (toType == BigDecimal.class) + result = bigDecValue(value); + if (toType == String.class) + result = stringValue(value); + if (Enum.class.isAssignableFrom(toType)) + result = enumValue(toType, value); + } + } else { + if (toType.isPrimitive()) { + result = primitiveDefaults.get(toType); + } + } + return result; + } + + /** + * Evaluates the given object as a boolean: if it is a Boolean object, it's + * easy; if it's a Number or a Character, returns true for non-zero objects; + * and otherwise returns true for non-null objects. + * + * @param value + * an object to interpret as a boolean + * @return the boolean value implied by the given object + */ + public static boolean booleanValue(Object value) { + if (value == null) + return false; + Class c = value.getClass(); + if (c == Boolean.class) + return (Boolean) value; + // if ( c == String.class ) + // return ((String)value).length() > 0; + if (c == Character.class) + return (Character) value != 0; + if (value instanceof Number) + return ((Number) value).doubleValue() != 0; + return true; // non-null + } + + public Enum<?> enumValue(Class toClass, Object o) { + Enum<?> result = null; + if (o == null) { + result = null; + } else if (o instanceof String[]) { + result = Enum.valueOf(toClass, ((String[]) o)[0]); + } else if (o instanceof String) { + result = Enum.valueOf(toClass, (String) o); + } + return result; + } + + /** + * Evaluates the given object as a long integer. + * + * @param value + * an object to interpret as a long integer + * @return the long integer value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a long integer + */ + public static long longValue(Object value) throws NumberFormatException { + if (value == null) + return 0L; + Class c = value.getClass(); + if (c.getSuperclass() == Number.class) + return ((Number) value).longValue(); + if (c == Boolean.class) + return (Boolean) value ? 1 : 0; + if (c == Character.class) + return (Character) value; + return Long.parseLong(stringValue(value, true)); + } + + /** + * Evaluates the given object as a double-precision floating-point number. + * + * @param value + * an object to interpret as a double + * @return the double value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a double + */ + public static double doubleValue(Object value) throws NumberFormatException { + if (value == null) + return 0.0; + Class c = value.getClass(); + if (c.getSuperclass() == Number.class) + return ((Number) value).doubleValue(); + if (c == Boolean.class) + return (Boolean) value ? 1 : 0; + if (c == Character.class) + return (Character) value; + String s = stringValue(value, true); + + return (s.length() == 0) ? 0.0 : Double.parseDouble(s); + /* + * For 1.1 parseDouble() is not available + */ + // return Double.valueOf( value.toString() ).doubleValue(); + } + + /** + * Evaluates the given object as a BigInteger. + * + * @param value + * an object to interpret as a BigInteger + * @return the BigInteger value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a BigInteger + */ + public static BigInteger bigIntValue(Object value) + throws NumberFormatException { + if (value == null) + return BigInteger.valueOf(0L); + Class c = value.getClass(); + if (c == BigInteger.class) + return (BigInteger) value; + if (c == BigDecimal.class) + return ((BigDecimal) value).toBigInteger(); + if (c.getSuperclass() == Number.class) + return BigInteger.valueOf(((Number) value).longValue()); + if (c == Boolean.class) + return BigInteger.valueOf((Boolean) value ? 1 : 0); + if (c == Character.class) + return BigInteger.valueOf(((Character) value).charValue()); + return new BigInteger(stringValue(value, true)); + } + + /** + * Evaluates the given object as a BigDecimal. + * + * @param value + * an object to interpret as a BigDecimal + * @return the BigDecimal value implied by the given object + * @throws NumberFormatException + * if the given object can't be understood as a BigDecimal + */ + public static BigDecimal bigDecValue(Object value) + throws NumberFormatException { + if (value == null) + return BigDecimal.valueOf(0L); + Class c = value.getClass(); + if (c == BigDecimal.class) + return (BigDecimal) value; + if (c == BigInteger.class) + return new BigDecimal((BigInteger) value); + if (c.getSuperclass() == Number.class) + return new BigDecimal(((Number) value).doubleValue()); + if (c == Boolean.class) + return BigDecimal.valueOf((Boolean) value ? 1 : 0); + if (c == Character.class) + return BigDecimal.valueOf(((Character) value).charValue()); + return new BigDecimal(stringValue(value, true)); + } + + /** + * Evaluates the given object as a String and trims it if the trim flag is + * true. + * + * @param value + * an object to interpret as a String + * @return the String value implied by the given object as returned by the + * toString() method, or "null" if the object is null. + */ + public static String stringValue(Object value, boolean trim) { + String result; + + if (value == null) { + result = NULL_STRING; + } else { + result = value.toString(); + if (trim) { + result = result.trim(); + } + } + return result; + } + + /** + * Evaluates the given object as a String. + * + * @param value + * an object to interpret as a String + * @return the String value implied by the given object as returned by the + * toString() method, or "null" if the object is null. + */ + public static String stringValue(Object value) { + return stringValue(value, false); + } + + protected Locale getLocale(Map<String, Object> context) { + Locale locale = null; + if (context != null) { + locale = (Locale) context.get(ActionContext.LOCALE); + } + if (locale == null) { + locale = container.getInstance(LocaleProvider.class).getLocale(); + } + return locale; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java new file mode 100644 index 0000000..ed6ac22 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterCreator.java @@ -0,0 +1,35 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterCreator; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.ognl.XWorkTypeConverterWrapper; + +/** + * Default implementation of {@link TypeConverterCreator} + */ +public class DefaultTypeConverterCreator implements TypeConverterCreator { + + private ObjectFactory objectFactory; + + @Inject + public void setObjectFactory(ObjectFactory objectFactory) { + this.objectFactory = objectFactory; + } + + public TypeConverter createTypeConverter(String className) throws Exception { + // type converters are used across users + Object obj = objectFactory.buildBean(className, null); + if (obj instanceof TypeConverter) { + return (TypeConverter) obj; + + // For backwards compatibility + } else if (obj instanceof ognl.TypeConverter) { + return new XWorkTypeConverterWrapper((ognl.TypeConverter) obj); + } else { + throw new IllegalArgumentException("Type converter class " + obj.getClass() + " doesn't implement com.opensymphony.xwork2.conversion.TypeConverter"); + } + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java new file mode 100644 index 0000000..2acaf96 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/DefaultTypeConverterHolder.java @@ -0,0 +1,97 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.conversion.TypeConverter; +import com.opensymphony.xwork2.conversion.TypeConverterHolder; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; + +/** + * Default implementation of {@link TypeConverterHolder} + */ +public class DefaultTypeConverterHolder implements TypeConverterHolder { + + /** + * Record class and its type converter mapping. + * <pre> + * - String - classname as String + * - TypeConverter - instance of TypeConverter + * </pre> + */ + private HashMap<String, TypeConverter> defaultMappings = new HashMap<>(); // non-action (eg. returned value) + + /** + * Target class conversion Mappings. + * <pre> + * Map<Class, Map<String, Object>> + * - Class -> convert to class + * - Map<String, Object> + * - String -> property name + * eg. Element_property, property etc. + * - Object -> String to represent properties + * eg. value part of + * KeyProperty_property=id + * -> TypeConverter to represent an Ognl TypeConverter + * eg. value part of + * property=foo.bar.MyConverter + * -> Class to represent a class + * eg. value part of + * Element_property=foo.bar.MyObject + * </pre> + */ + private HashMap<Class, Map<String, Object>> mappings = new HashMap<>(); // action + + /** + * Unavailable target class conversion mappings, serves as a simple cache. + */ + private HashSet<Class> noMapping = new HashSet<>(); // action + + /** + * Record classes that doesn't have conversion mapping defined. + * <pre> + * - String -> classname as String + * </pre> + */ + protected HashSet<String> unknownMappings = new HashSet<>(); // non-action (eg. returned value) + + public void addDefaultMapping(String className, TypeConverter typeConverter) { + defaultMappings.put(className, typeConverter); + if (unknownMappings.contains(className)) { + unknownMappings.remove(className); + } + } + + public boolean containsDefaultMapping(String className) { + return defaultMappings.containsKey(className); + } + + public TypeConverter getDefaultMapping(String className) { + return defaultMappings.get(className); + } + + public Map<String, Object> getMapping(Class clazz) { + return mappings.get(clazz); + } + + public void addMapping(Class clazz, Map<String, Object> mapping) { + mappings.put(clazz, mapping); + } + + public boolean containsNoMapping(Class clazz) { + return noMapping.contains(clazz); + } + + public void addNoMapping(Class clazz) { + noMapping.add(clazz); + } + + public boolean containsUnknownMapping(String className) { + return unknownMappings.contains(className); + } + + public void addUnknownMapping(String className) { + unknownMappings.add(className); + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java new file mode 100644 index 0000000..949eede --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/EnumTypeConverter.java @@ -0,0 +1,124 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.conversion.impl; + +import java.util.Map; + + +/** + * <code>EnumTypeConverter</code> + * + * <!-- START SNIPPET: description --> + * This class converts java 5 enums to String and from String[] to enum. + * <p/> + * One of Java 5's improvements is providing enumeration facility. + * Up to now, there existed no enumerations. The only way to simulate was the so-called int Enum pattern: + * {code} + * public static final int SEASON_WINTER = 0; + * public static final int SEASON_SPRING = 1; + * public static final int SEASON_SUMMER = 2; + * public static final int SEASON_FALL = 3; + * {code} + * <p/> + * Java 5.0 now provides the following construct: + * {code} + * public static enum Season { WINTER, SPRING, SUMMER, FALL }; + * {code} + * <!-- END SNIPPET: description --> + * + * <!-- START SNIPPET: example --> + * h3. Implementing Java 5 Enumeration Type Conversion + * <p/> + * 1. myAction-conversion.properties* + * <p/> + * Place a myAction-conversion.properties-file in the path of your Action. + * Add the following entry to the properties-file: + * {code} + * nameOfYourField=fullyClassifiedNameOfYourConverter + * {code} + * + * <p/> + * 2. myAction.java* + * Your action contains the _enumeration_: + * {code} + * public enum Criticality {DEBUG, INFO, WARNING, ERROR, FATAL} + * {code} + * + * * Your action contains the _private field_: + * {code} + * private myEnum myFieldForEnum; + * {code} + * + * Your action contains _getters and setters_ for your field: + * {code} + * public myEnum getCriticality() { + * return myFieldForEnum; + * } + * + * public void setCriticality(myEnum myFieldForEnum) { + * this.myFieldForEnum= myFieldForEnum; + * } + * {code} + * <p/> + * 3. JSP* + * <p/> + * In your jsp you can access an enumeration value just normal by using the known <ww:property>-Tag: + * {code} + * <ww:property value="myField"/> + * {code} + * <!-- END SNIPPET: example --> + * + * @author Tamara Cattivelli + * @author <a href="mailto:[email protected]">Rainer Hermanns</a> + * @version $Id$ + * @deprecated Since Struts 2.1.0 as enum support is now built into XWork + */ +@Deprecated public class EnumTypeConverter extends DefaultTypeConverter { + + /** + * Converts the given object to a given type. How this is to be done is implemented in toClass. The OGNL context, o + * and toClass are given. This method should be able to handle conversion in general without any context or object + * specified. + * + * @param context - OGNL context under which the conversion is being done + * @param o - the object to be converted + * @param toClass - the class that contains the code to convert to enumeration + * @return Converted value of type declared in toClass or TypeConverter.NoConversionPossible to indicate that the + * conversion was not possible. + */ + @Override + public Object convertValue(Map<String, Object> context, Object o, Class toClass) { + if (o instanceof String[]) { + return convertFromString(((String[]) o)[0], toClass); + } else if (o instanceof String) { + return convertFromString((String) o, toClass); + } + + return super.convertValue(context, o, toClass); + } + + /** + * Converts one or more String values to the specified class. + * @param value - the String values to be converted, such as those submitted from an HTML form + * @param toClass - the class to convert to + * @return the converted object + */ + public java.lang.Enum convertFromString(String value, Class toClass) { + return Enum.valueOf(toClass, value); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java new file mode 100644 index 0000000..69f1ced --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/GenericsObjectTypeDeterminer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; + + +/** + * GenericsObjectTypeDeterminer + * + * @author Patrick Lightbody + * @author Rainer Hermanns + * @author <a href='mailto:the_mindstorm[at]evolva[dot]ro'>Alexandru Popescu</a> + * + * @deprecated Use DefaultObjectTypeDeterminer instead. Since XWork 2.0.4 the DefaultObjectTypeDeterminer handles the + * annotation processing. + */ +@Deprecated public class GenericsObjectTypeDeterminer extends DefaultObjectTypeDeterminer { + + public GenericsObjectTypeDeterminer(XWorkConverter conv, + XWorkBasicConverter basicConv, ReflectionProvider prov) { + super(conv, prov); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java new file mode 100644 index 0000000..386d070 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/InstantiatingNullHandler.java @@ -0,0 +1,157 @@ +/* + * Copyright 2002-2006,2009 The Apache Software Foundation. + * + * 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 com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.ObjectFactory; +import com.opensymphony.xwork2.conversion.NullHandler; +import com.opensymphony.xwork2.conversion.ObjectTypeDeterminer; +import com.opensymphony.xwork2.inject.Inject; +import com.opensymphony.xwork2.util.reflection.ReflectionContextState; +import com.opensymphony.xwork2.util.reflection.ReflectionProvider; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.beans.PropertyDescriptor; +import java.util.*; + + +/** + * <!-- START SNIPPET: javadoc --> + * + * Provided that the key {@link ReflectionContextState#CREATE_NULL_OBJECTS} is in the action context with a value of true (this key is set + * only during the execution of the {@link com.opensymphony.xwork2.interceptor.ParametersInterceptor}), OGNL expressions + * that have caused a NullPointerException will be temporarily stopped for evaluation while the system automatically + * tries to solve the null references by automatically creating the object. + * + * <p/> The following rules are used when handling null references: + * + * <ul> + * + * <li>If the property is declared <i>exactly</i> as a {@link Collection} or {@link List}, then an ArrayList shall be + * returned and assigned to the null references.</li> + * + * <li>If the property is declared as a {@link Map}, then a HashMap will be returned and assigned to the null + * references.</li> + * + * <li>If the null property is a simple bean with a no-arg constructor, it will simply be created using the {@link + * ObjectFactory#buildBean(java.lang.Class, java.util.Map)} method.</li> + * + * </ul> + * + * <!-- END SNIPPET: javadoc --> + * + * <!-- START SNIPPET: example --> + * + * For example, if a form element has a text field named <b>person.name</b> and the expression <i>person</i> evaluates + * to null, then this class will be invoked. Because the <i>person</i> expression evaluates to a <i>Person</i> class, a + * new Person is created and assigned to the null reference. Finally, the name is set on that object and the overall + * effect is that the system automatically created a Person object for you, set it by calling setUsers() and then + * finally called getUsers().setName() as you would typically expect. + * + * <!-- END SNIPPET: example> + * + * @author Matt Ho + * @author Patrick Lightbody + */ +public class InstantiatingNullHandler implements NullHandler { + + /** + * @deprecated Use {@link ReflectionContextState#CREATE_NULL_OBJECTS} instead + */ + @Deprecated public static final String CREATE_NULL_OBJECTS = ReflectionContextState.CREATE_NULL_OBJECTS; + private static final Logger LOG = LogManager.getLogger(InstantiatingNullHandler.class); + private ReflectionProvider reflectionProvider; + private ObjectFactory objectFactory; + private ObjectTypeDeterminer objectTypeDeterminer; + + @Inject + public void setObjectTypeDeterminer(ObjectTypeDeterminer det) { + this.objectTypeDeterminer = det; + } + + @Inject + public void setReflectionProvider(ReflectionProvider prov) { + this.reflectionProvider = prov; + } + + @Inject + public void setObjectFactory(ObjectFactory fac) { + this.objectFactory = fac; + } + + public Object nullMethodResult(Map<String, Object> context, Object target, String methodName, Object[] args) { + LOG.debug("Entering nullMethodResult"); + return null; + } + + public Object nullPropertyValue(Map<String, Object> context, Object target, Object property) { + LOG.debug("Entering nullPropertyValue [target={}, property={}]", target, property); + boolean c = ReflectionContextState.isCreatingNullObjects(context); + + if (!c) { + return null; + } + + if ((target == null) || (property == null)) { + return null; + } + + try { + String propName = property.toString(); + Object realTarget = reflectionProvider.getRealTarget(propName, context, target); + Class clazz = null; + + if (realTarget != null) { + PropertyDescriptor pd = reflectionProvider.getPropertyDescriptor(realTarget.getClass(), propName); + if (pd == null) { + return null; + } + + clazz = pd.getPropertyType(); + } + + if (clazz == null) { + // can't do much here! + return null; + } + + Object param = createObject(clazz, realTarget, propName, context); + + reflectionProvider.setValue(propName, context, realTarget, param); + + return param; + } catch (Exception e) { + LOG.error("Could not create and/or set value back on to object", e); + } + + return null; + } + + private Object createObject(Class clazz, Object target, String property, Map<String, Object> context) throws Exception { + if (Set.class.isAssignableFrom(clazz)) { + return new HashSet(); + } else if (Collection.class.isAssignableFrom(clazz)) { + return new ArrayList(); + } else if (clazz == Map.class) { + return new HashMap(); + } else if (clazz == EnumMap.class) { + Class keyClass = objectTypeDeterminer.getKeyClass(target.getClass(), property); + return new EnumMap(keyClass); + } + + return objectFactory.buildBean(clazz, context); + } +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/NumberConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/NumberConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/NumberConverter.java new file mode 100644 index 0000000..ab6efc0 --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/NumberConverter.java @@ -0,0 +1,118 @@ +package com.opensymphony.xwork2.conversion.impl; + +import com.opensymphony.xwork2.XWorkException; + +import java.lang.reflect.Member; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.text.NumberFormat; +import java.text.ParsePosition; +import java.util.Map; + +public class NumberConverter extends DefaultTypeConverter { + + public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { + if (value instanceof String) { + if (toType == BigDecimal.class) { + return new BigDecimal((String) value); + } else if (toType == BigInteger.class) { + return new BigInteger((String) value); + } else if (toType.isPrimitive()) { + Object convertedValue = super.convertValue(context, value, toType); + String stringValue = (String) value; + if (!isInRange((Number) convertedValue, stringValue, toType)) + throw new XWorkException("Overflow or underflow casting: \"" + stringValue + "\" into class " + convertedValue.getClass().getName()); + + return convertedValue; + } else { + String stringValue = (String) value; + if (!toType.isPrimitive() && (stringValue == null || stringValue.length() == 0)) { + return null; + } + NumberFormat numFormat = NumberFormat.getInstance(getLocale(context)); + ParsePosition parsePos = new ParsePosition(0); + if (isIntegerType(toType)) { + numFormat.setParseIntegerOnly(true); + } + numFormat.setGroupingUsed(true); + Number number = numFormat.parse(stringValue, parsePos); + + if (parsePos.getIndex() != stringValue.length()) { + throw new XWorkException("Unparseable number: \"" + stringValue + "\" at position " + + parsePos.getIndex()); + } else { + if (!isInRange(number, stringValue, toType)) + throw new XWorkException("Overflow or underflow casting: \"" + stringValue + "\" into class " + number.getClass().getName()); + + value = super.convertValue(context, number, toType); + } + } + } else if (value instanceof Object[]) { + Object[] objArray = (Object[]) value; + + if (objArray.length == 1) { + return convertValue(context, null, null, null, objArray[0], toType); + } + } + + // pass it through DefaultTypeConverter + return super.convertValue(context, value, toType); + } + + protected boolean isInRange(Number value, String stringValue, Class toType) { + Number bigValue = null; + Number lowerBound = null; + Number upperBound = null; + + try { + if (double.class == toType || Double.class == toType) { + bigValue = new BigDecimal(stringValue); + // Double.MIN_VALUE is the smallest positive non-zero number + lowerBound = BigDecimal.valueOf(Double.MAX_VALUE).negate(); + upperBound = BigDecimal.valueOf(Double.MAX_VALUE); + } else if (float.class == toType || Float.class == toType) { + bigValue = new BigDecimal(stringValue); + // Float.MIN_VALUE is the smallest positive non-zero number + lowerBound = BigDecimal.valueOf(Float.MAX_VALUE).negate(); + upperBound = BigDecimal.valueOf(Float.MAX_VALUE); + } else if (byte.class == toType || Byte.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Byte.MIN_VALUE); + upperBound = BigInteger.valueOf(Byte.MAX_VALUE); + } else if (char.class == toType || Character.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Character.MIN_VALUE); + upperBound = BigInteger.valueOf(Character.MAX_VALUE); + } else if (short.class == toType || Short.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Short.MIN_VALUE); + upperBound = BigInteger.valueOf(Short.MAX_VALUE); + } else if (int.class == toType || Integer.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Integer.MIN_VALUE); + upperBound = BigInteger.valueOf(Integer.MAX_VALUE); + } else if (long.class == toType || Long.class == toType) { + bigValue = new BigInteger(stringValue); + lowerBound = BigInteger.valueOf(Long.MIN_VALUE); + upperBound = BigInteger.valueOf(Long.MAX_VALUE); + } else { + throw new IllegalArgumentException("Unexpected numeric type: " + toType.getName()); + } + } catch (NumberFormatException e) { + //shoult it fail here? BigInteger doesnt seem to be so nice parsing numbers as NumberFormat + return true; + } + + return ((Comparable)bigValue).compareTo(lowerBound) >= 0 && ((Comparable)bigValue).compareTo(upperBound) <= 0; + } + + private boolean isIntegerType(Class type) { + if (double.class == type || float.class == type || Double.class == type || Float.class == type + || char.class == type || Character.class == type) { + return false; + } + + return true; + } + +} http://git-wip-us.apache.org/repos/asf/struts/blob/31af5842/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java new file mode 100644 index 0000000..9c6cc8f --- /dev/null +++ b/core/src/main/java/com/opensymphony/xwork2/conversion/impl/StringConverter.java @@ -0,0 +1,73 @@ +package com.opensymphony.xwork2.conversion.impl; + +import org.apache.commons.lang3.StringUtils; + +import java.lang.reflect.Member; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class StringConverter extends DefaultTypeConverter { + + @Override + public Object convertValue(Map<String, Object> context, Object target, Member member, String propertyName, Object value, Class toType) { + String result = null; + + if (value instanceof int[]) { + int[] x = (int[]) value; + List<Integer> intArray = new ArrayList<>(x.length); + + for (int aX : x) { + intArray.add(Integer.valueOf(aX)); + } + + result = StringUtils.join(intArray, ", "); + } else if (value instanceof long[]) { + long[] x = (long[]) value; + List<Long> longArray = new ArrayList<>(x.length); + + for (long aX : x) { + longArray.add(Long.valueOf(aX)); + } + + result = StringUtils.join(longArray, ", "); + } else if (value instanceof double[]) { + double[] x = (double[]) value; + List<Double> doubleArray = new ArrayList<>(x.length); + + for (double aX : x) { + doubleArray.add(new Double(aX)); + } + + result = StringUtils.join(doubleArray, ", "); + } else if (value instanceof boolean[]) { + boolean[] x = (boolean[]) value; + List<Boolean> booleanArray = new ArrayList<>(x.length); + + for (boolean aX : x) { + booleanArray.add(new Boolean(aX)); + } + + result = StringUtils.join(booleanArray, ", "); + } else if (value instanceof Date) { + DateFormat df = null; + if (value instanceof java.sql.Time) { + df = DateFormat.getTimeInstance(DateFormat.MEDIUM, getLocale(context)); + } else if (value instanceof java.sql.Timestamp) { + SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, + DateFormat.MEDIUM, + getLocale(context)); + df = new SimpleDateFormat(dfmt.toPattern() + MILLISECOND_FORMAT); + } else { + df = DateFormat.getDateInstance(DateFormat.SHORT, getLocale(context)); + } + result = df.format(value); + } else if (value instanceof String[]) { + result = StringUtils.join((String[]) value, ", "); + } + return result; + } +}
