All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.milyn.util.MultiLineToStringBuilder Maven / Gradle / Ivy

The newest version!
package org.milyn.util;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import java.util.Map.Entry;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang.StringUtils;
import org.milyn.container.ExecutionContext;
import org.milyn.payload.FilterResult;
import org.milyn.payload.FilterSource;

/**
 * Static utility class for generating JSON like multi line Strings
 * from Maps, List, Arrays and the ExecutionContext.
 *
 * These methods do traverse sub Maps/Collections/Arrays. They can handle references
 * to parent nodes or to them selfs.
 *
 * @author maurice_zeijen
 *
 */
public class MultiLineToStringBuilder {

	private static final int SPACES = 3;
	private static final String PARENT_OPEN = "PARENT-";
	private static final String PARENT_CLOSE = "";
	private static final String SPACE = " ";
	private static final String QUOTE = "\"";
	private static final String SQUARE_BRACKET_CLOSE = "]";
	private static final String SQUARE_BRACKET_OPEN = "[";
	private static final String THIS = "THIS";
	private static final String NULL = "NULL";
	private static final String VALUE_KEY_SEPARATOR = " : ";
	private static final String COMMA = ",";
	private static final String CURLY_BRACKET_CLOSE = "}";
	private static final String CURLY_BRACKET_OPEN = "{";
	private static final String NL = System.getProperty("line.separator");
	private static final Pattern NL_PATTERN = Pattern.compile("\r\n|\n|\r");

	private static final List EXECUTION_CONTEXT_FILTER_LIST = new ArrayList();

	static {
		//These keys are exluded for the execution context string
		EXECUTION_CONTEXT_FILTER_LIST.add(FilterResult.CONTEXT_KEY);
		EXECUTION_CONTEXT_FILTER_LIST.add(FilterSource.CONTEXT_KEY);
	}

	private MultiLineToStringBuilder() {
	}

	/**
	 * Creates a multi line JSON like string for the execution context
	 *
	 * @param executionContext The ExecutionContext
	 * @return The String representation
	 */
    public static String toString(ExecutionContext executionContext) {
    	Stack stack = new Stack();
    	stack.push(executionContext);

    	StringBuilder builder = new StringBuilder();

    	builder.append("BeanContext : ");
    	builder.append(toString(executionContext.getBeanContext().getBeanMap(), stack, new ArrayList()));
    	builder.append(NL);
    	builder.append(NL);
    	builder.append("Attributes : ");
    	builder.append(toString(executionContext.getAttributes(), stack, EXECUTION_CONTEXT_FILTER_LIST));

    	return builder.toString();
    }

    /**
     * Creates a multi line JSON like string representation from a Map
     *
     * @param map The Map to create the string from
     * @return The String representation of the Map
     */
    public static String toString(Map map) {
    	return toString(map, Collections.emptyList());
	}

    /**
     * Creates a multi line JSON like string representation from a Map
     *
     * @param map The Map to create the string from
     * @param filterKeyList A list of objects that are ignored when encountered as keys
     * @return The String representation of the Map
     */
    public static String toString(Map map, List filterKeyList) {
		Stack stack = new Stack();
		stack.add(new Object()); // A little hack to make sure that the first level is rendered correctly
    	return toString(map, stack, filterKeyList);
	}

	public static String toString(Collection collection) {
		return toString(collection, Collections.emptyList());
	}

	public static String toString(Collection collection, List filterKeyList) {
		Stack stack = new Stack();
		stack.add(new Object()); // A little hack to make sure that the first level is rendered correctly

		return toString(collection, stack, filterKeyList);
	}

	public static String toString(Object[] array) {
		return toString(array, Collections.emptyList());
	}

	public static String toString(Object[] array, List filterKeyList) {
		Stack stack = new Stack();
		stack.add(new Object()); // A little hack to make sure that the first level is renderd ok

		return toString(array, stack, filterKeyList);
	}

	/**
     * Creates a multi line JSON like string representation from a Map
     *
     * @param map The Map to create the string from
     * @param filterKeyList A list of objects that are ignored when encountered as keys
     * @return The String representation of the Map
     */
	private static String toString(Map map, Stack parentStack, List filterKeys) {
    	StringBuilder builder = new StringBuilder();
    	builder.append(CURLY_BRACKET_OPEN);

    	String indent = StringUtils.repeat(SPACE, parentStack.size()*SPACES);
    	String bracketIndent = StringUtils.repeat(SPACE, (parentStack.size()-1)*SPACES);

    	int i = 0;
    	int size = map.entrySet().size();
    	for(Entry entry : map.entrySet()) {
    		String key = entry.getKey().toString();

    		if(filterKeys.contains(key)) {
    			continue;
    		}

    		builder.append(NL);

    		Object value = entry.getValue();

    		builder.append(indent);
    		builder.append(QUOTE);
    		builder.append(key);
    		builder.append(QUOTE);

    		builder.append(VALUE_KEY_SEPARATOR);
    		if(value == null) {
    			builder.append(NULL);
    		} else {
	    		if(isTraversable(value) && parentStack.contains(value)) {
	    			processParent(parentStack, builder, value);
	    		} else if(value == map){
	    			builder.append(THIS);
	    		} else {
	    			processValue(map, value, key, parentStack, builder, filterKeys);
	    		}
    		}
    		i++;
    		if(i < size) {
    			builder.append(COMMA);
    		}
    	}
    	if(i > 0) {
    		builder.append(NL);
    		builder.append(bracketIndent);
    	}
    	builder.append(CURLY_BRACKET_CLOSE);
    	return builder.toString();
    }



    private static String toString(Collection collection, Stack parentStack, List filterKeys) {
    	StringBuilder builder = new StringBuilder();
    	builder.append(SQUARE_BRACKET_OPEN);

    	String indent = StringUtils.repeat(SPACE, parentStack.size()*SPACES);
    	String bracketIndent = StringUtils.repeat(SPACE, (parentStack.size()-1)*SPACES);

    	int i = 0;
    	int size = collection.size();
    	for(Object value : collection) {
    		builder.append(NL);

    		builder.append(indent);

    		if(value == null) {
    			builder.append(NULL);
    		} else {
	    		if(isTraversable(value) && parentStack.contains(value)) {
	    			processParent(parentStack, builder, value);
	    		} else if(value == collection){
	    			builder.append(THIS);
	    		} else {
	    			processValue(collection, value, null, parentStack, builder, filterKeys);
	    		}
    		}
    		i++;
    		if(i < size) {
    			builder.append(COMMA);
    		}

    	}
    	if(i > 0) {
    		builder.append(NL);
    		builder.append(bracketIndent);
    	}
    	builder.append(SQUARE_BRACKET_CLOSE);
    	return builder.toString();
    }



    private static String toString(Object[] array, Stack parentStack, List filterKeys) {
    	StringBuilder builder = new StringBuilder();
    	builder.append(SQUARE_BRACKET_OPEN);

    	String indent = StringUtils.repeat(SPACE, parentStack.size()*SPACES);
    	String bracketIndent = StringUtils.repeat(SPACE, (parentStack.size()-1)*SPACES);

    	int i = 0;
    	int size = array.length;
    	for(Object value : array) {
    		builder.append(NL);

    		builder.append(indent);

    		if(value == null) {
    			builder.append(NULL);
    		} else {
	    		if(isTraversable(value) && parentStack.contains(value)) {
	    			processParent(parentStack, builder, value);
	    		} else if(value == array){
	    			builder.append(THIS);
	    		} else {
	    			processValue(array, value, null, parentStack, builder, filterKeys);
	    		}
    		}
    		i++;
    		if(i < size) {
    			builder.append(COMMA);
    		}

    	}
    	if(i > 0) {
    		builder.append(NL);
    		builder.append(bracketIndent);
    	}
    	builder.append(SQUARE_BRACKET_CLOSE);
    	return builder.toString();
    }

	private static void processParent(Stack parentStack, StringBuilder builder, Object value) {
		int index = parentStack.indexOf(value);

		builder.append(PARENT_OPEN)
		       .append(parentStack.size() - index)
               .append(PARENT_CLOSE);
	}

	@SuppressWarnings("unchecked")
	private static void processValue(Object current, Object value, String key, Stack parentStack, StringBuilder builder, List filterKeys) {
		if(value instanceof Map) {
			parentStack.push(current);
			builder.append(toString((Map) value, parentStack, filterKeys));
			parentStack.pop();
		} else if(value instanceof Collection) {
			parentStack.push(current);
			builder.append(toString((Collection) value, parentStack, filterKeys));
			parentStack.pop();
		} else if(value.getClass().isArray()) {
			parentStack.push(current);
			builder.append(toString((Object[]) value, parentStack, filterKeys));
			parentStack.pop();
		} else {

			if(value instanceof Number) {
				builder.append(value);
			} else {
				builder.append(QUOTE);
				String valueStr = value.toString();

				Matcher matcher = NL_PATTERN.matcher(valueStr);
				if(matcher.find()) {
					int keyLength = key == null ? 0 : key.length() + VALUE_KEY_SEPARATOR.length();
					String spaces = StringUtils.repeat(SPACE, keyLength + (parentStack.size()*SPACES));

					valueStr = matcher.replaceAll(NL + spaces);
					builder.append(valueStr);
				} else {
					builder.append(valueStr);
				}
				builder.append(QUOTE);
			}
		}
	}

    private static boolean isTraversable(Object obj) {
    	return obj instanceof Collection || obj instanceof Map || obj.getClass().isArray();
    }
}