
org.nuiton.util.beans.Binder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of nuiton-utils Show documentation
Show all versions of nuiton-utils Show documentation
Library of usefull class to be used in any project.
/*
* #%L
* Nuiton Utils
*
* $Id: Binder.java 2360 2012-06-11 10:24:36Z tchemit $
* $HeadURL: http://svn.nuiton.org/svn/nuiton-utils/tags/nuiton-utils-2.6.10/nuiton-utils/src/main/java/org/nuiton/util/beans/Binder.java $
* %%
* Copyright (C) 2004 - 2010 CodeLutin
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Lesser Public License for more details.
*
* You should have received a copy of the GNU General Lesser Public
* License along with this program. If not, see
* .
* #L%
*/
package org.nuiton.util.beans;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuiton.util.ObjectUtil;
import java.beans.PropertyDescriptor;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
/**
* A {@code binder} permits to copy some properties from an object to another
* one.
*
* It is based on a {@link BinderModel} which contains the mapping of properties
* to transfert from the source object to the destination object.
*
* Use the method {@link #copy(Object, Object, String...)} to transfert properties.
*
* Use the method {@link #obtainProperties(Object, String...)} to obtain some
* properties from a given object.
*
* For more informations about how to obtain a binder, see the
* {@link BinderFactory} or the package info javadoc or unit tests...
*
* @param the source bean type
* @param the destination bean type
* @author tchemit
* @see BinderFactory
* @see BinderModelBuilder
* @since 1.1.5
*/
public class Binder implements Serializable {
/** Logger. */
private static final Log log = LogFactory.getLog(Binder.class);
private static final long serialVersionUID = 1L;
/**
* 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 {
@SuppressWarnings({"unchecked"})
@Override
public Object copy(Object readValue) {
if (readValue instanceof Set>) {
return new HashSet((Set>) readValue);
}
// in any other cases, let says this is a ArrayList
if (readValue instanceof Collection>) {
return new ArrayList((Collection>) readValue);
}
return readValue;
}
};
/**
* Copy a given collection.
*
* @param readValue the collection value to copy
* @return the copied collection
*/
public abstract Object copy(Object readValue);
}
/** the model of the binder */
protected BinderModel model;
/**
* Obtains the type of the source bean.
*
* @return the type of the source bean
*/
public Class getSourceType() {
return getModel().getSourceType();
}
/**
* Obtains the type of the target bean.
*
* @return the type of the target bean
*/
public Class getTargetType() {
return getModel().getTargetType();
}
/**
* Obtain from the given object all properties registered in the binder
* model.
*
* @param source the bean to read
* @param propertyNames subset of properties to load
* @param includeNullValues get all the properties and
* values for the given bean. If false, you'll get only the values
* @return the map of properties obtained indexed by their property name,
* or an empty map is the given {@code from} is {@code null}.
* @since 2.3
*/
public Map obtainProperties(I source,
boolean includeNullValues, String... propertyNames) {
if (source == null) {
// special limit case
return Collections.emptyMap();
}
propertyNames = getProperties(propertyNames);
Map result = new TreeMap();
for (String sourceProperty : propertyNames) {
try {
Object read;
Method readMethod = model.getSourceReadMethod(sourceProperty);
read = readMethod.invoke(source);
if (log.isDebugEnabled()) {
log.debug("property " + sourceProperty + ", type : " +
readMethod.getReturnType() + ", value = " + read);
}
if (readMethod.getReturnType().isPrimitive() &&
ObjectUtil.getNullValue(
readMethod.getReturnType()).equals(read)) {
// for primitive type case, force nullity
read = null;
}
if (read != null) {
if (model.containsBinderProperty(sourceProperty)) {
if (model.containsCollectionProperty(sourceProperty)) {
read = bindCollection(sourceProperty, read);
} else {
read = bindProperty(sourceProperty, read);
}
} else if (model.containsCollectionProperty(sourceProperty)) {
// specific collection strategy is set, must use it
read = getCollectionValue(sourceProperty, read);
}
}
boolean include = read != null || includeNullValues;
if (include) {
result.put(sourceProperty, read);
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
}
}
return result;
}
/**
* Obtain from the given object all properties registered in the binder
* model.
*
* Note: If a property's value is null, it will not be injected in
* the result.
*
* @param source the bean to read
* @param propertyNames subset of properties to load
* @return the map of properties obtained indexed by their property name,
* or an empty map is the given {@code from} is {@code null}.
*/
public Map obtainProperties(I source,
String... propertyNames) {
return obtainProperties(source, false, propertyNames);
}
/**
* Copy properties from a source bean to a destination one according to
* the model of the binder. If {@code propertyNames} is defined, only
* those properties will be copied.
*
* Note: If {@code from} object is null, then {@code null} values
* will be set to mapped properties into {@code dst}
*
* @param source the bean to read
* @param target the bean to write
* @param propertyNames optional subset of properties to copy (if none is
* specifed, will use all the properties defined in
* binder)
* @throws NullPointerException if target parameter is {@code null}
*/
public void copy(I source, O target, String... propertyNames) {
copy(source, target, false, propertyNames);
}
/**
* Copy properties from a source bean to a destination one according to
* the model of the binder excluding {@code propertyNames}.
*
* Note: If {@code from} object is null, then {@code null} values
* will be set to mapped properties into {@code dst}.
*
* @param source the bean to read
* @param target the bean to write
* @param propertyNames optional subset of properties to copy (if none is
* specifed, will use all the properties defined in
* binder)
* @throws NullPointerException if target parameter is {@code null}
*/
public void copyExcluding(I source, O target, String... propertyNames) {
copy(source, target, true, propertyNames);
}
/**
* Copy properties from a source bean to a destination one according to
* the model of the binder.
*
* Note: If {@code from} object is null, then {@code null} values
* will be set to mapped properties into {@code dst}.
*
* @param source the bean to read
* @param target the bean to write
* @param excludeProperties true to exclude following {@code propertyNames}
* @param propertyNames optional subset of properties to copy (if none is
* specifed, will use all the properties defined in
* binder)
* @throws NullPointerException if target parameter is {@code null}
* @throws RuntimeException if a property can not be copied to the target object
*/
protected void copy(I source, O target, boolean excludeProperties,
String... propertyNames)
throws RuntimeException {
if (target == null) {
throw new NullPointerException("parameter 'target' can no be null");
}
propertyNames = excludeProperties ?
getAllPropertiesExclude(propertyNames) :
getProperties(propertyNames);
for (String sourceProperty : propertyNames) {
String targetProperty = model.getTargetProperty(sourceProperty);
try {
Object read = null;
Method readMethod = model.getSourceReadMethod(sourceProperty);
if (source != null) {
// obtain value from source
read = readMethod.invoke(source);
}
// obtain acceptable null value (for primitive types, use
// default values).
if (read == null) {
read = ObjectUtil.getNullValue(readMethod.getReturnType());
}
if (log.isDebugEnabled()) {
log.debug("property " + sourceProperty + ", type : " +
readMethod.getReturnType() + ", value = " + read);
}
if (model.containsBinderProperty(sourceProperty)) {
if (model.containsCollectionProperty(sourceProperty)) {
read = bindCollection(sourceProperty, read);
} else {
read = bindProperty(sourceProperty, read);
}
} else if (model.containsCollectionProperty(sourceProperty)) {
// specific collection strategy is set, must use it
read = getCollectionValue(sourceProperty, read);
}
model.getTargetWriteMethod(targetProperty).invoke(target, read);
} catch (Exception e) {
throw new RuntimeException(
"Could not bind property [" +
source.getClass().getName() + ":" +
sourceProperty + "] to [" +
target.getClass().getName() + ":" +
targetProperty + "]", e);
}
}
}
protected Object readProperty(String sourceProperty, Object source,
Method readMethod) {
try {
Object read = null;
if (source != null) {
// obtain value from source
read = readMethod.invoke(source);
}
// obtain acceptable null value (for primitive types, use
// default values).
if (read == null) {
read = ObjectUtil.getNullValue(readMethod.getReturnType());
}
if (log.isDebugEnabled()) {
log.debug("property " + sourceProperty + ", type : " +
readMethod.getReturnType() + ", value = " + read);
}
if (model.containsBinderProperty(sourceProperty)) {
if (model.containsCollectionProperty(sourceProperty)) {
read = bindCollection(sourceProperty, read);
} else {
read = bindProperty(sourceProperty, read);
}
} else if (model.containsCollectionProperty(sourceProperty)) {
// specific collection strategy is set, must use it
read = getCollectionValue(sourceProperty, read);
}
return read;
} catch (Exception e) {
throw new RuntimeException(
"could not read property " + sourceProperty +
" on source " + source);
}
}
protected List diff(I source,
O target,
boolean excludeProperties,
String... propertyNames) {
if (source == null) {
throw new NullPointerException("parameter 'source' can no be null");
}
if (target == null) {
throw new NullPointerException("parameter 'target' can no be null");
}
propertyNames = excludeProperties ?
getAllPropertiesExclude(propertyNames) :
getProperties(propertyNames);
List result = new LinkedList();
for (String sourceProperty : propertyNames) {
Method sourceReadMethod = model.getSourceReadMethod(sourceProperty);
Object sourceRead = readProperty(sourceProperty, source,
sourceReadMethod);
String targetProperty = model.getTargetProperty(sourceProperty);
Method targetReadMethod = model.getTargetReadMethod(targetProperty);
Object targetRead = readProperty(targetProperty, target,
targetReadMethod);
if (ObjectUtils.notEqual(sourceRead, targetRead)) {
PropertyDiff propertyDiff = new PropertyDiff(
sourceProperty,
sourceRead,
targetProperty,
targetRead,
sourceReadMethod.getReturnType()
);
result.add(propertyDiff);
}
}
return result;
}
/**
* Compare two beans property by property according to the model.
*
* List contains one element per property with different values (according
* to the result of an equals() call)
*
* @param source a bean of type I
* @param target a bean of type O
* @return a list with all the properties which values differ in source
* and target. Properties with equal values are not included.
* @since 2.3
*/
public List diff(I source, O target) {
return diff(source, target, false);
}
/**
* Compare two beans property by property according to the model.
*
* List contains one element per property with different values (according
* to the result of an equals() call)
*
* @param source a bean of type I
* @param target a bean of type O
* @param propertyNames property names to exclude from the diff
* @return a list with all the properties which values differ in source
* and target. Properties with equal values and excluded properties
* will not be contained in the result
* @since 2.3
*/
public List diffExcluding(I source,
O target,
String... propertyNames) {
return diff(source, target, true, propertyNames);
}
/**
* Get the model of the binder.
*
* @return the model of the binder
*/
protected BinderModel getModel() {
return model;
}
/**
* Set the model of the binder.
*
* @param model the model of the binder
*/
protected void setModel(BinderModel model) {
this.model = model;
}
/**
* Obtain the properties, if none is given in {@code propertyNames}
* parameter, will use all property names defined in binder's model,
* otherwise, check that all given property names are safe (registred in
* binder's model).
*
* @param propertyNames optional subset of properties to get
* @return the array of property names
*/
protected String[] getProperties(String... propertyNames) {
if (propertyNames.length == 0) {
// use all properties in the binder
propertyNames = model.getSourceDescriptors();
} else {
// 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");
}
}
}
return propertyNames;
}
/**
* Obtains all properties from binder's model except those {@code
* propertyNameExcludes}. Unknown properties will be ignored.
*
* @param propertyNameExcludes name of properties to exclude
* @return the array of property names without those in argument
*/
protected String[] getAllPropertiesExclude(String... propertyNameExcludes) {
List excludes = Arrays.asList(propertyNameExcludes);
List results = new ArrayList();
for (String propertyName : model.getSourceDescriptors()) {
if (!excludes.contains(propertyName)) {
results.add(propertyName);
}
}
return results.toArray(new String[results.size()]);
}
protected Object getCollectionValue(String sourceProperty, Object readValue) {
CollectionStrategy strategy =
model.getCollectionStrategy(sourceProperty);
Object result = strategy.copy(readValue);
return result;
}
protected Object bindProperty(String sourceProperty, Object read) throws IllegalAccessException, InstantiationException {
Binder, ?> binder = model.getBinder(sourceProperty);
Object result = bind(binder, read);
return result;
}
@SuppressWarnings({"unchecked"})
protected Object bindCollection(String sourceProperty, Object read) throws IllegalAccessException, InstantiationException {
if (read == null) {
return null;
}
Binder, ?> binder = model.getBinder(sourceProperty);
Collection result;
if (read instanceof Set>) {
result = new HashSet();
} else {
// in any other cases, let says this is a ArrayList
result = new ArrayList();
}
Collection> collection = (Collection>) read;
for (Object o : collection) {
Object r = bind(binder, o);
result.add(r);
}
return result;
}
@SuppressWarnings({"unchecked"})
protected Object bind(Binder binder, Object read) throws IllegalAccessException, InstantiationException {
Object result = read.getClass().newInstance();
binder.copy(read, result);
return result;
}
/**
* Model of a {@link Binder}.
*
* TODO tchemit 20100225 should have special cases for collections treatment.
*
* @param the source type
* @param the target type
* @author tchemit
* @since 1.1.5
*/
public static class BinderModel implements Serializable {
/** source type */
protected final Class sourceType;
/** destination type */
protected final Class targetType;
/** source type descriptors (key are property names) */
protected final Map sourceDescriptors;
/** destination descriptors (key are property names) */
protected final Map targetDescriptors;
/**
* properties mapping (key are source properties, value are destination
* properties)
*/
protected final Map propertiesMapping;
/** mapping of collection properties strategies */
protected Map collectionStrategies;
/** mapping of extra binders to use to copy properties */
protected Map> binders;
private static final long serialVersionUID = 2L;
public BinderModel(Class sourceType, Class targetType) {
this.sourceType = sourceType;
this.targetType = targetType;
sourceDescriptors = new TreeMap();
targetDescriptors = new TreeMap();
propertiesMapping = new TreeMap();
collectionStrategies = new TreeMap();
binders = new TreeMap>();
}
/**
* Gets the type of the binder's source.
*
* @return the type of the source object in the binder
*/
public Class getSourceType() {
return sourceType;
}
/**
* Gets the type of the binder's destination
*
* @return the type of the destination object in the binder
*/
public Class getTargetType() {
return targetType;
}
/**
* Gets all registred property names of the binder's source type.
*
* @return the array of all source object properties names to bind
*/
public String[] getSourceDescriptors() {
Set universe = sourceDescriptors.keySet();
return universe.toArray(new String[sourceDescriptors.size()]);
}
public CollectionStrategy getCollectionStrategy(String property) {
return collectionStrategies.get(property);
}
/**
* Gets all registred property names of the binder's destination type.
*
* @return the array of all source object properties names to bind
*/
public String[] getTargetDescriptors() {
Set universe = targetDescriptors.keySet();
return universe.toArray(new String[targetDescriptors.size()]);
}
/**
* Gets the destination property name given the
*
* @param sourceProperty the name of the source property to bind
* @return the name of the destination object property to bind, or
* {@code null} if {@code propertySrc} is unknown in the model
*/
public String getTargetProperty(String sourceProperty) {
if (!containsSourceProperty(sourceProperty)) {
return null;
}
String dstProperty = propertiesMapping.get(sourceProperty);
return dstProperty;
}
/**
* Gets the bean descriptor of the source type for the given
* destination property.
*
* @param sourceProperty name of the source type property name
* @return the descriptor or {@code null} if not found.
*/
public PropertyDescriptor getSourceDescriptor(String sourceProperty) {
// check src property is registred
if (!containsSourceProperty(sourceProperty)) {
return null;
}
PropertyDescriptor descriptor = sourceDescriptors.get(sourceProperty);
return descriptor;
}
/**
* @param srcProperty the name of a property of the source object.
* @return the method to read in a source object for the given property.
*/
public Method getSourceReadMethod(String srcProperty) {
PropertyDescriptor descriptor = getSourceDescriptor(srcProperty);
Method readMethod = null;
if (descriptor != null) {
readMethod = descriptor.getReadMethod();
}
return readMethod;
}
/**
* @param sourceProperty the name of a property of the source object.
* @return the method to write in a source object for the given property.
*/
public Method getSourceWriteMethod(String sourceProperty) {
PropertyDescriptor descriptor = getSourceDescriptor(sourceProperty);
Method writeMethod = null;
if (descriptor != null) {
writeMethod = descriptor.getWriteMethod();
}
return writeMethod;
}
/**
* Gets the bean descriptor of the destination type for the given
* destination property.
*
* @param targetProperty name of the destination type property name
* @return the descriptor or {@code null} if not found.
*/
public PropertyDescriptor getTargetDescriptor(String targetProperty) {
// check dst property is registred
if (!containsTargetProperty(targetProperty)) {
return null;
}
PropertyDescriptor descriptor = targetDescriptors.get(targetProperty);
return descriptor;
}
/**
* @param targetProperty the name of a property of the destination object.
* @return the method to read in a destination object for the given
* property.
*/
public Method getTargetReadMethod(String targetProperty) {
PropertyDescriptor descriptor = getTargetDescriptor(targetProperty);
Method readMethod = null;
if (descriptor != null) {
readMethod = descriptor.getReadMethod();
}
return readMethod;
}
/**
* @param targetProperty the name of a property of the destination object.
* @return the method to write in a destination object for the given
* property.
*/
public Method getTargetWriteMethod(String targetProperty) {
PropertyDescriptor descriptor = getTargetDescriptor(targetProperty);
Method writeMethod = null;
if (descriptor != null) {
writeMethod = descriptor.getWriteMethod();
}
return writeMethod;
}
public Class> getCollectionType(String sourceProperty) {
Method method = getSourceReadMethod(sourceProperty);
Class> type = method.getReturnType();
if (Collection.class.isAssignableFrom(type)) {
return type;
}
return null;
}
public void addCollectionStrategy(String propertyName,
CollectionStrategy strategy) {
collectionStrategies.put(propertyName, strategy);
}
public void addBinder(String propertyName, Binder, ?> binder) {
binders.put(propertyName, binder);
}
protected boolean containsSourceProperty(String sourceProperty) {
return propertiesMapping.containsKey(sourceProperty);
}
protected boolean containsTargetProperty(String targetProperty) {
return propertiesMapping.containsValue(targetProperty);
}
protected boolean containsCollectionProperty(String propertyName) {
return collectionStrategies.containsKey(propertyName);
}
protected boolean containsBinderProperty(String propertyName) {
return binders.containsKey(propertyName);
}
protected void addBinding(PropertyDescriptor sourceDescriptor,
PropertyDescriptor targetDescriptor) {
String sourceProperty = sourceDescriptor.getName();
String targetProperty = targetDescriptor.getName();
sourceDescriptors.put(sourceProperty, sourceDescriptor);
targetDescriptors.put(targetProperty, targetDescriptor);
propertiesMapping.put(sourceProperty, targetProperty);
}
protected void removeBinding(String source) {
String target = propertiesMapping.get(source);
sourceDescriptors.remove(source);
targetDescriptors.remove(target);
propertiesMapping.remove(source);
if (containsBinderProperty(source)) {
binders.remove(source);
}
if (containsCollectionProperty(source)) {
collectionStrategies.remove(source);
}
}
protected Map getPropertiesMapping() {
return propertiesMapping;
}
public Binder, ?> getBinder(String sourceProperty) {
return binders.get(sourceProperty);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy