Author: tchemit Date: 2010-05-06 11:53:21 +0200 (Thu, 06 May 2010) New Revision: 1840 Url: http://nuiton.org/repositories/revision/nuiton-utils/1840 Log: add CollectionStragegy for binder api Modified: trunk/src/main/java/org/nuiton/util/beans/Binder.java trunk/src/main/java/org/nuiton/util/beans/BinderBuilder.java trunk/src/main/java/org/nuiton/util/beans/BinderModel.java trunk/src/main/java/org/nuiton/util/beans/BinderProvider.java Modified: trunk/src/main/java/org/nuiton/util/beans/Binder.java =================================================================== --- trunk/src/main/java/org/nuiton/util/beans/Binder.java 2010-05-06 09:36:24 UTC (rev 1839) +++ trunk/src/main/java/org/nuiton/util/beans/Binder.java 2010-05-06 09:53:21 UTC (rev 1840) @@ -29,9 +29,16 @@ import org.apache.commons.logging.LogFactory; import org.nuiton.util.ObjectUtil; +import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; import java.util.Map; +import java.util.Set; import java.util.TreeMap; /** @@ -50,18 +57,47 @@ * @param <O> the destination bean type * @since 1.1.5 */ -public class Binder<I, O> implements java.io.Serializable { +public class Binder<I, O> implements Serializable { - /** - * Logger - */ + /** Logger */ private static final Log log = LogFactory.getLog(Binder.class); private static final long serialVersionUID = 1L; /** - * the model of the binder + * Types of loading of collections. + * + * @since 1.3 */ + public enum CollectionStrategy { + + /** To just copy the reference of the collection. */ + copy { + public Object copy(Object readValue) { + + // by default, just return same reference + return readValue; + } + }, + + /** To duplicate the collection */ + duplicate { + + @Override + public Object copy(Object readValue) { + if (readValue instanceof Set<?>) { + return new HashSet((Set<?>) readValue); + } + if (readValue instanceof List<?>) { + return new ArrayList((Collection<?>) readValue); + } + return readValue; + }}; + + public abstract Object copy(Object readValue); + } + + /** the model of the binder */ protected BinderModel<I, O> model; /** @@ -80,7 +116,7 @@ String... propertyNames) { if (source == null) { // special limit case - return java.util.Collections.emptyMap(); + return Collections.emptyMap(); } propertyNames = getProperties(propertyNames); @@ -94,15 +130,21 @@ read = readMethod.invoke(source); if (log.isDebugEnabled()) { log.debug("property " + sourceProperty + ", type : " + - readMethod.getReturnType() + ", value = " + read); + readMethod.getReturnType() + ", value = " + read); } if (readMethod.getReturnType().isPrimitive() && - ObjectUtil.getNullValue( - readMethod.getReturnType()).equals(read)) { + ObjectUtil.getNullValue( + readMethod.getReturnType()).equals(read)) { // for primitive type case, force nullity read = null; } if (read != null) { + + if (model.containsCollectionProperty(sourceProperty)) { + // specific collection strategy is set, must use it + read = getCollectionValue(sourceProperty, read); + } + result.put(sourceProperty, read); } } catch (IllegalAccessException e) { @@ -136,6 +178,7 @@ propertyNames = getProperties(propertyNames); + for (String sourceProperty : propertyNames) { String targetProperty = model.getTargetProperty(sourceProperty); @@ -154,8 +197,13 @@ } if (log.isDebugEnabled()) { log.debug("property " + sourceProperty + ", type : " + - readMethod.getReturnType() + ", value = " + read); + readMethod.getReturnType() + ", value = " + read); } + + if (model.containsCollectionProperty(sourceProperty)) { + // specific collection strategy is set, must use it + read = getCollectionValue(sourceProperty, read); + } model.getTargetWriteMethod(targetProperty).invoke(target, read); } catch (IllegalAccessException e) { throw new RuntimeException(e); @@ -165,6 +213,13 @@ } } + protected Object getCollectionValue(String sourceProperty, Object readValue) { + CollectionStrategy strategy = + model.getCollectionStrategy(sourceProperty); + Object result = strategy.copy(readValue); + return result; + } + /** * Obtain the properties, if none is given in {@code propertyNames} * parameter, will use all property names defined in binder's model, @@ -184,8 +239,9 @@ // use a subset of properties, must check them for (String propertyName : propertyNames) { if (!model.containsSourceProperty(propertyName)) { - throw new IllegalArgumentException("property '" + - propertyName + "' is not known by binder"); + throw new IllegalArgumentException( + "property '" + propertyName + + "' is not known by binder"); } } } Modified: trunk/src/main/java/org/nuiton/util/beans/BinderBuilder.java =================================================================== --- trunk/src/main/java/org/nuiton/util/beans/BinderBuilder.java 2010-05-06 09:36:24 UTC (rev 1839) +++ trunk/src/main/java/org/nuiton/util/beans/BinderBuilder.java 2010-05-06 09:53:21 UTC (rev 1840) @@ -31,6 +31,7 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Collection; import java.util.Map; import java.util.TreeMap; @@ -46,17 +47,13 @@ */ public class BinderBuilder { - /** - * current model used to build the binder - */ + /** current model used to build the binder */ protected BinderModel<?, ?> model; - /** - * source properties descriptors - */ + + /** source properties descriptors */ protected Map<String, PropertyDescriptor> sourceDescriptors; - /** - * target properties descriptors - */ + + /** target properties descriptors */ protected Map<String, PropertyDescriptor> targetDescriptors; public BinderBuilder() { @@ -130,9 +127,10 @@ } if (model != null) { - throw new IllegalStateException("there is already a binderModel in " + - "construction, release it with the method createBinder " + - "before using this method."); + throw new IllegalStateException( + "there is already a binderModel in construction, release " + + "it with the method createBinder before using this method." + ); } // init model @@ -142,31 +140,9 @@ sourceDescriptors = new TreeMap<String, PropertyDescriptor>(); loadDescriptors(model.getSourceType(), sourceDescriptors); -// try { -// -// BeanInfo beanInfo = Introspector.getBeanInfo(model.getSourceType()); -// for (PropertyDescriptor descriptor : -// beanInfo.getPropertyDescriptors()) { -// sourceDescriptors.put(descriptor.getName(), descriptor); -// } -// } catch (IntrospectionException e) { -// throw new RuntimeException("Could not obtain bean properties " + -// "descriptors for source type " + sourceType, e); -// } // obtain target descriptors targetDescriptors = new TreeMap<String, PropertyDescriptor>(); loadDescriptors(model.getTargetType(), targetDescriptors); -// try { -// -// BeanInfo beanInfo = Introspector.getBeanInfo(model.getTargetType()); -// for (PropertyDescriptor descriptor : -// beanInfo.getPropertyDescriptors()) { -// targetDescriptors.put(descriptor.getName(), descriptor); -// } -// } catch (IntrospectionException e) { -// throw new RuntimeException("Could not obtain bean properties " + -// "descriptors for target type " + targetType, e); -// } return this; } @@ -186,7 +162,7 @@ public Binder<?, ?> createBinder() throws NullPointerException, IllegalStateException { - Binder binder = createBinder(Binder.class); + Binder<?, ?> binder = createBinder(Binder.class); return binder; } @@ -214,8 +190,8 @@ binder.setModel(model); return binder; } catch (Exception e) { - throw new IllegalStateException("could not instanciate binder " + - binderType, e); + throw new IllegalStateException( + "could not instanciate binder " + binderType, e); } finally { // release resources of the model model = null; @@ -242,8 +218,8 @@ checkModelExists(); for (String property : properties) { if (property == null) { - throw new NullPointerException("parameter 'properties' can " + - "not contains a null value"); + throw new NullPointerException( + "parameter 'properties' can not contains a null value"); } addProperty0(property, property); } @@ -267,12 +243,12 @@ String targetProperty) throws IllegalStateException, NullPointerException { if (sourceProperty == null) { - throw new NullPointerException("parameter 'sourceProperty' " + - "can not be null"); + throw new NullPointerException( + "parameter 'sourceProperty' can not be null"); } if (targetProperty == null) { - throw new NullPointerException("parameter 'targetProperty' " + - "can not be null"); + throw new NullPointerException( + "parameter 'targetProperty' can not be null"); } checkModelExists(); addProperty0(sourceProperty, targetProperty); @@ -305,40 +281,64 @@ NullPointerException { checkModelExists(); if (sourceAndTargetProperties.length % 2 != 0) { - throw new IllegalArgumentException("must have couple(s) of " + - "(sourceProperty,targetProperty) but had " + - Arrays.toString(sourceAndTargetProperties)); + throw new IllegalArgumentException( + "must have couple(s) of sourceProperty,targetProperty) " + + "but had " + Arrays.toString(sourceAndTargetProperties)); } for (int i = 0, max = sourceAndTargetProperties.length / 2; i < max; i++) { String sourceProperty = sourceAndTargetProperties[2 * i]; String targetProperty = sourceAndTargetProperties[2 * i + 1]; if (sourceProperty == null) { - throw new NullPointerException("parameter " + - "'sourceAndTargetProperties' can not contains " + - "a null value"); + throw new NullPointerException( + "parameter 'sourceAndTargetProperties' can not " + + "contains a null value"); } if (targetProperty == null) { - throw new NullPointerException("parameter " + - "'sourceAndTargetProperties' can not contains " + - "a null value"); + throw new NullPointerException( + "parameter 'sourceAndTargetProperties' can not " + + "contains a null value"); } addProperty0(sourceProperty, targetProperty); } return this; } + public BinderBuilder addCollectionStrategy(String propertyName, + Binder.CollectionStrategy strategy) { + // check property is registred + if (!model.containsSourceProperty(propertyName)) { + throw new IllegalArgumentException( + "source property '" + propertyName + "' " + + " is NOT registred."); + } + + // check property is collection type + PropertyDescriptor descriptor = sourceDescriptors.get(propertyName); + Class<?> type = descriptor.getPropertyType(); + if (!Collection.class.isAssignableFrom(type)) { + throw new IllegalStateException( + "source property '" + propertyName + + "' is not a collection type [" + type + "]"); + } + + // can safely add the strategy + model.addCollectionStrategy(propertyName, strategy); + + return this; + } + protected void addProperty0(String sourceProperty, String targetProperty) { // check srcProperty does not exist if (model.containsSourceProperty(sourceProperty)) { throw new IllegalArgumentException("source property '" + - sourceProperty + "' " + " was already registred."); + sourceProperty + "' " + " was already registred."); } // check dstProperty does not exist if (model.containsTargetProperty(targetProperty)) { throw new IllegalArgumentException("destination property '" + - targetProperty + "' " + " was already registred."); + targetProperty + "' " + " was already registred."); } // obtain source descriptor @@ -346,14 +346,14 @@ sourceDescriptors.get(sourceProperty); if (sourceDescriptor == null) { throw new IllegalArgumentException("no property '" + - sourceProperty + "' " + "found on type " + - model.getSourceType()); + sourceProperty + "' " + "found on type " + + model.getSourceType()); } // check srcProperty is readable Method readMethod = sourceDescriptor.getReadMethod(); if (readMethod == null) { throw new IllegalArgumentException("property '" + sourceProperty + - "' " + "is not readable on type " + model.getSourceType()); + "' " + "is not readable on type " + model.getSourceType()); } // obtain dst descriptor @@ -361,25 +361,25 @@ targetDescriptors.get(targetProperty); if (targetDescriptor == null) { throw new IllegalArgumentException("no property '" + - targetProperty + "' " + "found on type " + - model.getTargetType()); + targetProperty + "' " + "found on type " + + model.getTargetType()); } // check dstProperty is writable Method writeMethod = sourceDescriptor.getWriteMethod(); if (writeMethod == null) { throw new IllegalArgumentException("property '" + targetProperty + - "' " + "is not writable on type " + model.getTargetType()); + "' " + "is not writable on type " + model.getTargetType()); } // check types are ok Class<?> sourceType = sourceDescriptor.getPropertyType(); Class<?> targetType = targetDescriptor.getPropertyType(); //TODO-TC20100221 : should check if primitive and boxed it in such case - if (sourceType != targetType) { + if (!sourceType.equals(targetType)) { throw new IllegalArgumentException("source property '" + - sourceProperty + "' and target property '" + - targetProperty + "' are not compatible ( sourceType : " + - sourceType + " vs targetType :" + targetType + ')'); + sourceProperty + "' and target property '" + + targetProperty + "' are not compatible ( sourceType : " + + sourceType + " vs targetType :" + targetType + ')'); } // safe to add the binding @@ -390,7 +390,7 @@ protected void checkModelExists() throws IllegalStateException { if (model == null) { throw new IllegalStateException("there is not model, must " + - "create one with createBinderModel method"); + "create one with createBinderModel method"); } } @@ -412,14 +412,14 @@ } } catch (IntrospectionException e) { throw new RuntimeException("Could not obtain bean properties " + - "descriptors for source type " + type, e); + "descriptors for source type " + type, e); } Class<?>[] interfaces = type.getInterfaces(); for (Class<?> i : interfaces) { loadDescriptors(i, descriptors); } Class<?> superClass = type.getSuperclass(); - if (superClass != null && superClass != Object.class) { + if (superClass != null && !Object.class.equals(superClass)) { loadDescriptors(superClass, descriptors); } } Modified: trunk/src/main/java/org/nuiton/util/beans/BinderModel.java =================================================================== --- trunk/src/main/java/org/nuiton/util/beans/BinderModel.java 2010-05-06 09:36:24 UTC (rev 1839) +++ trunk/src/main/java/org/nuiton/util/beans/BinderModel.java 2010-05-06 09:53:21 UTC (rev 1840) @@ -28,6 +28,7 @@ import java.beans.PropertyDescriptor; import java.io.Serializable; import java.lang.reflect.Method; +import java.util.Collection; import java.util.Map; import java.util.Set; import java.util.TreeMap; @@ -35,7 +36,7 @@ /** * Model of a {@link Binder}. - * + * <p/> * TODO-TC20100225 should have special cases for collections treatment. * * @author tchemit <chemit@codelutin.com> @@ -45,37 +46,36 @@ */ public class BinderModel<S, T> implements Serializable { - /** - * source type - */ + /** source type */ protected final Class<S> sourceType; - /** - * destination type - */ + + /** destination type */ protected final Class<T> targetType; - /** - * source type descriptors (key are property names) - */ + /** source type descriptors (key are property names) */ protected final Map<String, PropertyDescriptor> sourceDescriptors; - /** - * destination descriptors (key are property names) - */ + + /** destination descriptors (key are property names) */ protected final Map<String, PropertyDescriptor> targetDescriptors; + /** * properties mapping (key are source properties, value are destination * properties) */ protected final Map<String, String> propertiesMapping; - private static final long serialVersionUID = 1L; + /** mapping of collection properties strategies */ + protected Map<String, Binder.CollectionStrategy> collectionStrategies; + private static final long serialVersionUID = 2L; + public BinderModel(Class<S> sourceType, Class<T> targetType) { this.sourceType = sourceType; this.targetType = targetType; sourceDescriptors = new TreeMap<String, PropertyDescriptor>(); targetDescriptors = new TreeMap<String, PropertyDescriptor>(); propertiesMapping = new TreeMap<String, String>(); + collectionStrategies = new TreeMap<String, Binder.CollectionStrategy>(); } /** @@ -106,6 +106,10 @@ return universe.toArray(new String[sourceDescriptors.size()]); } + public Binder.CollectionStrategy getCollectionStrategy(String property) { + return collectionStrategies.get(property); + } + /** * Gets all registred property names of the binder's destination type. * @@ -217,6 +221,15 @@ return writeMethod; } + public Class<?> getCollectionType(String sourceProperty) { + Method method = getSourceReadMethod(sourceProperty); + Class<?> type = method.getReturnType(); + if (Collection.class.isAssignableFrom(type)) { + return type; + } + return null; + } + protected boolean containsSourceProperty(String sourceProperty) { return propertiesMapping.containsKey(sourceProperty); } @@ -225,6 +238,15 @@ return propertiesMapping.containsValue(targetProperty); } + protected boolean containsCollectionProperty(String propertyName) { + return collectionStrategies.containsKey(propertyName); + } + + protected void addCollectionStrategy(String propertyName, + Binder.CollectionStrategy strategy) { + collectionStrategies.put(propertyName, strategy); + } + protected void addBinding(PropertyDescriptor sourceDescriptor, PropertyDescriptor targetDescriptor) { Modified: trunk/src/main/java/org/nuiton/util/beans/BinderProvider.java =================================================================== --- trunk/src/main/java/org/nuiton/util/beans/BinderProvider.java 2010-05-06 09:36:24 UTC (rev 1839) +++ trunk/src/main/java/org/nuiton/util/beans/BinderProvider.java 2010-05-06 09:53:21 UTC (rev 1840) @@ -63,15 +63,12 @@ */ public class BinderProvider { - /** - * Logger - */ + /** Logger */ private static final Log log = LogFactory.getLog(BinderProvider.class); - /** - * Cache of registred binders indexed by their unique entry - */ - protected static Map<BinderEntry, Binder> binders; + /** Cache of registred binders indexed by their unique entry */ + protected static Map<BinderEntry, Binder<?, ?>> binders; + /** * Gets the registred mirror binder (source type = target type) with no * context name specified. @@ -175,8 +172,8 @@ * @param binderType the type of binder to instanciate and register. * @see BinderBuilder#createBinder(Class) */ - public static <B extends Binder<?,?>> void registerBinder(BinderBuilder builder, - Class<B> binderType) { + public static <B extends Binder<?, ?>> void registerBinder(BinderBuilder builder, + Class<B> binderType) { registerBinder(builder, binderType, null); } @@ -207,9 +204,9 @@ * @param name the context's name * @see BinderBuilder#createBinder(Class) */ - public static <B extends Binder<?,?>> void registerBinder(BinderBuilder builder, - Class<B> binderType, - String name) { + public static <B extends Binder<?, ?>> void registerBinder(BinderBuilder builder, + Class<B> binderType, + String name) { // instanciate the binder B binder = builder.createBinder(binderType); // register it @@ -235,12 +232,12 @@ if (log.isDebugEnabled()) { log.debug("binder to seek : " + entry); } - Binder<?,?> oldBinder = getBinders().get(entry); + Binder<?, ?> oldBinder = getBinders().get(entry); if (oldBinder != null) { // already a binder for this entry if (log.isWarnEnabled()) { log.warn("Binder already registred for " + entry + " : " + - oldBinder); + oldBinder); log.warn("Will be replace by the new binder " + binder); } } @@ -251,15 +248,15 @@ getBinders().put(entry, binder); } - protected static Map<BinderEntry, Binder> getBinders() { + protected static Map<BinderEntry, Binder<?, ?>> getBinders() { if (binders == null) { - binders = new HashMap<BinderEntry, Binder>(); + binders = new HashMap<BinderEntry, Binder<?, ?>>(); } return binders; } public static void clear() { - if (binders!=null) { + if (binders != null) { binders.clear(); binders = null; } @@ -274,7 +271,9 @@ public static class BinderEntry { protected final Class<?> sourceType; + protected final Class<?> targetType; + protected final String name; @@ -300,12 +299,16 @@ @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } BinderEntry that = (BinderEntry) o; - return name == null ? that.name == null : name.equals(that.name) && + return (name == null ? that.name == null : name.equals(that.name)) && sourceType.equals(that.sourceType) && targetType.equals(that.targetType); }