/***********************************************************************************
*
* Copyright (c) 2014 Kamil Baczkowicz
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* and Eclipse Distribution License v1.0 which accompany this distribution.
*
* The Eclipse Public License is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* The Eclipse Distribution License is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* Contributors:
*
* Kamil Baczkowicz - initial API and implementation and/or initial documentation
*
*/
package pl.baczkowicz.spy.formatting;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import pl.baczkowicz.spy.common.generated.ConversionFormatterDetails;
import pl.baczkowicz.spy.common.generated.ConversionMethod;
import pl.baczkowicz.spy.common.generated.FormatterDetails;
import pl.baczkowicz.spy.common.generated.FormatterFunction;
import pl.baczkowicz.spy.common.generated.SubstringConversionFormatterDetails;
import pl.baczkowicz.spy.common.generated.SubstringExtractFormatterDetails;
import pl.baczkowicz.spy.common.generated.SubstringFormatterDetails;
import pl.baczkowicz.spy.common.generated.SubstringReplaceFormatterDetails;
import pl.baczkowicz.spy.exceptions.ConversionException;
import pl.baczkowicz.spy.utils.ConversionUtils;
/**
* Formatting-related utils.
*/
public class FormattingUtils
{
public static final String DEFAULT_PREFIX = "default";
public static final String SCRIPT_PREFIX = "script";
/** Diagnostic logger. */
private final static Logger logger = LoggerFactory.getLogger(FormattingUtils.class);
/**
* Formats the given number into one where thousands are separated by a space.
*
* @param number The number to format
* @return Formatted string (e.g. "1 315 124" for 1315124)
*/
public static String formatNumber(final long number)
{
long divided = number;
final StringBuffer sb = new StringBuffer();
while (divided > 1000)
{
long rest = divided % 1000;
sb.insert(0, " " + String.format("%03d", rest));
divided = divided / 1000;
}
long rest = divided % 1000;
sb.insert(0, rest);
return sb.toString();
}
/**
* Formats the given text using the provided formatter.
*
* @param customFormatter The formatter details
* @param text The text to be formatted
*
* @return The formatted text
*/
public static String formatText(final FormatterDetails customFormatter, final String text, final byte[] rawText)
{
logger.trace("Formatting '" + text + "' with " + customFormatter.getName());
String formattedText = text;
for (final FormatterFunction function : customFormatter.getFunction())
{
if (function.getSubstringReplace() != null)
{
formattedText = doSubstringReplacement(function.getSubstringReplace(), formattedText);
}
else if (function.getSubstringExtract() != null)
{
formattedText = doSubstringExtract(function.getSubstringExtract(), formattedText);
}
else if (function.getSubstringConversion() != null)
{
formattedText = doSubstringConversion(function.getSubstringConversion(), formattedText);
}
else if (function.getConversion() != null)
{
// Here we want the raw text, otherwise the encoding might be incorrect
if (customFormatter.getFunction().size() == 1 && rawText != null)
{
formattedText = convertText(function.getConversion().getFormat(), rawText);
}
else
{
formattedText = convertText(function.getConversion().getFormat(), formattedText);
}
}
else if (function.getCharacterReplace() != null)
{
formattedText = replaceCharacters(function.getCharacterReplace().getFormat(), formattedText,
function.getCharacterReplace().getCharacterRangeFrom(), function.getCharacterReplace().getCharacterRangeTo(),
function.getCharacterReplace().getWrapCharacter());
}
logger.trace("After function transformation = '" + formattedText + "'");
}
return formattedText;
}
/**
* Replaces characters using the given conversion method.
*
* @param conversionMethod The conversion method to be used
* @param input The input text
* @param fromCharacter From index
* @param toCharacter To index
* @param wrap Characters to put around the converted text
*
* @return The converted text
*/
public static String replaceCharacters(final ConversionMethod conversionMethod, final String input, final int fromCharacter, final int toCharacter, final String wrap)
{
String convertedText = input;
for (int i = fromCharacter; i <= toCharacter; i++)
{
final String characterToReplace = new String(Character.toChars(i));
if (wrap != null)
{
convertedText = convertedText.replace(
characterToReplace,
wrap + convertText(conversionMethod, characterToReplace) + wrap);
}
else
{
convertedText = convertedText.replace(
characterToReplace,
convertText(conversionMethod, characterToReplace));
}
}
return convertedText;
}
/**
* Extracts a substring from the given text.
*
* @param details Details about what to extract
* @param text The text from which to extract
*
* @return The extracted value
*
* @throws ConversionException Thrown when cannot process the parameters correctly
*/
private static String extractValueForConversion(final SubstringFormatterDetails details, final String text) throws ConversionException
{
final int startTagIndex = text.indexOf(details.getStartTag());
if (startTagIndex != -1)
{
final int endTagIndex = text.indexOf(details.getEndTag(), startTagIndex);
if (endTagIndex != -1)
{
return text.substring(startTagIndex + details.getStartTag().length(), endTagIndex);
}
}
throw new ConversionException("Cannot find tags");
}
/**
* Replaces the given input text (optionally with the configured tags) with the given output text.
*
* @param details The formatter details
* @param text The text to modify
* @param input The input text to replace
* @param output The text to replace the input with
*
* @return The converted text
*/
private static String replaceTextAndTags(final SubstringFormatterDetails details, final String text, final String input, final String output)
{
String convertedText = text;
if (details.isKeepTags())
{
convertedText = convertedText.replace(input, output);
}
else
{
convertedText = convertedText.replace(details.getStartTag() + input + details.getEndTag(), output);
}
return convertedText;
}
/**
* Performs a string conversion for the given text based on the supplied formatter details.
*
* @param details The formatter details
* @param text The text to format
*
* @return The formatted text
*/
private static String doSubstringConversion(final SubstringConversionFormatterDetails details, final String text)
{
String convertedText = text;
try
{
final String input = extractValueForConversion(details, convertedText);
// The actual conversion value
final String output = convertText(details.getFormat(), input);
convertedText = replaceTextAndTags(details, convertedText, input, output);
}
catch (ConversionException e)
{
// Ignore, just use the input text as output
}
return convertedText;
}
/**
* Performs a string replacement for the given text based on the supplied formatter details.
*
* @param details The formatter details
* @param text The text to format
*
* @return The formatted text
*/
private static String doSubstringReplacement(final SubstringReplaceFormatterDetails details, final String text)
{
String convertedText = text;
try
{
final String input = extractValueForConversion(details, convertedText);
// The actual replacement value
final String output = details.getReplaceWith();
convertedText = replaceTextAndTags(details, convertedText, input, output);
}
catch (ConversionException e)
{
// Ignore, just use the input text as output
}
return convertedText;
}
/**
* Performs a string extraction for the given text based on the supplied formatter details.
*
* @param details The formatter details
* @param text The text to format
*
* @return The formatted text
*/
private static String doSubstringExtract(final SubstringExtractFormatterDetails details, final String text)
{
String convertedText = text;
try
{
final String input = extractValueForConversion(details, convertedText);
if (details.isKeepTags())
{
convertedText = details.getStartTag() + input + details.getEndTag();
}
else
{
convertedText = input;
}
}
catch (ConversionException e)
{
// Ignore, just use the input text as output
}
return convertedText;
}
/**
* Converts the given text using the supplied method.
*
* @param method The method to use for conversion
* @param text The text to be converted
*
* @return The converted text
*/
public static String convertText(final ConversionMethod method, final String text)
{
switch (method)
{
case PLAIN:
{
return text;
}
case HEX_ENCODE:
{
return ConversionUtils.stringToHex(text);
}
case HEX_DECODE:
{
return ConversionUtils.hexToStringNoException(text);
}
case BASE_64_ENCODE:
{
return ConversionUtils.stringToBase64(text);
}
case BASE_64_DECODE:
{
return ConversionUtils.base64ToString(text);
}
default:
return text;
}
}
/**
* Converts the given text using the supplied method.
*
* @param method The method to use for conversion
* @param text The text to be converted
*
* @return The converted text
*/
public static String convertText(final ConversionMethod method, final byte[] text)
{
switch (method)
{
case HEX_ENCODE:
{
return new String(Hex.encodeHex(text));
}
case HEX_DECODE:
{
return ConversionUtils.hexToStringNoException(ConversionUtils.arrayToString(text));
}
case BASE_64_ENCODE:
{
return Base64.encodeBase64String(text);
}
case BASE_64_DECODE:
{
return new String(Base64.decodeBase64(text));
}
default:
return ConversionUtils.arrayToString(text);
}
}
/**
* Formats the given text using the supplied format.
*
* @param format The format to use for conversion
* @param text The text to be formatted
*
* @return The formatted text
*/
public static String checkAndFormatText(final FormatterDetails format, final String text)
{
if (format != null)
{
return FormattingUtils.formatText(format, text, null);
}
return text;
}
/**
* Formats the given text using the supplied format.
*
* @param format The format to use for conversion
* @param text The text to be formatted
*
* @return The formatted text
*/
public static String checkAndFormatText(final FormatterDetails format, final byte[] text)
{
if (format != null)
{
return FormattingUtils.formatText(format, ConversionUtils.arrayToString(text), text);
}
return ConversionUtils.arrayToString(text);
}
/**
* Creates a basic formatter function from the supplied conversion method.
*
* @param conversionMethod The conversion method to be used
*
* @return Created formatter function
*/
private static FormatterFunction createBasicFormatterFunction(final ConversionMethod conversionMethod)
{
final FormatterFunction function = new FormatterFunction();
final ConversionFormatterDetails conversionFormatterDetails = new ConversionFormatterDetails();
conversionFormatterDetails.setFormat(conversionMethod);
function.setConversion(conversionFormatterDetails);
return function;
}
/**
* Creates formatter details for the given parameters.
*
* @param id The ID of the formatter
* @param name The name of the formatter
* @param conversionMethod The conversion method
*
* @return FormatterDetails object
*/
public static FormatterDetails createBasicFormatter(final String id, final String name, final String description,
final ConversionMethod conversionMethod)
{
final FormatterDetails formatter = new FormatterDetails();
formatter.setID(id);
formatter.setName(name);
formatter.setDescription(description);
formatter.getFunction().add(createBasicFormatterFunction(conversionMethod));
return formatter;
}
public static boolean isScriptBased(final FormatterDetails formatter)
{
return formatter.getID().startsWith(SCRIPT_PREFIX) || formatter.getID().startsWith(DEFAULT_PREFIX + SCRIPT_PREFIX);
}
public static List<FormatterDetails> createBaseFormatters()
{
final List<FormatterDetails> baseFormatters = new ArrayList<>();
baseFormatters.add(FormattingUtils.createBasicFormatter(DEFAULT_PREFIX,
"Plain", "No formatting - as received.", ConversionMethod.PLAIN));
baseFormatters.add(FormattingUtils.createBasicFormatter(DEFAULT_PREFIX + "-hexDecoder",
"HEX decoder", "Decodes from a HEX string.", ConversionMethod.HEX_DECODE));
baseFormatters.add(FormattingUtils.createBasicFormatter(DEFAULT_PREFIX + "-hexEncoder",
"HEX encoder", "Encodes the given value as a HEX string.", ConversionMethod.HEX_ENCODE));
baseFormatters.add(FormattingUtils.createBasicFormatter(DEFAULT_PREFIX + "-base64Decoder",
"Base64 decoder", "Decodes a Base64 string.", ConversionMethod.BASE_64_DECODE));
baseFormatters.add(FormattingUtils.createBasicFormatter(DEFAULT_PREFIX + "-base64Encoder",
"Base64 encoder", "Encodes the given value to Base64.", ConversionMethod.BASE_64_ENCODE));
return baseFormatters;
}
public static boolean isDefault(final FormatterDetails formatter)
{
return formatter.getID().startsWith(DEFAULT_PREFIX);
}
public static String prettyXml(final Document document, final int indent) throws TransformerException
{
final TransformerFactory transformerFactory = TransformerFactory.newInstance();
final Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", Integer.toString(indent));
final StringWriter writer = new StringWriter();
final Result result = new StreamResult(writer);
final Source source = new DOMSource(document);
transformer.transform(source, result);
return writer.getBuffer().toString();
}
public static String prettyXml(final String xml, final int indent) throws SAXException, IOException, TransformerException, ParserConfigurationException
{
final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
final DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder();
final Document document = documentBuilder.parse(new InputSource(new StringReader(xml)));
return prettyXml(document, indent);
}
}