/******************************************************************************* * Copyright (c) 2013 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; 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; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import com.google.inject.Inject; import com.google.inject.Injector; import com.google.inject.Provider; import de.gebit.integrity.dsl.Constant; import de.gebit.integrity.dsl.ConstantDefinition; import de.gebit.integrity.dsl.ConstantValue; import de.gebit.integrity.dsl.CustomOperation; import de.gebit.integrity.dsl.StandardOperation; import de.gebit.integrity.dsl.StringValue; import de.gebit.integrity.dsl.ValueOrEnumValueOrOperation; import de.gebit.integrity.dsl.ValueOrEnumValueOrOperationCollection; import de.gebit.integrity.dsl.Variable; import de.gebit.integrity.operations.UnexecutableException; import de.gebit.integrity.operations.custom.CustomOperationWrapper; import de.gebit.integrity.operations.standard.StandardOperationProcessor; import de.gebit.integrity.parameter.conversion.Conversion.Priority; import de.gebit.integrity.parameter.conversion.conversions.java.identity.ObjectToObject; import de.gebit.integrity.parameter.conversion.conversions.java.other.ObjectToMap; import de.gebit.integrity.parameter.resolving.ParameterResolver; import de.gebit.integrity.parameter.variables.VariableManager; import de.gebit.integrity.string.FormattedString; import de.gebit.integrity.string.FormattedStringElement; import de.gebit.integrity.utils.JavaTypeUtil; import de.gebit.integrity.utils.ParameterUtil.UnresolvableVariableException; import de.gebit.integrity.wrapper.WrapperFactory; /** * Abstract base class for a value converter which uses conversion classes to determine how a given value is to be * converted into a desired form. This modularity makes it easy to extend the converter with additional conversions. * * @author Rene Schneider - initial API and implementation * */ public abstract class AbstractModularValueConverter implements ValueConverter { /** * The parameter resolver. */ @Inject protected ParameterResolver parameterResolver; /** * The variable manager. */ @Inject(optional = true) protected VariableManager variableManager; /** * The wrapper factory. */ @Inject(optional = true) protected WrapperFactory wrapperFactory; /** * The processor for standard operations. */ @Inject protected StandardOperationProcessor standardOperationProcessor; /** * The Guice injector. Required to inject stuff into instances of conversions. */ @Inject protected Injector injector; /** * The conversion context provider. */ @Inject protected Provider<ConversionContext> conversionContextProvider; /** * All known conversions. */ private final Map<ConversionKey, Class<? extends Conversion<?, ?>>> conversions = new HashMap<ConversionKey, Class<? extends Conversion<?, ?>>>(); /** * Conversions derived from the directly added conversions by searching superclasses of the target type. */ private final Map<ConversionKey, List<Class<? extends Conversion<?, ?>>>> derivedConversions = new HashMap<ConversionKey, List<Class<? extends Conversion<?, ?>>>>(); /** * Reverse index of all known conversions. */ private final Map<Class<? extends Conversion<?, ?>>, ConversionKey> conversionToKey = new HashMap<Class<? extends Conversion<?, ?>>, ConversionKey>(); /** * The default conversions for all known source types. These are the conversions with the highest priority from * their respective source types' conversion pool. */ private final Map<Class<?>, Class<? extends Conversion<?, ?>>> defaultConversions = new HashMap<Class<?>, Class<? extends Conversion<?, ?>>>(); /** * The current defaults' priority. Used to fill the {@link #defaultConversions} map. */ private final Map<Class<?>, Integer> conversionPriority = new HashMap<Class<?>, Integer>(); /** * These classes mark endpoints after which further search for conversions isn't practical anymore. */ private final Set<Class<?>> conversionSearchBlockers = new HashSet<Class<?>>(); /** * Implement this method to initialize known conversions. * */ protected abstract void initializeConversions(); /** * Initializes the contents of {@link #conversionSearchBlockers}. */ protected void initializeSearchBlockers() { conversionSearchBlockers.add(java.io.Serializable.class); // conversionSearchBlockers.add(java.lang.Object.class); } /** * Default constructor. Initializes all conversions and search blockers. */ public AbstractModularValueConverter() { initializeConversions(); initializeSearchBlockers(); } @Override public Object convertValue(Class<?> aTargetType, Object aValue, ConversionContext aConversionContext) throws UnresolvableVariableException, UnexecutableException { return convertValue(aTargetType, null, aValue, aConversionContext); } @Override public Object convertValue(Class<?> aTargetType, Class<?> aParameterizedType, Object aValue, ConversionContext aConversionContext) throws UnresolvableVariableException, UnexecutableException { return convertValue(aTargetType, aParameterizedType, aValue, aConversionContext, new HashSet<Object>()); } /** * Extended version of #convertValue(Class, Class, Object, UnresolvableVariableHandling). * * @param aTargetType * @param aParameterizedType * @param aValue * @param aConversionContext * @param someVisitedObjects * @return * @throws UnresolvableVariableException * @throws ClassNotFoundException * @throws UnexecutableException * @throws InstantiationException */ public Object convertValue(Class<?> aTargetType, Class<?> aParameterizedType, Object aValue, ConversionContext aConversionContext, Set<Object> someVisitedObjects) throws UnresolvableVariableException, UnexecutableException { ConversionContext tempConversionContext = safeguardConversionContext(aConversionContext); if (someVisitedObjects.contains(aValue)) { // endless loop protection return null; } else { try { someVisitedObjects.add(aValue); if (aValue instanceof ValueOrEnumValueOrOperationCollection) { return convertEncapsulatedValueCollectionToTargetType(aTargetType, aParameterizedType, (ValueOrEnumValueOrOperationCollection) aValue, tempConversionContext, someVisitedObjects); } else if (aValue instanceof ValueOrEnumValueOrOperation) { return convertEncapsulatedValueToTargetType(aTargetType, aParameterizedType, (ValueOrEnumValueOrOperation) aValue, tempConversionContext, someVisitedObjects); } else if (aValue instanceof ConstantValue) { return convertEncapsulatedConstantValueToTargetType(aTargetType, aParameterizedType, (ConstantValue) aValue, tempConversionContext, someVisitedObjects); } else { return convertPlainValueToTargetType(aTargetType, aParameterizedType, aValue, tempConversionContext, someVisitedObjects); } } finally { someVisitedObjects.remove(aValue); } } } /** * Converts a given plain value (no instance of {@link ValueOrEnumValueOrOperation} or * {@link ValueOrEnumValueOrOperationCollection}) to a given Java type class, if possible. * * @param aTargetType * the target type * @param aParameterizedType * the parameterized (via generics) type attached to the given target type, if applicable - for example * if a conversion to List<Integer> is desired, the target type is List, and the parameterized type is * Integer * @param aValue * the value * @param aConversionContext * defines some parameters controlling how the conversion is done in detail * @return the converted value */ protected Object convertPlainValueToTargetType(Class<?> aTargetType, Class<?> aParameterizedType, Object aValue, ConversionContext aConversionContext, Set<Object> someVisitedValues) { if (aValue == null) { return null; } if (aTargetType != null && aTargetType.isArray()) { if (String.class.isAssignableFrom(aValue.getClass()) || StringValue.class.isAssignableFrom(aValue.getClass())) { // Could be special case of issue #66: single string to byte array // I admit that this stuff is not beautiful, but it works, and byte arrays should be the only special // case of this kind so a more decoupled solution like with the normal conversions seems like a bit of // overkill here. if (aTargetType.getComponentType() == Byte.class) { if (String.class.isAssignableFrom(aValue.getClass())) { return handleConversionOfStringToByteWrapperArray((String) aValue); } else if (StringValue.class.isAssignableFrom(aValue.getClass())) { return handleConversionOfStringValueToByteWrapperArray((StringValue) aValue); } } else if (aTargetType.getComponentType() == byte.class) { if (String.class.isAssignableFrom(aValue.getClass())) { return handleConversionOfStringToByteArray((String) aValue); } else if (StringValue.class.isAssignableFrom(aValue.getClass())) { return handleConversionOfStringValueToByteArray((StringValue) aValue); } } } Class<?> tempActualParamType = aTargetType.getComponentType(); Object tempResultArray; if (aValue.getClass().isArray()) { // 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)); } } 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)); } return tempResultArray; } else { if (aValue.getClass().isArray()) { if (aTargetType != null) { if (Array.getLength(aValue) == 0) { return null; } else if (Array.getLength(aValue) == 1) { return convertSingleValueToTargetType(aTargetType, aParameterizedType, Array.get(aValue, 0), aConversionContext, someVisitedValues); } } else { Class<?> tempCurrentArrayType = transformPrimitiveTypes(aValue.getClass().getComponentType()); Class<?> tempTargetArrayType; if (tempCurrentArrayType == Object.class) { // If it's an object array, guessing by querying the conversions wouldn't lead to meaningful // results. So all that's left for us is to create an object array as target. tempTargetArrayType = Object.class; } else { // The arrays' target type is determined by looking at the conversion being used Class<? extends Conversion<?, ?>> tempConversionClass = findConversion(tempCurrentArrayType, aTargetType, someVisitedValues, aConversionContext); ConversionKey tempKey = conversionToKey.get(tempConversionClass); tempTargetArrayType = tempKey.getTargetType(); } Object tempArray = Array.newInstance(tempTargetArrayType, Array.getLength(aValue)); for (int i = 0; i < Array.getLength(aValue); i++) { Object tempConvertedValue = convertSingleValueToTargetType(aTargetType, aParameterizedType, Array.get(aValue, i), aConversionContext, someVisitedValues); if (!tempTargetArrayType.isAssignableFrom(tempConvertedValue.getClass())) { // Oops - this case is pretty unlikely, but theoretically possible. In this case, the // heuristic approach of guessing a proper target array type above hasn't worked out, since // after conversion, one particular value doesn't fit in the new array. We'll fall back to // an object array in that case. tempTargetArrayType = Object.class; Object tempOldArray = tempArray; tempArray = Array.newInstance(tempTargetArrayType, Array.getLength(aValue)); System.arraycopy(tempOldArray, 0, tempArray, 0, i); } Array.set(tempArray, i, tempConvertedValue); } return tempArray; } // this is not convertible, but since this method does not guarantee any conversion... return aValue; } else { // unresolvable variables can't happen here, since variable values should have gone down the other path return convertSingleValueToTargetType(aTargetType, aParameterizedType, aValue, aConversionContext, someVisitedValues); } } } @SuppressWarnings("unchecked") private Object convertSingleValueToTargetType(Class<?> aTargetType, Class<?> aParameterizedType, Object aValue, ConversionContext aConversionContext, Set<Object> someVisitedValues) { if (aValue == null) { return null; } Class<?> tempTargetType = transformPrimitiveTypes(aTargetType); Class<?> tempSourceType = transformPrimitiveTypes(aValue.getClass()); String tempSourceTypeName = tempSourceType.getName(); 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; } } try { @SuppressWarnings("rawtypes") Conversion tempConversion = findAndInstantiateConversion(tempSourceType, tempTargetType, someVisitedValues, aConversionContext); if (tempConversion != null) { return tempConversion.convert(aValue, tempTargetType, aConversionContext); } throw new ConversionUnsupportedException(aValue.getClass(), aTargetType, "Could not find a matching conversion"); } catch (InstantiationException exc) { throw new ConversionFailedException(aValue.getClass(), tempTargetType, "Failed to instantiate conversion", exc); } catch (IllegalAccessException exc) { throw new ConversionFailedException(aValue.getClass(), tempTargetType, "Failed to instantiate conversion", exc); } catch (ConversionUnsupportedException exc) { throw exc; // SUPPRESS CHECKSTYLE IllegalCatch } catch (Throwable exc) { throw new ConversionFailedException(aValue.getClass(), tempTargetType, "Unexpected error during conversion", exc); } } private Class<?> transformPrimitiveTypes(Class<?> aType) { if (int.class.equals(aType)) { return Integer.class; } else if (long.class.equals(aType)) { return Long.class; } else if (short.class.equals(aType)) { return Short.class; } else if (byte.class.equals(aType)) { return Byte.class; } else if (float.class.equals(aType)) { return Float.class; } else if (double.class.equals(aType)) { return Double.class; } else if (char.class.equals(aType)) { return Character.class; } else if (boolean.class.equals(aType)) { return Boolean.class; } else { return aType; } } /** * Converts a given {@link ValueOrEnumValueOrOperation} to a given Java type class, if possible. * * @param aTargetType * the target type * @param aParameterizedType * the parameterized (via generics) type attached to the given target type, if applicable - for example * if a conversion to List<Integer> is desired, the target type is List, and the parameterized type is * Integer * @param aValue * the value * @param aConversionContext * defines some parameters controlling how the conversion is done in detail * @return the converted value * @throws UnresolvableVariableException * @throws ClassNotFoundException * @throws InstantiationException * @throws UnexecutableException */ protected Object convertEncapsulatedValueToTargetType(Class<?> aTargetType, Class<?> aParameterizedType, ValueOrEnumValueOrOperation aValue, ConversionContext aConversionContext, Set<Object> someVisitedValues) throws UnresolvableVariableException, UnexecutableException { if (aValue == null) { return null; } try { if (aValue instanceof StandardOperation) { Object tempResult = standardOperationProcessor.executeOperation((StandardOperation) aValue); return convertPlainValueToTargetType(aTargetType, aParameterizedType, tempResult, aConversionContext, someVisitedValues); } else if (aValue instanceof CustomOperation) { if (wrapperFactory == null) { // cannot execute operations without the ability to load them return null; } else { CustomOperationWrapper tempWrapper = wrapperFactory .newCustomOperationWrapper((CustomOperation) aValue); Object tempResult = tempWrapper.executeOperation(); return convertPlainValueToTargetType(aTargetType, aParameterizedType, tempResult, aConversionContext, someVisitedValues); } } else if (aValue instanceof Variable) { Object tempResult = parameterResolver.resolveSingleParameterValue(aValue, aConversionContext.getUnresolvableVariableHandlingPolicy()); if (tempResult instanceof ValueOrEnumValueOrOperation) { // In case of an operation inside a variable, we need to recurse (see issue #36) return convertEncapsulatedValueToTargetType(aTargetType, aParameterizedType, (ValueOrEnumValueOrOperation) tempResult, aConversionContext, someVisitedValues); } else if (tempResult instanceof ValueOrEnumValueOrOperationCollection) { // The same is true in case of collections return convertEncapsulatedValueCollectionToTargetType(aTargetType, aParameterizedType, (ValueOrEnumValueOrOperationCollection) tempResult, aConversionContext, someVisitedValues); } else { return convertSingleValueToTargetType(aTargetType, aParameterizedType, tempResult, aConversionContext, someVisitedValues); } } else { return convertPlainValueToTargetType(aTargetType, aParameterizedType, aValue, aConversionContext, someVisitedValues); } } catch (ClassNotFoundException exc) { throw new ConversionFailedException(null, aTargetType, exc.getMessage(), exc); } catch (InstantiationException exc) { throw new ConversionFailedException(null, aTargetType, exc.getMessage(), exc); } } /** * Converts a given {@link ValueOrEnumValueOrOperation} to a given Java type class, if possible. * * @param aTargetType * the target type * @param aParameterizedType * the parameterized (via generics) type attached to the given target type, if applicable - for example * if a conversion to List<Integer> is desired, the target type is List, and the parameterized type is * Integer * @param aValue * the value * @param aConversionContext * defines some parameters controlling how the conversion is done in detail * @return the converted value * @throws UnresolvableVariableException * @throws ClassNotFoundException * @throws InstantiationException * @throws UnexecutableException */ protected Object convertEncapsulatedConstantValueToTargetType(Class<?> aTargetType, Class<?> aParameterizedType, ConstantValue aValue, ConversionContext aConversionContext, Set<Object> someVisitedValues) throws UnresolvableVariableException, UnexecutableException { if (aValue == null) { return null; } if (aValue instanceof Constant) { if (variableManager != null) { // Constants need to be "constantly" defined in the variable manager at runtime, so we can ask it // directly. Object tempResult = variableManager.get(((Constant) aValue).getName()); return convertValue(aTargetType, aParameterizedType, tempResult, aConversionContext, someVisitedValues); } 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); } catch (ClassNotFoundException exc) { exc.printStackTrace(); } catch (InstantiationException exc) { exc.printStackTrace(); } } return null; } else { return convertPlainValueToTargetType(aTargetType, aParameterizedType, aValue, aConversionContext, someVisitedValues); } } /** * Converts a given value collection to a given Java type class, if possible. Will return an array if the collection * contains more than one item. * * @param aTargetType * the target type * @param aParameterizedType * the parameterized (via generics) type attached to the given target type, if applicable - for example * if a conversion to List<Integer> is desired, the target type is List, and the parameterized type is * Integer * @param aCollection * the value collection * @param aConversionContext * defines some parameters controlling how the conversion is done in detail * @return the converted value * @throws UnresolvableVariableException * @throws ClassNotFoundException * @throws UnexecutableException * @throws InstantiationException */ @SuppressWarnings({ "rawtypes" }) protected Object convertEncapsulatedValueCollectionToTargetType(Class<?> aTargetType, Class<?> aParameterizedType, ValueOrEnumValueOrOperationCollection aCollection, ConversionContext aConversionContext, Set<Object> someVisitedValues) throws UnresolvableVariableException, UnexecutableException { Class<?> tempTargetType = null; Class<? extends Collection> tempCollectionType = null; boolean tempWrapResultIntoArray = false; if (aTargetType != null) { if (aTargetType.isArray()) { if ((aTargetType == Byte[].class || aTargetType == byte[].class)) { // This is a special case (issue #66): byte arrays are to be treated as "non-array" targets. tempTargetType = aTargetType; tempWrapResultIntoArray = false; } else { tempTargetType = aTargetType.getComponentType(); tempWrapResultIntoArray = true; } } else if (List.class.isAssignableFrom(aTargetType)) { tempCollectionType = ArrayList.class; } else if (Set.class.isAssignableFrom(aTargetType)) { tempCollectionType = HashSet.class; } else if (Collection.class.isAssignableFrom(aTargetType)) { tempCollectionType = ArrayList.class; } else { tempTargetType = aTargetType; } } // Collections may specify a target type via a generics parameter if (tempCollectionType != null && aParameterizedType != null) { tempTargetType = aParameterizedType; } Class<?> tempTargetArrayType = tempTargetType; if (tempTargetArrayType == null) { tempTargetArrayType = Object.class; } if (aCollection.getMoreValues() != null && aCollection.getMoreValues().size() > 0) { // 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)); Object tempResultValue = convertEncapsulatedValueToTargetType(tempTargetType, aParameterizedType, tempValue, aConversionContext, someVisitedValues); Array.set(tempResultArray, i, tempResultValue); } // now we need to see whether we're even allowed to return an array if (aTargetType == null) { return tempResultArray; } else if (aTargetType.isArray()) { return tempResultArray; } else if (tempCollectionType != null) { return wrapInCollection((Class<? extends Collection>) tempCollectionType, tempResultArray); } else { throw new IllegalArgumentException("Parameter type class " + aTargetType + " is not an array, but more than one value was given for conversion."); } } else { // this is just a single value Object tempResult = convertEncapsulatedValueToTargetType(tempTargetType, aParameterizedType, aCollection.getValue(), aConversionContext, someVisitedValues); // but we might need to return this as an array with one element if (aTargetType == null) { return tempResult; } else if (tempWrapResultIntoArray) { Object tempResultArray = Array.newInstance(tempTargetArrayType, 1); Array.set(tempResultArray, 0, tempResult); return tempResultArray; } else if (tempCollectionType != null) { return wrapInCollection((Class<? extends Collection>) tempCollectionType, tempResult); } else { return tempResult; } } } /** * Wraps a value (or an array of values) in a collection of the given type. * * @param aCollectionType * the collection type * @param anArrayOrSingleType * the array or value to wrap * @return the collection */ @SuppressWarnings({ "unchecked", "rawtypes" }) protected <T extends Collection> T wrapInCollection(Class<T> aCollectionType, Object anArrayOrSingleType) { T tempCollectionInstance; try { tempCollectionInstance = aCollectionType.newInstance(); } catch (IllegalAccessException exc) { throw new RuntimeException("Failed to create collection instance", exc); } catch (InstantiationException exc) { throw new RuntimeException("Failed to create collection instance", exc); } if (anArrayOrSingleType.getClass().isArray()) { for (int i = 0; i < Array.getLength(anArrayOrSingleType); i++) { Collections.addAll(tempCollectionInstance, Array.get(anArrayOrSingleType, i)); } } else { Collections.addAll(tempCollectionInstance, anArrayOrSingleType); } return tempCollectionInstance; } @Override public String convertValueToString(Object aValue, boolean aForceIntermediateMapFlag, ConversionContext aConversionContext) { return convertValueToFormattedString(aValue, aForceIntermediateMapFlag, aConversionContext) .toUnformattedString(); } @Override public FormattedString convertValueToFormattedString(Object aValue, boolean aForceIntermediateMapFlag, ConversionContext aConversionContext) { if (aForceIntermediateMapFlag) { try { Map<?, ?>[] tempIntermediateMap = (Map[]) convertValue(Map[].class, aValue, aConversionContext); return convertValueToFormattedString(tempIntermediateMap, false, aConversionContext); } catch (UnresolvableVariableException exc) { exc.printStackTrace(); return new FormattedString("FAILURE"); } catch (UnexecutableException exc) { exc.printStackTrace(); return new FormattedString("FAILURE"); } catch (ConversionException exc) { exc.printStackTrace(); return new FormattedString("FAILURE"); } } // always convert to an array, so array values will convert fine FormattedString[] tempResult = convertValueToFormattedStringArray(aValue, aConversionContext); FormattedString tempBuffer = new FormattedString(); for (FormattedString tempSingleResult : tempResult) { if (tempBuffer.getElementCount() > 0) { tempBuffer.add(new FormattedStringElement(", ")); } tempBuffer.add(tempSingleResult); } return tempBuffer; } @Override public String[] convertValueToStringArray(Object aValue, ConversionContext aConversionContext) { FormattedString[] tempFormattedStrings = convertValueToStringArray(aValue, aConversionContext, new HashSet<Object>()); String[] tempStrings = new String[tempFormattedStrings.length]; for (int i = 0; i < tempFormattedStrings.length; i++) { tempStrings[i] = tempFormattedStrings[i].toUnformattedString(); } return tempStrings; } @Override public FormattedString[] convertValueToFormattedStringArray(Object aValue, ConversionContext aConversionContext) { return convertValueToStringArray(aValue, aConversionContext, new HashSet<Object>()); } /** * Extended version of {@link #convertValueToStringArray(Object, UnresolvableVariableHandling)}. * * @param aValue * @param aConversionContext * @param someVisitedValues * @return */ public FormattedString[] convertValueToStringArray(Object aValue, ConversionContext aConversionContext, Set<Object> someVisitedValues) { ConversionContext tempConversionContext = safeguardConversionContext(aConversionContext); FormattedString[] tempResult; try { if (aValue instanceof ValueOrEnumValueOrOperationCollection) { 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); } else { tempResult = (FormattedString[]) convertValue(FormattedString[].class, null, aValue, tempConversionContext, someVisitedValues); } } catch (UnexecutableException exc) { // this is expected to happen in some cases during dry run - but not a problem return new FormattedString[] { new FormattedString("???") }; } catch (UnresolvableVariableException exc) { // This is expected to happen - for example in case of operations depending on undefined variables. return new FormattedString[] { new FormattedString("???") }; } catch (ConversionException exc) { exc.printStackTrace(); return new FormattedString[] { new FormattedString("FAILURE") }; } if (tempResult == null) { return new FormattedString[] { new FormattedString("null") }; } else { return tempResult; } } /** * Adds the given conversion class to the map of available conversions. * * @param aConversion * the conversion to add */ protected void addConversion(Class<? extends Conversion<?, ?>> aConversion) { Class<? extends Conversion<?, ?>> tempConversion = (Class<? extends Conversion<?, ?>>) aConversion; ConversionKey tempConversionKey = new ConversionKey(tempConversion); // See whether the new conversion has a higher priority than the current default conversion for the given // source type int tempNewPriority = determineConversionPriority(aConversion); Integer tempCurrentPriority = conversionPriority.get(tempConversionKey.getSourceType()); if (tempCurrentPriority == null || (tempNewPriority > tempCurrentPriority)) { defaultConversions.put(tempConversionKey.getSourceType(), tempConversion); conversionPriority.put(tempConversionKey.getSourceType(), tempNewPriority); } // Now add the conversion itself to the existing conversions... conversions.put(tempConversionKey, tempConversion); conversionToKey.put(tempConversion, tempConversionKey); // ...and then add all derived conversions. Note that duplicates can occur here! for (ConversionKey tempDerivedConversionKey : tempConversionKey.generateDerivedKeys()) { List<Class<? extends Conversion<?, ?>>> tempList = derivedConversions.get(tempDerivedConversionKey); if (tempList == null) { tempList = new ArrayList<Class<? extends Conversion<?, ?>>>(); derivedConversions.put(tempDerivedConversionKey, tempList); } tempList.add(tempConversion); Collections.sort(tempList, new Comparator<Class<? extends Conversion<?, ?>>>() { @Override public int compare(Class<? extends Conversion<?, ?>> aFirstConversion, Class<? extends Conversion<?, ?>> aSecondConversion) { int tempFirstPriority = determineConversionPriority(aFirstConversion); int tempSecondPriority = determineConversionPriority(aSecondConversion); return (tempSecondPriority - tempFirstPriority); } }); } } /** * Determines the priority of the given conversion class. * * @param aConversion * the conversion to introspect * @return the numeric priority */ protected int determineConversionPriority(Class<? extends Conversion<?, ?>> aConversion) { Priority tempPriorityAnnotation = aConversion.getAnnotation(Priority.class); int tempPriority = Integer.MIN_VALUE; if (tempPriorityAnnotation != null) { tempPriority = tempPriorityAnnotation.value(); } return tempPriority; } /** * This class defines a key for efficient searching for conversions in maps. * * * @author Rene Schneider - initial API and implementation * */ protected static class ConversionKey { /** * The source type. */ private Class<?> sourceType; /** * The target type. */ private Class<?> targetType; /** * Internally, a string is used to determine equality and hash code. */ private String internalKey; private void initializeInternalKey(Class<?> aSourceType, Class<?> aTargetType) { sourceType = aSourceType; targetType = aTargetType; internalKey = (aSourceType.getName() + " -> " + aTargetType.getName()); } public Class<?> getSourceType() { return sourceType; } public Class<?> getTargetType() { return targetType; } /** * Creates a new instance. * * @param aSourceType * the source type * @param aTargetType * the target type */ public ConversionKey(Class<?> aSourceType, Class<?> aTargetType) { initializeInternalKey(aSourceType, aTargetType); } /** * Takes a {@link Conversion} implementation and determines the applicable conversion key. * * @param aConversion * the conversion to look at */ public ConversionKey(Class<? extends Conversion<?, ?>> aConversion) { Class<?> tempClass = aConversion; Type tempType = JavaTypeUtil.findGenericInterfaceOrSuperType(tempClass, Conversion.class); if (tempType != null) { // Replacing one of the types (source OR target) in the Conversion superinterface with a variable is // supported. In that case, it is expected that the first generic superinterface in the whole class // hierarchy does define that variable. Class<?> tempSourceType = null; Object tempSourceTypeObj = ((ParameterizedType) tempType).getActualTypeArguments()[0]; if (tempSourceTypeObj instanceof Class) { tempSourceType = (Class<?>) tempSourceTypeObj; } else if (tempSourceTypeObj instanceof TypeVariable) { Type tempSubType = JavaTypeUtil.findGenericInterfaceOrSuperType(tempClass, null); tempSourceType = (Class<?>) ((ParameterizedType) tempSubType).getActualTypeArguments()[0]; } Class<?> tempTargetType = null; Object tempTargetTypeObj = ((ParameterizedType) tempType).getActualTypeArguments()[1]; if (tempTargetTypeObj instanceof Class) { tempTargetType = (Class<?>) tempTargetTypeObj; } else if (tempTargetTypeObj instanceof TypeVariable) { Type tempSubType = JavaTypeUtil.findGenericInterfaceOrSuperType(tempClass, null); tempTargetType = (Class<?>) ((ParameterizedType) tempSubType).getActualTypeArguments()[0]; } initializeInternalKey(tempSourceType, tempTargetType); } else { throw new IllegalArgumentException("Was unable to find valid generic Conversion superinterface"); } } /** * Generates the derived conversion keys from the current key. "Derived keys" means keys which cover the whole * target type superclass hierarchy. * * @return the derived key list */ public List<ConversionKey> generateDerivedKeys() { List<ConversionKey> tempResults = new ArrayList<ConversionKey>(); Class<?> tempTargetTypeInFocus = targetType.getSuperclass(); while (tempTargetTypeInFocus != null) { tempResults.add(new ConversionKey(sourceType, tempTargetTypeInFocus)); for (Class<?> tempTargetInterface : tempTargetTypeInFocus.getInterfaces()) { tempResults.add(new ConversionKey(sourceType, tempTargetInterface)); } tempTargetTypeInFocus = tempTargetTypeInFocus.getSuperclass(); } return tempResults; } @Override public int hashCode() { return internalKey.hashCode(); } @Override public boolean equals(Object anObject) { if (!(anObject instanceof ConversionKey)) { return false; } else { return internalKey.equals(((ConversionKey) anObject).internalKey); } } @Override public String toString() { return internalKey; } } /** * Searches all known conversions for a match which is able to convert a given source type into a given target type. * This instantiates a found conversion class. * * @param aSourceType * the source type * @param aTargetType * the target type * @param someVisitedValues * @param aConversionContext * The conversion context * @return a ready-to-use, instantiated conversion, or null if none was found * @throws InstantiationException * @throws IllegalAccessException */ protected Conversion<?, ?> findAndInstantiateConversion(Class<?> aSourceType, Class<?> aTargetType, Set<Object> someVisitedValues, ConversionContext aConversionContext) throws InstantiationException, IllegalAccessException { Class<? extends Conversion<?, ?>> tempConversionClass = findConversion(aSourceType, aTargetType, someVisitedValues, aConversionContext); return createConversionInstance(tempConversionClass, someVisitedValues); } /** * Searches all known conversions for a match which is able to convert a given source type into a given target type. * Returns the conversion class (must be instantiated before actually converting something). * * @param aSourceType * the source type * @param aTargetType * the target type * @param aConversionContext * The conversion context * @return a conversion class, or null if none was found */ protected Class<? extends Conversion<?, ?>> findConversion(Class<?> aSourceType, Class<?> aTargetType, Set<Object> someVisitedValues, ConversionContext aConversionContext) { Class<? extends Conversion<?, ?>> tempConversion = findConversionRecursive(aSourceType, aTargetType, aConversionContext); if (tempConversion != null) { return tempConversion; } // If nothing found yet, continue search in the derived conversion lists return findDerivedConversionRecursive(aSourceType, aTargetType, aConversionContext); } /** * Searches all known derived conversions for a match which is able to convert a given source type into a subclass * of a given target type. If there are multiple matches (which is likely, if the target type is very generic), the * conversion with the highest priority wins. * * @param aSourceType * the source type * @param aTargetType * the target type * @return a conversion class, or null if none was found */ protected Class<? extends Conversion<?, ?>> findDerivedConversionRecursive(Class<?> aSourceType, Class<?> aTargetType, ConversionContext aConversionContext) { if (aSourceType == null || aTargetType == null) { return null; } Class<? extends Conversion<?, ?>> tempConversion = searchDerivedConversionMap(aSourceType, aTargetType); if (tempConversion != null) { return tempConversion; } for (Class<?> tempSourceInterface : aSourceType.getInterfaces()) { tempConversion = findDerivedConversionRecursive(tempSourceInterface, aTargetType, aConversionContext); if (tempConversion != null) { return tempConversion; } } return findDerivedConversionRecursive(aSourceType.getSuperclass(), aTargetType, aConversionContext); } /** * Searches the derived conversion map for a match. If one or multiple are found, the one with the highest priority * is returned (the lists in the map are pre-sorted that way). * * @param aSourceType * the source type * @param aTargetType * the target type * @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)); if (tempList != null && !tempList.isEmpty()) { return tempList.get(0); } return null; } /** * Searches all known conversions for a match which is able to convert a given source type into a given target type. * Returns the conversion class (must be instantiated before actually converting something). * * @param aSourceType * the source type * @param aTargetType * the target type * @param aConversionContext * The conversion context * @return a conversion class, or null if none was found */ protected Class<? extends Conversion<?, ?>> findConversionRecursive(Class<?> aSourceType, Class<?> aTargetType, ConversionContext aConversionContext) { if (conversionSearchBlockers.contains(aSourceType) || conversionSearchBlockers.contains(aTargetType)) { return null; } Class<?> tempSourceTypeInFocus = aSourceType; while (tempSourceTypeInFocus != null) { Class<? extends Conversion<?, ?>> tempConversionClass = null; if (aTargetType == null || aTargetType == Object.class) { // This is the default target type case tempConversionClass = filterConversionClass(defaultConversions.get(tempSourceTypeInFocus), aSourceType, aTargetType, aConversionContext, true); } else { // We actually have a target type Class<?> tempTargetTypeInFocus = aTargetType; outerLoop: while (tempTargetTypeInFocus != null) { if (conversionSearchBlockers.contains(tempTargetTypeInFocus)) { break; } tempConversionClass = filterConversionClass( conversions.get(new ConversionKey(tempSourceTypeInFocus, tempTargetTypeInFocus)), aSourceType, aTargetType, aConversionContext, false); if (tempConversionClass != null) { break outerLoop; } for (Class<?> tempTargetInterface : tempTargetTypeInFocus.getInterfaces()) { tempConversionClass = findConversionRecursive(tempSourceTypeInFocus, tempTargetInterface, aConversionContext); if (tempConversionClass != null) { break outerLoop; } } tempTargetTypeInFocus = tempTargetTypeInFocus.getSuperclass(); } } if (tempConversionClass != null) { return tempConversionClass; } else { for (Class<?> tempSourceInterface : tempSourceTypeInFocus.getInterfaces()) { if (aTargetType == null || aTargetType == Object.class) { // This is the default target type case Class<? extends Conversion<?, ?>> tempConversion = findConversionRecursive(tempSourceInterface, null, aConversionContext); if (tempConversion != null) { return tempConversion; } } else { // We actually have a target type Class<? extends Conversion<?, ?>> tempConversion = findConversionRecursive(tempSourceInterface, aTargetType, aConversionContext); if (tempConversion != null) { return tempConversion; } // Search the interfaces of the target type as well for (Class<?> tempTargetInterface : aTargetType.getInterfaces()) { tempConversion = findConversionRecursive(tempSourceInterface, tempTargetInterface, aConversionContext); if (tempConversion != null) { return tempConversion; } } } } tempSourceTypeInFocus = tempSourceTypeInFocus.getSuperclass(); } } return null; } /** * This filtering method has the ability to replace a conversion class found by the normal lookup before it's * actually used. This is the point at which special rules (for example dictated by the {@link ConversionContext}) * may be enforced. * * @param aConversionClass * the conversion class in question * @param aSourceType * the source type * @param aTargetType * the target type * @param aConversionContext * the conversion context to use * @param aDefaultConversionFlag * whether this is a default conversion or specifically requested to match the target type * @return the conversion class to use, which may be the input class or any other replacement */ protected Class<? extends Conversion<?, ?>> filterConversionClass( Class<? extends Conversion<?, ?>> aConversionClass, Class<?> aSourceType, Class<?> aTargetType, ConversionContext aConversionContext, boolean aDefaultConversionFlag) { if (aConversionClass != null) { if (aConversionContext.getSkipBeanToMapDefaultConversion()) { if (aDefaultConversionFlag && ObjectToMap.class.isAssignableFrom(aConversionClass)) { return ObjectToObject.class; } } } return aConversionClass; } /** * Creates an instance of the given conversion class. This also injects the Guice dependencies. * * @param aConversionClass * the conversion * @return the new instance * @throws InstantiationException * @throws IllegalAccessException */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected <C extends Conversion> C createConversionInstance(Class<C> aConversionClass, Set<Object> someVisitedValues) throws InstantiationException, IllegalAccessException { if (aConversionClass == null) { return null; } C tempInstance = aConversionClass.newInstance(); injector.injectMembers(tempInstance); tempInstance.setVisitedObjects(someVisitedValues); return tempInstance; } /** * This method creates a default conversion context in case none is provided, and returns the provided context * otherwise. * * @param aContext * the context to safeguard * @return a context (guaranteed not to return null) */ public ConversionContext safeguardConversionContext(ConversionContext aContext) { if (aContext == null) { return conversionContextProvider.get(); } else { return aContext; } } /** * Handles the special case of issue #66: single string to byte array. * * @param aSource * the string to convert * @return the byte array */ protected byte[] handleConversionOfStringToByteArray(String aSource) { return aSource.getBytes(Charset.defaultCharset()); } /** * Handles the special case of issue #66: single string to byte array. * * @param aSource * the string to convert * @return the byte array (using the wrapper type) */ protected Byte[] handleConversionOfStringToByteWrapperArray(String aSource) { byte[] tempArray = handleConversionOfStringToByteArray(aSource); Byte[] tempWrapperArray = new Byte[tempArray.length]; for (int i = 0; i < tempArray.length; i++) { tempWrapperArray[i] = tempArray[i]; } return tempWrapperArray; } /** * Handles the special case of issue #66: single string to byte array. * * @param aSource * the string to convert (wrapper type) * @return the byte array */ protected byte[] handleConversionOfStringValueToByteArray(StringValue aSource) { return handleConversionOfStringToByteArray(aSource.getStringValue()); } /** * Handles the special case of issue #66: single string to byte array. * * @param aSource * the string to convert (wrapper type) * @return the byte array (using the wrapper type) */ protected Byte[] handleConversionOfStringValueToByteWrapperArray(StringValue aSource) { return handleConversionOfStringToByteWrapperArray(aSource.getStringValue()); } }