/**
* Copyright (C) 2015 Valkyrie RCP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.valkyriercp.util;
import com.google.common.base.Objects;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.*;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import org.valkyriercp.binding.form.FormModel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
/**
* Utility class for dealing with objects.
*
* @author <a href = "mailto:julio.arguello@gmail.com" >Julio Arg??ello (JAF)</a>
*/
public final class ObjectUtils {
/**
* The logger.
*/
private static final Logger LOGGER = LoggerFactory.getLogger(ObjectUtils.class);
/**
* Utility classes should have a private constructor.
*/
private ObjectUtils() {
super();
}
/**
* Gets the value of a given property into a given bean.
*
* @param <Q>
* the bean type.
* @param bean
* the bean itself.
* @param propertyName
* the property name.
*
* @return the property value.
*
* @see #getPropertyValue(Object, String, Class)
*/
public static <Q> Object getPropertyValue(Q bean, String propertyName) {
return ObjectUtils.getPropertyValue(bean, propertyName, Object.class);
}
/**
* Gets the value of a given property into a given bean.
*
* @param <T>
* the property type.
* @param <Q>
* the bean type.
* @param bean
* the bean itself.
* @param propertyName
* the property name.
* @param propertyType
* the property type.
* @return the property value.
*
* @see PropertyAccessorFactory
*/
@SuppressWarnings("unchecked")
public static <T, Q> T getPropertyValue(Q bean, String propertyName, Class<T> propertyType) {
Assert.notNull(bean, "bean");
Assert.notNull(propertyName, "propertyName");
final PropertyAccessor propertyAccessor = PropertyAccessorFactory.forDirectFieldAccess(bean);
try {
Assert.isAssignable(propertyType, propertyAccessor.getPropertyType(propertyName));
} catch (InvalidPropertyException e) {
throw new IllegalStateException("Invalid property \"" + propertyName + "\"", e);
}
return (T) propertyAccessor.getPropertyValue(propertyName);
}
/**
* Makes a shallow copy of the source object into the target one.
* <p>
* This method differs from {@link ReflectionUtils#shallowCopyFieldState(Object, Object)} this doesn't require
* source and target objects to share the same class hierarchy.
*
* @param source
* the source object.
* @param target
* the target object.
*/
public static void shallowCopy(Object source, Object target) {
ObjectUtils.doShallowCopy(source, target, Boolean.TRUE);
}
/**
* Makes a shallow copy of the source object into the target one excluding properties not in
* <code>propertyNames</code>.
* <p>
* This method differs from {@link ReflectionUtils#shallowCopyFieldState(Object, Object)} this doesn't require
* source and target objects to share the same class hierarchy.
*
* @param source
* the source object.
* @param target
* the target object.
* @param propertyNames
* the property names to be processed. Never mind if property names are invalid, in such a case are
* ignored.
*/
public static void shallowCopy(Object source, Object target, final String... propertyNames) {
ObjectUtils.doShallowCopy(source, target, Boolean.FALSE, propertyNames);
}
/**
* Get all declared fields on the leaf class and all superclasses. Leaf class methods are included first.
*
* @param leafClass
* the leaf class.
* @return all declared fields.
*
* @see ReflectionUtils#getAllDeclaredMethods(Class) since is the same approach as this one.
*/
public static Field[] getAllDeclaredFields(Class<?> leafClass) {
Assert.notNull(leafClass, "leafClass");
final List<Field> fields = new ArrayList<Field>(32);
ReflectionUtils.doWithFields(leafClass, new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
fields.add(field);
}
});
return fields.toArray(new Field[fields.size()]);
}
/**
* Method based on
* {@link org.apache.commons.collections.ListUtils#isEqualList(java.util.Collection, java.util.Collection)} rewrote
* for performance reasons.
* <p>
* Basically employs {@link ObjectUtils#equals(Object)} instead of {@link #equals(Object)} since the first
* one checks identity before calling <code>equals</code>.
*
* @param <T>
* the type of the elements in the list.
* @param list1
* the first list, may be null
* @param list2
* the second list, may be null
*
* @return whether the lists are equal by value comparison
*/
public static <T> Boolean isEqualList(List<T> list1, List<T> list2) {
if (list1 == list2) {
return Boolean.TRUE;
} else if ((list1 == null) || (list2 == null) || (list1.size() != list2.size())) {
return Boolean.FALSE;
}
final Iterator<T> itr1 = list1.iterator();
final Iterator<T> itr2 = list2.iterator();
Object obj1 = null;
Object obj2 = null;
while (itr1.hasNext() && itr2.hasNext()) {
obj1 = itr1.next();
obj2 = itr2.next();
if (!(obj1 == null ? obj2 == null : Objects.equal(obj1, obj2))) {
return Boolean.FALSE;
}
}
return !(itr1.hasNext() || itr2.hasNext());
}
/**
* This is a utility method for getting raw objects that may have been proxied. It is intended to be used in cases
* where raw implementations are needed rather than working with interfaces which they implement.
*
* @param bean
* the potential proxy.
* @return the most inner unwrapped bean.
*
* @see #unwrapProxy(Object, Boolean)
* @since 20101223 thanks to <a href="http://jirabluebell.b2b2000.com/browse/BLUE-34">BLUE-34</a>
*/
public static Object unwrapProxy(Object bean) {
return ObjectUtils.unwrapProxy(bean, Boolean.TRUE);
}
/**
* This is a utility method for getting raw objects that may have been proxied. It is intended to be used in cases
* where raw implementations are needed rather than working with interfaces which they implement.
*
* @param bean
* the potential proxy.
* @param recursive
* whether to procceeed recursively through nested proxies.
* @return the unwrapped bean or <code>null</code> if target bean is <code>null</code>. If <code>recursive</code>
* parameter is <code>true</code> then returns the most inner unwrapped bean, otherwise the nearest target
* bean is returned.
*
* Based on this <a href="http://forum.springsource.org/showthread.php?t=60216">Spring forum topic</a>.
* @see Advised
* @since 20101223 thanks to <a href="http://jirabluebell.b2b2000.com/browse/BLUE-34">BLUE-34</a>
*/
public static Object unwrapProxy(Object bean, Boolean recursive) {
Assert.notNull(recursive, "recursive");
Object unwrapped = bean;
// If the given object is a proxy, set the return value as the object being proxied, otherwise return the given
// object
if ((bean != null) && (bean instanceof Advised) && (AopUtils.isAopProxy(bean))) {
final Advised advised = (Advised) bean;
try {
final Object target = advised.getTargetSource().getTarget();
unwrapped = recursive ? ObjectUtils.unwrapProxy(target, recursive) : target;
} catch (Exception e) {
unwrapped = bean;
ObjectUtils.LOGGER.warn("Failure unwrapping \"" + bean + "\".", e);
}
}
return unwrapped;
}
/**
* Makes a shallow copy of the source object into the target one excluding properties not in
* <code>propertyNames</code> unless <code>allProperties</code> is true.
* <p>
* This method differs from {@link ReflectionUtils#shallowCopyFieldState(Object, Object)} this doesn't require
* source and target objects to share the same class hierarchy.
*
* @param source
* the source object.
* @param target
* the target object.
* @param allProperties
* if <code>true</code> then <code>propertyNames</code> will be ignored and all properties processed.
* @param propertyNames
* the property names to be processed. Never mind if property names are invalid, in such a case are
* ignored.
*/
private static void doShallowCopy(Object source, Object target, final Boolean allProperties,
final String... propertyNames) {
Assert.notNull(source, "source");
Assert.notNull(target, "target");
Assert.notNull(allProperties, "allProperties");
Assert.notNull(propertyNames, "propertyNames");
final PropertyAccessor sourceAccessor = PropertyAccessorFactory.forDirectFieldAccess(source);
final PropertyAccessor targetAccessor = PropertyAccessorFactory.forDirectFieldAccess(target);
// Try to copy every property
ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
/**
* {@inheritDoc}
*/
@Override
public void doWith(Field field) { // throws IllegalArgumentException, IllegalAccessException {
final String fieldName = field.getName();
Boolean proceed = !Modifier.isFinal(field.getModifiers());
proceed &= (targetAccessor.isWritableProperty(fieldName));
proceed &= (allProperties | Arrays.asList(propertyNames).contains(fieldName));
if (proceed) {
final Object value = sourceAccessor.getPropertyValue(fieldName);
targetAccessor.setPropertyValue(fieldName, value);
}
}
});
}
/**
* This method tries to map the values of the given object on the valueModels of the formModel. Instead of
* setting the object as a backing object, all valueModels are processed one by one and the corresponding
* property value is fetched from the objectToMap and set on that valueModel. This triggers the usual
* buffering etc. just as if the user entered the values.
*
* @param formModel
* @param objectToMap
*/
public static void mapObjectOnFormModel(FormModel formModel, Object objectToMap)
{
BeanWrapper beanWrapper = new BeanWrapperImpl(objectToMap);
for (String fieldName : (Set<String>) formModel.getFieldNames())
{
try
{
formModel.getValueModel(fieldName).setValue(beanWrapper.getPropertyValue(fieldName));
}
catch (BeansException be)
{
// silently ignoring, just mapping values, so if there's one missing, don't bother
}
}
}
}