/*******************************************************************************
* 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.conversions.java.other;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.SortedMap;
import java.util.TreeMap;
import de.gebit.integrity.comparator.MapComparisonResult;
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.string.FormatTokenElement;
import de.gebit.integrity.string.FormatTokenElement.FormatTokenType;
import de.gebit.integrity.string.FormattedString;
import de.gebit.integrity.string.FormattedStringElement;
/**
* A default Integrity conversion.
*
* @author Rene Schneider - initial API and implementation
*
* @param <T>
* the target type
*/
@SuppressWarnings("rawtypes")
public abstract class AbstractMapToString<T> extends Conversion<Map, T> {
/**
* This static map stores the nesting depth information for string formatting. It is a map in order to be multi-
* threading-safe, in case someone starts multiple test runner instances in one VM and runs them in parallel.
*/
private static Map<Thread, Integer> nestedObjectDepthMap = new Hashtable<Thread, Integer>();
/**
* The property in the {@link ConversionContext} used to transport the path in the map.
*/
protected static final String MAP_PATH_PROPERTY = "path";
/**
* Converts the provided {@link Map} to a {@link FormattedString}.
*
* @param aSource
* the source value
* @param aConversionContext
* the conversion context
* @return the resulting string
*/
protected FormattedString convertToFormattedString(Map<?, ?> aSource, ConversionContext aConversionContext)
throws ConversionFailedException {
String tempParentMapPath = (String) aConversionContext.getProperty(MAP_PATH_PROPERTY);
FormattedString tempBuffer = new FormattedString("{");
tempBuffer.add(new FormatTokenElement(FormatTokenType.NEWLINE));
Integer tempDepth = nestedObjectDepthMap.get(Thread.currentThread());
if (tempDepth == null) {
tempDepth = 1;
} else {
tempDepth++;
}
nestedObjectDepthMap.put(Thread.currentThread(), tempDepth);
// In order to provide a consistent ordering of map entries in the string, we want to sort the map by natural
// key ordering
SortedMap<?, ?> tempSortedSource = null;
if (aSource instanceof SortedMap) {
// Either our source map is already sorted...
tempSortedSource = (SortedMap) aSource;
} else {
// ...or we need to sort it by creating a TreeMap and filling it
tempSortedSource = new TreeMap<>(aSource);
}
try {
boolean tempFirst = true;
for (Entry<?, ?> tempEntry : ((Map<?, ?>) tempSortedSource).entrySet()) {
String tempCurrentMapPath = (tempParentMapPath != null ? tempParentMapPath + "." : "")
+ tempEntry.getKey();
aConversionContext.withProperty(MAP_PATH_PROPERTY, tempCurrentMapPath);
boolean tempCurrentPathFailed = (aConversionContext
.getComparisonResult() instanceof MapComparisonResult)
&& ((MapComparisonResult) aConversionContext.getComparisonResult()).getFailedPaths()
.contains(tempCurrentMapPath);
FormattedString[] tempConvertedValues = convertValueToFormattedStringArrayRecursive(
tempEntry.getValue(), aConversionContext);
if (!tempFirst) {
tempBuffer.add(new FormatTokenElement(FormatTokenType.NEWLINE, ", "));
}
FormattedString tempInnerBuffer = new FormattedString();
if (tempConvertedValues.length == 1) {
tempInnerBuffer.add(tempConvertedValues[0]);
} else {
for (int i = 0; i < tempConvertedValues.length; i++) {
if (i > 0) {
tempInnerBuffer.add(new FormattedStringElement(", "));
}
tempInnerBuffer.add(tempConvertedValues[i]);
}
}
tempBuffer.addMultiple(new FormatTokenElement(FormatTokenType.TAB), tempDepth);
if (tempCurrentPathFailed) {
tempBuffer.add(new FormatTokenElement(FormatTokenType.UNDERLINE_START));
tempBuffer.add(new FormatTokenElement(FormatTokenType.BOLD_START));
}
tempBuffer.add(tempEntry.getKey() + " = ");
tempBuffer.add(tempInnerBuffer);
if (tempCurrentPathFailed) {
tempBuffer.add(new FormatTokenElement(FormatTokenType.BOLD_END));
tempBuffer.add(new FormatTokenElement(FormatTokenType.UNDERLINE_END));
}
tempFirst = false;
}
} finally {
tempDepth--;
tempBuffer.add(new FormatTokenElement(FormatTokenType.NEWLINE));
tempBuffer.addMultiple(new FormatTokenElement(FormatTokenType.TAB), tempDepth);
tempBuffer.add("}");
if (tempDepth == 0) {
nestedObjectDepthMap.remove(Thread.currentThread());
} else {
nestedObjectDepthMap.put(Thread.currentThread(), tempDepth);
}
}
return tempBuffer;
}
}