Skip to content

Commit

Permalink
fixed #114: Complex objects inside maps are ignored by value conversion
Browse files Browse the repository at this point in the history
  • Loading branch information
S1artie committed Jul 8, 2016
1 parent 3bacebb commit c485d71
Show file tree
Hide file tree
Showing 13 changed files with 569 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
package de.gebit.integrity.parameter.conversion;

import java.lang.reflect.Array;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
Expand Down Expand Up @@ -174,8 +175,8 @@ public Object convertValue(Class<?> aTargetType, Class<?> aParameterizedType, Ob
* @throws InstantiationException
*/
public Object convertValue(Class<?> aTargetType, Class<?> aParameterizedType, Object aValue,
ConversionContext aConversionContext, Set<Object> someVisitedObjects) throws UnresolvableVariableException,
UnexecutableException {
ConversionContext aConversionContext, Set<Object> someVisitedObjects)
throws UnresolvableVariableException, UnexecutableException {
ConversionContext tempConversionContext = safeguardConversionContext(aConversionContext);

if (someVisitedObjects.contains(aValue)) {
Expand All @@ -194,8 +195,8 @@ public Object convertValue(Class<?> aTargetType, Class<?> aParameterizedType, Ob
return convertEncapsulatedConstantValueToTargetType(aTargetType, aParameterizedType,
(ConstantValue) aValue, tempConversionContext, someVisitedObjects);
} else {
return convertPlainValueToTargetType(aTargetType, aParameterizedType, aValue,
tempConversionContext, someVisitedObjects);
return convertPlainValueToTargetType(aTargetType, aParameterizedType, aValue, tempConversionContext,
someVisitedObjects);
}
} finally {
someVisitedObjects.remove(aValue);
Expand Down Expand Up @@ -253,20 +254,14 @@ protected Object convertPlainValueToTargetType(Class<?> aTargetType, Class<?> aP
// both are arrays
tempResultArray = Array.newInstance(tempActualParamType, Array.getLength(aValue));
for (int i = 0; i < Array.getLength(aValue); i++) {
Array.set(
tempResultArray,
i,
convertPlainValueToTargetType(tempActualParamType, aParameterizedType,
Array.get(aValue, i), aConversionContext, someVisitedValues));
Array.set(tempResultArray, i, convertPlainValueToTargetType(tempActualParamType, aParameterizedType,
Array.get(aValue, i), aConversionContext, someVisitedValues));
}
} else {
// target is an array, but value is a single value
tempResultArray = Array.newInstance(tempActualParamType, 1);
Array.set(
tempResultArray,
0,
convertPlainValueToTargetType(tempActualParamType, aParameterizedType, aValue,
aConversionContext, someVisitedValues));
Array.set(tempResultArray, 0, convertPlainValueToTargetType(tempActualParamType, aParameterizedType,
aValue, aConversionContext, someVisitedValues));
}
return tempResultArray;
} else {
Expand Down Expand Up @@ -338,18 +333,37 @@ private Object convertSingleValueToTargetType(Class<?> aTargetType, Class<?> aPa
Class<?> tempSourceType = transformPrimitiveTypes(aValue.getClass());
String tempSourceTypeName = tempSourceType.getName();

// No conversion necessary if target type is a superclass or the same as the current type
if (tempTargetType != null && tempTargetType.isAssignableFrom(tempSourceType)) {
// ...except if the source type is one of Integritys' internal types, which shouldn't generally been given
// to fixtures in an unconverted state.
if (!tempSourceTypeName.startsWith("de.gebit.integrity.dsl.")) {
return aValue;
if (Map.class.isAssignableFrom(tempSourceType)) {
// Maps need special attention: we may have to convert their contents. Therefore we perform a Map-to-Map
// conversion in this case, which iterates over inner value and ensures those are also converted, if
// necessary.
// In order to do this, we need to specify a concrete map target type.
if (tempTargetType == null) {
tempTargetType = HashMap.class; // HashMap is a good default
} else if (Map.class.isAssignableFrom(tempTargetType)) {
// If the target type is also a map, see whether the target is an abstract type
if ((tempTargetType.getModifiers() & Modifier.ABSTRACT) != 0) {
// If it is, just use the source value type as target. This basically performs no conversion of the
// map object itself, but it nevertheless results in an execution of the Map-to-Map conversion,
// which triggers conversion of inner values
tempTargetType = tempSourceType;
}
}
} else {
// No conversion necessary if target type is a superclass or the same as the current type
if (tempTargetType != null && tempTargetType.isAssignableFrom(tempSourceType)) {
// ...except if the source type is one of Integritys' internal types, which shouldn't generally been
// given
// to fixtures in an unconverted state.
if (!tempSourceTypeName.startsWith("de.gebit.integrity.dsl.")) {
return aValue;
}
}
}

if (tempTargetType == null && tempSourceTypeName.startsWith("java.")) {
// Java types generally have themselves as "default type" and don't need to be converted to anything
return aValue;
if (tempTargetType == null && tempSourceTypeName.startsWith("java.")) {
// Java types generally have themselves as "default type" and don't need to be converted to anything
return aValue;
}
}

try {
Expand All @@ -372,8 +386,8 @@ private Object convertSingleValueToTargetType(Class<?> aTargetType, Class<?> aPa
throw exc;
// SUPPRESS CHECKSTYLE IllegalCatch
} catch (Throwable exc) {
throw new ConversionFailedException(aValue.getClass(), tempTargetType,
"Unexpected error during conversion", exc);
throw new ConversionFailedException(aValue.getClass(), tempTargetType, "Unexpected error during conversion",
exc);
}
}

Expand Down Expand Up @@ -502,8 +516,8 @@ protected Object convertEncapsulatedConstantValueToTargetType(Class<?> aTargetTy
} else if (((Constant) aValue).getName().eContainer() instanceof ConstantDefinition) {
// Without the variable manager, we can still attempt to resolve statically.
try {
return parameterResolver.resolveStatically((ConstantDefinition) ((Constant) aValue).getName()
.eContainer(), null);
return parameterResolver
.resolveStatically((ConstantDefinition) ((Constant) aValue).getName().eContainer(), null);
} catch (ClassNotFoundException exc) {
exc.printStackTrace();
} catch (InstantiationException exc) {
Expand Down Expand Up @@ -580,8 +594,8 @@ protected Object convertEncapsulatedValueCollectionToTargetType(Class<?> aTarget
// this is actually an array
Object tempResultArray = Array.newInstance(tempTargetArrayType, aCollection.getMoreValues().size() + 1);
for (int i = 0; i < aCollection.getMoreValues().size() + 1; i++) {
ValueOrEnumValueOrOperation tempValue = (i == 0 ? aCollection.getValue() : aCollection.getMoreValues()
.get(i - 1));
ValueOrEnumValueOrOperation tempValue = (i == 0 ? aCollection.getValue()
: aCollection.getMoreValues().get(i - 1));
Object tempResultValue = convertEncapsulatedValueToTargetType(tempTargetType, aParameterizedType,
tempValue, aConversionContext, someVisitedValues);
Array.set(tempResultArray, i, tempResultValue);
Expand Down Expand Up @@ -720,9 +734,8 @@ public FormattedString[] convertValueToStringArray(Object aValue, ConversionCont
FormattedString[] tempResult;
try {
if (aValue instanceof ValueOrEnumValueOrOperationCollection) {
tempResult = (FormattedString[]) convertEncapsulatedValueCollectionToTargetType(
FormattedString[].class, null, (ValueOrEnumValueOrOperationCollection) aValue,
tempConversionContext, someVisitedValues);
tempResult = (FormattedString[]) convertEncapsulatedValueCollectionToTargetType(FormattedString[].class,
null, (ValueOrEnumValueOrOperationCollection) aValue, tempConversionContext, someVisitedValues);
} else if (aValue instanceof ValueOrEnumValueOrOperation) {
tempResult = (FormattedString[]) convertEncapsulatedValueToTargetType(FormattedString[].class, null,
(ValueOrEnumValueOrOperation) aValue, tempConversionContext, someVisitedValues);
Expand Down Expand Up @@ -958,8 +971,8 @@ public String toString() {
* @throws IllegalAccessException
*/
protected Conversion<?, ?> findAndInstantiateConversion(Class<?> aSourceType, Class<?> aTargetType,
Set<Object> someVisitedValues, ConversionContext aConversionContext) throws InstantiationException,
IllegalAccessException {
Set<Object> someVisitedValues, ConversionContext aConversionContext)
throws InstantiationException, IllegalAccessException {
Class<? extends Conversion<?, ?>> tempConversionClass = findConversion(aSourceType, aTargetType,
someVisitedValues, aConversionContext);

Expand Down Expand Up @@ -1034,8 +1047,8 @@ public String toString() {
* @return a conversion class, or null if none was found
*/
protected Class<? extends Conversion<?, ?>> searchDerivedConversionMap(Class<?> aSourceType, Class<?> aTargetType) {
List<Class<? extends Conversion<?, ?>>> tempList = derivedConversions.get(new ConversionKey(aSourceType,
aTargetType));
List<Class<? extends Conversion<?, ?>>> tempList = derivedConversions
.get(new ConversionKey(aSourceType, aTargetType));
if (tempList != null && !tempList.isEmpty()) {
return tempList.get(0);
}
Expand Down Expand Up @@ -1173,8 +1186,8 @@ public String toString() {
* @throws IllegalAccessException
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
protected <C extends Conversion> C createConversionInstance(Class<C> aConversionClass, Set<Object> someVisitedValues)
throws InstantiationException, IllegalAccessException {
protected <C extends Conversion> C createConversionInstance(Class<C> aConversionClass,
Set<Object> someVisitedValues) throws InstantiationException, IllegalAccessException {
if (aConversionClass == null) {
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@
import de.gebit.integrity.parameter.conversion.conversions.java.other.EnumToString;
import de.gebit.integrity.parameter.conversion.conversions.java.other.MapToBean;
import de.gebit.integrity.parameter.conversion.conversions.java.other.MapToFormattedString;
import de.gebit.integrity.parameter.conversion.conversions.java.other.MapToMap;
import de.gebit.integrity.parameter.conversion.conversions.java.other.MapToString;
import de.gebit.integrity.parameter.conversion.conversions.java.other.ObjectToFormattedString;
import de.gebit.integrity.parameter.conversion.conversions.java.other.ObjectToMap;
Expand Down Expand Up @@ -209,6 +210,7 @@ protected void initializeConversions() {
addConversion(MapToBean.class);
addConversion(MapToString.class);
addConversion(MapToFormattedString.class);
addConversion(MapToMap.class);
addConversion(ObjectToMap.class);
addConversion(EnumToString.class);
addConversion(EnumToFormattedString.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*******************************************************************************
* Copyright (c) 2016 Rene Schneider, GEBIT Solutions GmbH and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*******************************************************************************/
package de.gebit.integrity.parameter.conversion.conversions.java.other;

import java.util.Map;
import java.util.Map.Entry;

import de.gebit.integrity.operations.UnexecutableException;
import de.gebit.integrity.parameter.conversion.Conversion;
import de.gebit.integrity.parameter.conversion.ConversionContext;
import de.gebit.integrity.parameter.conversion.ConversionFailedException;
import de.gebit.integrity.utils.ParameterUtil.UnresolvableVariableException;

/**
* A default Integrity conversion.
*
*
* @author Rene Schneider - initial API and implementation
*
*/
@SuppressWarnings("rawtypes")
@de.gebit.integrity.parameter.conversion.Conversion.Priority(Integer.MIN_VALUE)
public class MapToMap extends Conversion<Map, Map> {

@SuppressWarnings("unchecked")
@Override
public Map convert(Map aSource, Class<? extends Map> aTargetType, ConversionContext aConversionContext)
throws ConversionFailedException {

try {
Map tempTargetInstance = aTargetType.newInstance();

Class<?> tempTargetValueType = null;
if (aTargetType.getTypeParameters().length >= 2) {
tempTargetValueType = aTargetType.getTypeParameters()[1].getGenericDeclaration().getComponentType();
}

for (Entry<?, ?> tempEntry : ((Map<?, ?>) aSource).entrySet()) {
// // find setter
// Method tempWriteMethod = new PropertyDescriptor(tempEntry.getKey(), aTargetType).getWriteMethod();
// if (tempWriteMethod == null || tempWriteMethod.getParameterTypes().length != 1) {
// throw new ConversionFailedException(aSource.getClass(), aTargetType,
// "No accessible standards-compliant setter found for '" + tempEntry.getKey() + "'");
// }
//
// aTargetType.getp
// Class<?> tempTargetType = tempWriteMethod.getParameterTypes()[0];
// Class<?> tempParameterizedType = null;
//
// // See whether we can find a generic type parameter for the given target class
// Class<?> tempClassInFocus = aTargetType;
// while (tempClassInFocus != null) {
// try {
// Field tempField = tempClassInFocus.getDeclaredField(tempEntry.getKey());
// Type tempGenericType = tempField.getGenericType();
// if (tempGenericType instanceof ParameterizedType) {
// Type tempInnerType = ((ParameterizedType) tempGenericType).getActualTypeArguments()[0];
// if (tempInnerType instanceof WildcardType) {
// if (((WildcardType) tempInnerType).getUpperBounds() == null) {
// tempParameterizedType = null;
// } else {
// Type tempUpperBound = ((WildcardType) tempInnerType).getUpperBounds()[0];
// if (tempUpperBound instanceof ParameterizedType) {
// tempParameterizedType = (Class<?>) ((ParameterizedType) tempUpperBound)
// .getRawType();
// } else {
// tempParameterizedType = (Class<?>) tempUpperBound;
// }
// }
// // lower bounds not currently supported!
//
// } else {
// if (tempInnerType instanceof ParameterizedType) {
// tempParameterizedType = (Class<?>) ((ParameterizedType) tempInnerType).getRawType();
// } else {
// tempParameterizedType = (Class<?>) tempInnerType;
// }
// }
// }
// break;
// } catch (SecurityException exc) {
// // don't care, just continue
// } catch (NoSuchFieldException exc) {
// // don't care, just continue
// }
// tempClassInFocus = tempClassInFocus.getSuperclass();
// }

Object tempConvertedValue;
// if (tempEntry.getValue() instanceof Map) { // map itself
// // case for only one element within an array type
// Class<?> tempOriginalClass = tempWriteMethod.getParameterTypes()[0];
// Class<?> tempClass = tempOriginalClass;
// if (tempOriginalClass.isArray()) {
// tempClass = tempClass.getComponentType();
// }
//
// tempConvertedValue = convert(((Map) tempEntry.getValue()), tempClass, aConversionContext);
//
// if (tempOriginalClass.isArray()) {
// Object tempCopy = tempConvertedValue;
// tempConvertedValue = Array.newInstance(tempClass, 1);
// Array.set(tempConvertedValue, 0, tempCopy);
// }
// } else { // value
tempConvertedValue = convertValueRecursive(tempTargetValueType, null, tempEntry.getValue(),
aConversionContext);
// }
tempTargetInstance.put(tempEntry.getKey(), tempConvertedValue);
}
return tempTargetInstance;
} catch (

InstantiationException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (IllegalAccessException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (UnresolvableVariableException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (ClassNotFoundException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (UnexecutableException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (IllegalArgumentException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
} catch (SecurityException exc) {
throw new ConversionFailedException(aSource.getClass(), aTargetType, null, exc);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,14 @@ packagedef integrity.fixtures.basic.beans with

testdef createTransientTestBean uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createTransientTestBean
calldef createTransientTestBeanCall uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createTransientTestBean

testdef createMapTestBean uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBean
calldef createMapTestBeanCall uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBean

testdef createMapTestBeanWithMapInMap uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBeanWithMapInMap
calldef createMapTestBeanWithMapInMapCall uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBeanWithMapInMap

testdef createMapTestBeanWithTreeMap uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBeanWithTreeMap
calldef createMapTestBeanWithTreeMapCall uses de.gebit.integrity.tests.fixtures.basic.beans.BeanFixture#createMapTestBeanWithTreeMap

packageend
Loading

0 comments on commit c485d71

Please sign in to comment.