net.sf.mmm.util.value.impl.ComposedValueConverterImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of mmm-util-value Show documentation
Show all versions of mmm-util-value Show documentation
This project provides common utitlities to for converting values.
The newest version!
/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.util.value.impl;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import net.sf.mmm.util.collection.api.MapFactory;
import net.sf.mmm.util.collection.base.AdvancedClassHierarchyMap;
import net.sf.mmm.util.exception.api.NlsParseException;
import net.sf.mmm.util.exception.api.ValueException;
import net.sf.mmm.util.reflect.api.GenericType;
import net.sf.mmm.util.value.api.ComposedValueConverter;
import net.sf.mmm.util.value.api.ValueConverter;
import net.sf.mmm.util.value.base.AbstractComposedValueConverter;
/**
* This is the implementation of the {@link net.sf.mmm.util.value.api.ComposedValueConverter} interface.
*
* @author Joerg Hohwiller (hohwille at users.sourceforge.net)
* @since 1.0.1
*/
public class ComposedValueConverterImpl extends AbstractComposedValueConverter {
private static final Logger LOG = LoggerFactory.getLogger(ComposedValueConverterImpl.class);
private final TargetClass2ConverterMap targetClass2converterMap;
private final TargetClass2ConverterMap targetArrayClass2converterMap;
private List> converters;
/**
* The constructor.
*/
public ComposedValueConverterImpl() {
super();
this.targetClass2converterMap = new TargetClass2ConverterMap();
this.targetArrayClass2converterMap = new TargetClass2ConverterMap();
}
@Override
protected void doInitialize() {
super.doInitialize();
Objects.requireNonNull(this.converters, "converters");
for (ValueConverter, ?> converter : this.converters) {
if (!(converter instanceof ComposedValueConverter)) {
addConverterInternal(converter);
}
}
}
/**
* This method registers the given {@code converter} to this composed converter.
*
* @param converter is the converter to add.
*/
public void addConverter(ValueConverter, ?> converter) {
getInitializationState().requireNotInitilized();
if (this.converters == null) {
this.converters = new ArrayList<>();
}
this.converters.add(converter);
}
/**
* This method registers the given {@code converter} to this composed converter.
*
* @param converter is the converter to add.
* @return the converter with the same {@link ValueConverter#getSourceType() source-type} and
* {@link ValueConverter#getTargetType() target-type} that has been replaced by {@code converter} or
* {@code null} if no converter has been replaced.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
private ValueConverter, ?> addConverterInternal(ValueConverter, ?> converter) {
getInitializationState().requireNotInitilized();
Class> targetType = converter.getTargetType();
TargetClass2ConverterMap map;
if (targetType.isArray()) {
map = this.targetArrayClass2converterMap;
targetType = targetType.getComponentType();
} else {
map = this.targetClass2converterMap;
}
ComposedTargetTypeConverter targetConverter = map.get(targetType);
if ((targetConverter == null) || !targetType.equals(targetConverter.getTargetType())) {
// if targetConverter is NOT null here, then a ValueConverter for a more
// specific (e.g. String or Integer) type than targetType (e.g. Object or
// Number) is currently registered here and will therefore be replaced by
// the new more general ValueConverter
targetConverter = new ComposedTargetTypeConverter(targetType);
map.put(targetType, targetConverter);
}
return targetConverter.addConverter(converter);
}
/**
* This method injects a {@link List} of {@link ValueConverter}s to {@link #addConverter(ValueConverter) add}.
*
* @param converterList is the list of converters to register.
*/
@Inject
public void setConverters(List> converterList) {
getInitializationState().requireNotInitilized();
this.converters = converterList;
}
/**
* @return the converters
*/
List> getConverters() {
return this.converters;
}
@Override
@SuppressWarnings({ "rawtypes", "unchecked" })
public T convert(Object value, Object valueSource, GenericType targetType) {
if (value == null) {
return null;
}
if (LOG.isTraceEnabled()) {
Class> valueClass = value.getClass();
String valueClassName;
if (valueClass.isArray()) {
valueClassName = valueClass.getComponentType().getName() + "[]";
} else {
valueClassName = value.getClass().getName();
}
LOG.trace("starting conversion of '" + value + "' from '" + valueClassName + "' to '" + targetType + "'");
}
Class> targetClass = targetType.getRetrievalClass();
if (targetClass.isInstance(value)) {
// generic collections or maps might need converting of their items...
boolean conversionRequired = false;
if (value instanceof Collection>) {
Collection> collection = (Collection>) value;
// find first non-null item...
Iterator> iterator = collection.iterator();
while (iterator.hasNext()) {
Object item = iterator.next();
if (item != null) {
GenericType> itemType = getReflectionUtil().createGenericType(item.getClass());
GenericType> componentType = targetType.getComponentType();
if (!componentType.isAssignableFrom(itemType)) {
conversionRequired = true;
}
break;
}
}
} else if (value instanceof Map, ?>) {
conversionRequired = true;
// bug: does not compile if unbound wildcards are used
Map map = (Map) value;
// find first non-null item...
if (map.isEmpty()) {
Iterator iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry entry = iterator.next();
Object item = entry.getValue();
if (item != null) {
Object key = entry.getKey();
GenericType> itemType = getReflectionUtil().createGenericType(item.getClass());
GenericType> componentType = targetType.getComponentType();
if (!componentType.isAssignableFrom(itemType)) {
conversionRequired = true;
}
if (key != null) {
// currently we do not care about the key...
}
break;
}
}
}
}
if (!conversionRequired) {
LOG.trace("Value is already an instance of expected type.");
return (T) value;
}
}
TargetClass2ConverterMap converterMap;
if (targetClass.isArray()) {
converterMap = this.targetArrayClass2converterMap;
targetClass = targetClass.getComponentType();
} else {
converterMap = this.targetClass2converterMap;
if (targetClass.isPrimitive()) {
targetClass = getReflectionUtil().getNonPrimitiveType(targetClass);
}
}
return (T) convertRecursive(value, valueSource, targetType, targetClass, null, converterMap);
}
/**
* This method determines if the given {@code converter} is applicable for the given {@code targetType}.
*
* @see ValueConverter#getTargetType()
*
* @param converter is the {@link ValueConverter} to check.
* @param targetType is the {@link GenericType} to match with {@link ValueConverter#getTargetType()}.
* @return {@code true} if the given {@code converter} is applicable, {@code false} otherwise.
*/
protected boolean isApplicable(ValueConverter, ?> converter, GenericType> targetType) {
Class> expectedTargetClass = targetType.getRetrievalClass();
if (expectedTargetClass.isArray()) {
expectedTargetClass = expectedTargetClass.getComponentType();
}
return isApplicable(converter.getTargetType(), expectedTargetClass);
}
/**
* This method determines if the given {@code converterTargetClass} is applicable for the {@code expectedTargetClass}.
*
* @param converterTargetClass is the {@link ValueConverter#getTargetType() target-class} of the
* {@link ValueConverter} to check.
* @param expectedTargetClass is the target-class to convert to.
* @return {@code true} if the conversion is applicable.
*/
protected boolean isApplicable(Class> converterTargetClass, Class> expectedTargetClass) {
if (converterTargetClass.isAssignableFrom(expectedTargetClass)) {
return true;
} else if (expectedTargetClass.isPrimitive()) {
Class> expectedNonPrimitiveClass = getReflectionUtil().getNonPrimitiveType(expectedTargetClass);
return converterTargetClass.isAssignableFrom(expectedNonPrimitiveClass);
// } else if ((converterTargetClass.isArray()) &&
// (expectedTargetClass.isArray())) {
// return isApplicable(converterTargetClass.getComponentType(),
// expectedTargetClass
// .getComponentType());
}
return false;
}
/**
* This method determines if the given {@code type} is accepted as significant type for registration and lookup of
* {@link ValueConverter}s. E.g. interfaces such as {@link Cloneable} or {@link java.io.Serializable} are not more
* significant than {@link Object} in order to choose the appropriate {@link ValueConverter} and should therefore be
* skipped when the {@link Class}-hierarchy is recursively traversed.
* ATTENTION:
* If this method returns {@code false} the behaviour differs between {@link Class#isInterface() interfaces} and
* regular classes. For an interface the entire traversal of super-interfaces is skipped, while for a regular class,
* just that class is skipped, but {@link Class#getSuperclass() super-classes} are recursively traversed.
*
* @param type is the {@link Class} reflecting the type to check.
* @return {@code true} if the given {@code type} is acceptable, {@code false} if the given {@code type} should be
* ignored.
*/
protected boolean isAccepted(Class> type) {
if (getReflectionUtil().isMarkerInterface(type)) {
return false;
}
if (type == Comparable.class) {
return false;
}
return true;
}
/**
* This method performs the {@link #convert(Object, Object, GenericType) conversion} recursive.
*
* @param value is the value to convert.
* @param valueSource describes the source of the value. This may be the filename where the value was read from, an
* XPath where the value was located in an XML document, etc. It is used in exceptions thrown if something goes
* wrong. This will help to find the problem easier.
* @param targetType is the {@link GenericType} to convert the {@code value} to.
* @param currentTargetClass is the current {@link ValueConverter#getTargetType() target-type} to try.
* @param previousConverter is the converter that has been tried last time without success. It is used to avoid trying
* the same converter again. Will initially be {@code null}.
* @param converterMap is the {@link TargetClass2ConverterMap}.
* @return the converted {@code value} or {@code null} if the conversion is NOT possible. The returned value has to be
* an {@link Class#isInstance(Object) instance} of the given {@code targetType}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object convertRecursive(Object value, Object valueSource, GenericType> targetType, Class> currentTargetClass, ValueConverter previousConverter,
TargetClass2ConverterMap converterMap) {
boolean traceEnabled = LOG.isTraceEnabled();
ValueConverter lastConverter = previousConverter;
Class> currentClass = currentTargetClass;
Object result = null;
try {
while (currentClass != null) {
if (isAccepted(currentClass)) {
if (traceEnabled) {
LOG.trace("searching converter for target-type '" + currentClass + "'");
}
ValueConverter converter = converterMap.get(currentClass);
if ((converter != null) && (converter != lastConverter) && (isApplicable(converter, targetType))) {
if (traceEnabled) {
StringWriter sw = new StringWriter(50);
sw.append("trying converter for target-type '");
sw.append(converter.getTargetType().toString());
sw.append("'");
if (!converter.getTargetType().equals(currentClass)) {
sw.append(" for current-type '");
sw.append(currentClass.toString());
sw.append("'");
}
LOG.trace(sw.toString());
}
result = converter.convert(value, valueSource, targetType);
if (result != null) {
return result;
}
lastConverter = converter;
}
}
for (Class> superInterface : currentClass.getInterfaces()) {
if (isAccepted(superInterface)) {
result = convertRecursive(value, valueSource, targetType, superInterface, lastConverter, converterMap);
if (result != null) {
return result;
}
}
}
if ((currentClass.isInterface()) && (targetType.getRetrievalClass() == currentClass)) {
currentClass = Object.class;
} else {
currentClass = currentClass.getSuperclass();
}
}
} catch (ValueException e) {
throw e;
} catch (RuntimeException e) {
throw new NlsParseException(e, value, targetType, valueSource);
}
return null;
}
/**
* This inner class is a composed converter for all {@link ValueConverter}s with the same
* {@link ValueConverter#getTargetType() target-type}.
*
* @param is the generic {@link #getTargetType() target-type}.
*/
protected class ComposedTargetTypeConverter implements ValueConverter
© 2015 - 2024 Weber Informatics LLC | Privacy Policy