/*******************************************************************************
 * Copyright (c) 2000, 2004 IBM Corporation 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
 *
 * Contributors:
 *     IBM Corporation - initial API and implementation
 *******************************************************************************/
package org.erlide.jinterface.backend.util;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInput;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.UTFDataFormatException;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import org.erlide.jinterface.util.ErlLogger;

import com.ericsson.otp.erlang.OtpErlangAtom;
import com.ericsson.otp.erlang.OtpErlangBinary;
import com.ericsson.otp.erlang.OtpErlangException;
import com.ericsson.otp.erlang.OtpErlangList;
import com.ericsson.otp.erlang.OtpErlangLong;
import com.ericsson.otp.erlang.OtpErlangObject;
import com.ericsson.otp.erlang.OtpErlangRangeException;
import com.ericsson.otp.erlang.OtpErlangString;
import com.ericsson.otp.erlang.OtpErlangTuple;

/**
 * Provides convenient utility methods to other types in this package.
 */
public final class Util {

	public interface Comparable {

		/**
		 * Returns 0 if this and c are equal, >0 if this is greater than c, or
		 * <0 if this is less than c.
		 */
		int compareTo(Comparable c);
	}

	public interface Comparer {

		/**
		 * Returns 0 if a and b are equal, >0 if a is greater than b, or <0 if a
		 * is less than b.
		 */
		int compare(Object a, Object b);
	}

	private static final String ARGUMENTS_DELIMITER = "#"; //$NON-NLS-1$

	/* Bundle containing messages */
	static ResourceBundle bundle;

	private static final String BUNDLE_NAME = "org.erlide.jinterface.backend.util.messages"; //$NON-NLS-1$

	private static final char[] DOUBLE_QUOTES = "''".toCharArray(); //$NON-NLS-1$

	private static final String EMPTY_ARGUMENT = "   "; //$NON-NLS-1$

	// public static final String[] fgEmptyStringArray = new String[0];

	private static final char[] SINGLE_QUOTE = "'".toCharArray(); //$NON-NLS-1$

	static {
		relocalize();
	}

	private Util() {
		// cannot be instantiated
	}

	/**
	 * Lookup the message with the given PLUGIN_ID in this catalog
	 */
	public static String bind(final String id) {
		return bind(id, (String[]) null);
	}

	/**
	 * Lookup the message with the given PLUGIN_ID in this catalog and bind its
	 * substitution locations with the given string.
	 */
	public static String bind(final String id, final String binding) {
		return bind(id, new String[] { binding });
	}

	/**
	 * Lookup the message with the given PLUGIN_ID in this catalog and bind its
	 * substitution locations with the given strings.
	 */
	public static String bind(final String id, final String binding1,
			final String binding2) {
		return bind(id, new String[] { binding1, binding2 });
	}

	/**
	 * Lookup the message with the given PLUGIN_ID in this catalog and bind its
	 * substitution locations with the given string values.
	 */
	public static String bind(final String id, final String[] bindings) {
		if (id == null) {
			return "No message available"; //$NON-NLS-1$
		}
		String message = null;
		try {
			message = bundle.getString(id);
		} catch (final MissingResourceException e) {
			// If we got an exception looking for the message, fail gracefully
			// by just returning
			// the id we were looking for. In most cases this is
			// semi-informative so is not too bad.
			return "Missing message: " + id + " in: " + BUNDLE_NAME; //$NON-NLS-2$ //$NON-NLS-1$
		}
		// for compatibility with MessageFormat which eliminates double quotes
		// in original message
		final char[] messageWithNoDoubleQuotes = CharOperation.replace(message
				.toCharArray(), DOUBLE_QUOTES, SINGLE_QUOTE);

		if (bindings == null) {
			return new String(messageWithNoDoubleQuotes);
		}

		final int length = messageWithNoDoubleQuotes.length;
		int start = 0;
		int end = length;
		final StringBuilder output = new StringBuilder();
		while (true) {
			end = CharOperation.indexOf('{', messageWithNoDoubleQuotes, start);
			if (end > -1) {
				output.append(messageWithNoDoubleQuotes, start, end - start);
				if ((start = CharOperation.indexOf('}',
						messageWithNoDoubleQuotes, end + 1)) > -1) {
					int index = -1;
					final String argId = new String(messageWithNoDoubleQuotes,
							end + 1, start - end - 1);
					try {
						index = Integer.parseInt(argId);
						output.append(bindings[index]);
					} catch (final NumberFormatException nfe) { // could be
						// nested
						// message PLUGIN_ID
						// {compiler.name}
						boolean done = false;
						if (!argId.equals(id)) {
							String argMessage = null;
							try {
								argMessage = bundle.getString(argId);
								output.append(argMessage);
								done = true;
							} catch (final MissingResourceException e) {
								// unable to bind argument, ignore (will leave
								// argument in)
							}
						}
						if (!done) {
							output.append(messageWithNoDoubleQuotes, end + 1,
									start - end);
						}
					} catch (final ArrayIndexOutOfBoundsException e) {
						output
								.append("{missing " + Integer.toString(index) + "}"); //$NON-NLS-2$ //$NON-NLS-1$
					}
					start++;
				} else {
					output.append(messageWithNoDoubleQuotes, end, length);
					break;
				}
			} else {
				output.append(messageWithNoDoubleQuotes, start, length - start);
				break;
			}
		}
		return output.toString();
	}

	/**
	 * Checks the type signature in String sig, starting at start and ending
	 * before end (end is not included). Returns the index of the character
	 * immediately after the signature if valid, or -1 if not valid.
	 */
	/* NOT USED */
	/*
	 * private static int checkTypeSignature(String sig, int start, int end,
	 * boolean allowVoid) { if (start >= end) { return -1; } int i = start; char
	 * c = sig.charAt(i++); int nestingDepth = 0; while (c == '[') {
	 * ++nestingDepth; if (i >= end) { return -1; } c = sig.charAt(i++); }
	 * switch (c) { case 'B': case 'C': case 'D': case 'F': case 'I': case 'J':
	 * case 'S': case 'Z': break; case 'V': if (!allowVoid) { return -1; } //
	 * array of void is not allowed if (nestingDepth != 0) { return -1; } break;
	 * case 'L': final int semicolon = sig.indexOf(';', i); // Must have at
	 * least one character between L and ; if (semicolon <= i || semicolon >=
	 * end) { return -1; } i = semicolon + 1; break; default: return -1; }
	 * return i; }
	 */

	/**
	 * Combines two hash codes to make a new one.
	 */
	public static int combineHashCodes(final int hashCode1, final int hashCode2) {
		return hashCode1 * 17 + hashCode2;
	}

	/**
	 * Compares two byte arrays. Returns <0 if a byte in a is less than the
	 * corresponding byte in b, or if a is shorter, or if a is null. Returns >0
	 * if a byte in a is greater than the corresponding byte in b, or if a is
	 * longer, or if b is null. Returns 0 if they are equal or both null.
	 */
	public static int compare(final byte[] a, final byte[] b) {
		if (a == b) {
			return 0;
		}
		if (a == null) {
			return -1;
		}
		if (b == null) {
			return 1;
		}
		final int len = Math.min(a.length, b.length);
		for (int i = 0; i < len; ++i) {
			final int diff = a[i] - b[i];
			if (diff != 0) {
				return diff;
			}
		}
		if (a.length > len) {
			return 1;
		}
		if (b.length > len) {
			return -1;
		}
		return 0;
	}

	/**
	 * Compares two strings lexicographically. The comparison is based on the
	 * Unicode value of each character in the strings.
	 * 
	 * @return the value <code>0</code> if the str1 is equal to str2; a value
	 *         less than <code>0</code> if str1 is lexicographically less than
	 *         str2; and a value greater than <code>0</code> if str1 is
	 *         lexicographically greater than str2.
	 */
	public static int compare(final char[] str1, final char[] str2) {
		final int len1 = str1.length;
		final int len2 = str2.length;
		int n = Math.min(len1, len2);
		int i = 0;
		while (n-- != 0) {
			final char c1 = str1[i];
			final char c2 = str2[i++];
			if (c1 != c2) {
				return c1 - c2;
			}
		}
		return len1 - len2;
	}

	/**
	 * Concatenate two strings with a char in between.
	 * 
	 * @see #concat(String, String)
	 */
	public static String concat(String s1, final char c, String s2) {
		if (s1 == null) {
			s1 = "null"; //$NON-NLS-1$
		}
		if (s2 == null) {
			s2 = "null"; //$NON-NLS-1$
		}
		final int l1 = s1.length();
		final int l2 = s2.length();
		final char[] buf = new char[l1 + 1 + l2];
		s1.getChars(0, l1, buf, 0);
		buf[l1] = c;
		s2.getChars(0, l2, buf, l1 + 1);
		return new String(buf);
	}

	/**
	 * Concatenate two strings. Much faster than using +, which: - creates a
	 * StringBuilder, - which is synchronized, - of default size, so the
	 * resulting char array is often larger than needed. This implementation
	 * creates an extra char array, since the String constructor copies its
	 * argument, but there's no way around this.
	 */
	public static String concat(String s1, String s2) {
		if (s1 == null) {
			s1 = "null"; //$NON-NLS-1$
		}
		if (s2 == null) {
			s2 = "null"; //$NON-NLS-1$
		}
		final int l1 = s1.length();
		final int l2 = s2.length();
		final char[] buf = new char[l1 + l2];
		s1.getChars(0, l1, buf, 0);
		s2.getChars(0, l2, buf, l1);
		return new String(buf);
	}

	/**
	 * Concatenate three strings.
	 * 
	 * @see #concat(String, String)
	 */
	public static String concat(String s1, String s2, String s3) {
		if (s1 == null) {
			s1 = "null"; //$NON-NLS-1$
		}
		if (s2 == null) {
			s2 = "null"; //$NON-NLS-1$
		}
		if (s3 == null) {
			s3 = "null"; //$NON-NLS-1$
		}
		final int l1 = s1.length();
		final int l2 = s2.length();
		final int l3 = s3.length();
		final char[] buf = new char[l1 + l2 + l3];
		s1.getChars(0, l1, buf, 0);
		s2.getChars(0, l2, buf, l1);
		s3.getChars(0, l3, buf, l1 + l2);
		return new String(buf);
	}

	/**
	 * Converts a type signature from the IBinaryType representation to the DC
	 * representation.
	 */
	public static String convertTypeSignature(final char[] sig) {
		return new String(sig).replace('/', '.');
	}

	/**
	 * Returns true iff str.toLowerCase().endsWith(end.toLowerCase())
	 * implementation is not creating extra strings.
	 */
	public static final boolean endsWithIgnoreCase(final String str,
			final String end) {

		if (str == null && end == null) {
			return true;
		}
		if (str == null) {
			return false;
		}
		if (end == null) {
			return false;
		}
		if (str.equals(end)) {
			return true;
		}

		final int strLength = str.length();
		final int endLength = end.length();

		// return false if the string is smaller than the end.
		if (endLength > strLength) {
			return false;
		}

		// return false if any character of the end are
		// not the same in lower case.
		for (int i = 1; i <= endLength; i++) {
			if (Character.toLowerCase(end.charAt(endLength - i)) != Character
					.toLowerCase(str.charAt(strLength - i))) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Compares two arrays using equals() on the elements. Either or both arrays
	 * may be null. Returns true if both are null. Returns false if only one is
	 * null. If both are arrays, returns true iff they have the same length and
	 * all elements are equal.
	 */
	public static boolean equalArraysOrNull(final int[] a, final int[] b) {
		if (a == b) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}
		final int len = a.length;
		if (len != b.length) {
			return false;
		}
		for (int i = 0; i < len; ++i) {
			if (a[i] != b[i]) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Compares two arrays using equals() on the elements. Either or both arrays
	 * may be null. Returns true if both are null. Returns false if only one is
	 * null. If both are arrays, returns true iff they have the same length and
	 * all elements compare true with equals.
	 */
	public static boolean equalArraysOrNull(final Object[] a, final Object[] b) {
		if (a == b) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}

		final int len = a.length;
		if (len != b.length) {
			return false;
		}
		for (int i = 0; i < len; ++i) {
			if (a[i] == null) {
				if (b[i] != null) {
					return false;
				}
			} else {
				if (!a[i].equals(b[i])) {
					return false;
				}
			}
		}
		return true;
	}

	/**
	 * Compares two arrays using equals() on the elements. The arrays are first
	 * sorted. Either or both arrays may be null. Returns true if both are null.
	 * Returns false if only one is null. If both are arrays, returns true iff
	 * they have the same length and iff, after sorting both arrays, all
	 * elements compare true with equals. The original arrays are left
	 * untouched.
	 */
	public static boolean equalArraysOrNullSortFirst(Comparable[] a,
			Comparable[] b) {
		if (a == b) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}
		final int len = a.length;
		if (len != b.length) {
			return false;
		}
		if (len >= 2) { // only need to sort if more than two items
			a = sortCopy(a);
			b = sortCopy(b);
		}
		for (int i = 0; i < len; ++i) {
			if (!a[i].equals(b[i])) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Compares two String arrays using equals() on the elements. The arrays are
	 * first sorted. Either or both arrays may be null. Returns true if both are
	 * null. Returns false if only one is null. If both are arrays, returns true
	 * iff they have the same length and iff, after sorting both arrays, all
	 * elements compare true with equals. The original arrays are left
	 * untouched.
	 */
	public static boolean equalArraysOrNullSortFirst(String[] a, String[] b) {
		if (a == b) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}
		final int len = a.length;
		if (len != b.length) {
			return false;
		}
		if (len >= 2) { // only need to sort if more than two items
			a = sortCopy(a);
			b = sortCopy(b);
		}
		for (int i = 0; i < len; ++i) {
			if (!a[i].equals(b[i])) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Compares two objects using equals(). Either or both array may be null.
	 * Returns true if both are null. Returns false if only one is null.
	 * Otherwise, return the result of comparing with equals().
	 */
	public static boolean equalOrNull(final Object a, final Object b) {
		if (a == b) {
			return true;
		}
		if (a == null || b == null) {
			return false;
		}
		return a.equals(b);
	}

	/**
	 * Given a qualified name, extract the last component. If the input is not
	 * qualified, the same string is answered.
	 */
	public static String extractLastName(final String qualifiedName) {
		final int i = qualifiedName.lastIndexOf('.');
		if (i == -1) {
			return qualifiedName;
		}
		return qualifiedName.substring(i + 1);
	}

	/**
	 * Finds the first line separator used by the given text.
	 * 
	 * @return</code> "\n"</code> or</code> "\r"</code> or</code> "\r\n"
	 *                </code>, or <code>null</code> if none found
	 */
	public static String findLineSeparator(final char[] text) {
		// find the first line separator
		final int length = text.length;
		if (length > 0) {
			char nextChar = text[0];
			for (int i = 0; i < length; i++) {
				final char currentChar = nextChar;
				nextChar = i < length - 1 ? text[i + 1] : ' ';
				if (currentChar == '\n') {
					return "\n"; //$NON-NLS-1$
				}
				if (currentChar == '\r') {
					return nextChar == '\n' ? "\r\n" : "\r"; //$NON-NLS-1$ //$NON-NLS-2$
				}

			}
		}
		// not found
		return null;
	}

	// /**
	// * Returns the line separator used by the given buffer. Uses the given
	// text
	// * if none found.
	// *
	// * @return</code> "\n"</code> or</code> "\r"</code> or</code> "\r\n"
	// * </code>
	// */
	// private static String getLineSeparator(char[] text, char[] buffer) {
	// // search in this buffer's contents first
	// String lineSeparator = findLineSeparator(buffer);
	// if (lineSeparator == null) {
	// // search in the given text
	// lineSeparator = findLineSeparator(text);
	// if (lineSeparator == null) {
	// // default to system line separator
	// return "\n";
	// }
	// }
	// return lineSeparator;
	// }

	/**
	 * Returns the number of parameter types in a method signature.
	 */
	public static int getParameterCount(final char[] sig) {
		int i = CharOperation.indexOf('(', sig) + 1;
		Assert.isTrue(i != 0);
		int count = 0;
		final int len = sig.length;
		for (;;) {
			if (i == len) {
				break;
			}
			final char c = sig[i];
			if (c == ')') {
				break;
			}
			if (c == '[') {
				++i;
			} else if (c == 'L') {
				++count;
				i = CharOperation.indexOf(';', sig, i + 1) + 1;
				Assert.isTrue(i != 0);
			} else {
				++count;
				++i;
			}
		}
		return count;
	}

	/**
	 * Put all the arguments in one String.
	 */
	public static String getProblemArgumentsForMarker(final String[] arguments) {
		final StringBuilder args = new StringBuilder(10);

		args.append(arguments.length);
		args.append(':');

		for (int j = 0; j < arguments.length; j++) {
			if (j != 0) {
				args.append(ARGUMENTS_DELIMITER);
			}

			if (arguments[j].length() == 0) {
				args.append(EMPTY_ARGUMENT);
			} else {
				args.append(arguments[j]);
			}
		}

		return args.toString();
	}

	/**
	 * Separate all the arguments of a String made by
	 * getProblemArgumentsForMarker
	 */
	public static String[] getProblemArgumentsFromMarker(String argumentsString) {
		if (argumentsString == null) {
			return null;
		}
		final int index = argumentsString.indexOf(':');
		if (index == -1) {
			return null;
		}

		final int length = argumentsString.length();
		int numberOfArg;
		try {
			numberOfArg = Integer.parseInt(argumentsString.substring(0, index));
		} catch (final NumberFormatException e) {
			return null;
		}
		argumentsString = argumentsString.substring(index + 1, length);

		String[] args = new String[length];
		int count = 0;

		final StringTokenizer tokenizer = new StringTokenizer(argumentsString,
				ARGUMENTS_DELIMITER);
		while (tokenizer.hasMoreTokens()) {
			String argument = tokenizer.nextToken();
			if (EMPTY_ARGUMENT.equals(argument)) {
				argument = ""; //$NON-NLS-1$
			}
			args[count++] = argument;
		}

		if (count != numberOfArg) {
			return null;
		}

		System.arraycopy(args, 0, args = new String[count], 0, count);
		return args;
	}

	/**
	 * Validate the given compilation unit name. A compilation unit name must
	 * obey the following rules:
	 * <ul>
	 * <li>it must not be null
	 * <li>it must include the <code>".erl"</code> suffix
	 * <li>its prefix must be a valid identifier
	 * </ul>
	 * </p>
	 * 
	 * @param name
	 *            the name of a compilation unit
	 * @return a boolean
	 */
	public static boolean isValidModuleFileName(final String name) {
		if (!name.endsWith(".erl")) {
			return false;
		}
		final int pos = name.lastIndexOf('.');
		return isValidModuleName(name.substring(0, pos));
	}

	public static boolean isValidModuleName(final String name) {
		return name.matches("[a-z][a-zA-Z0-9_]*");
	}

	// /**
	// * Normalizes the cariage returns in the given text. They are all changed
	// to
	// * use the given buffer's line separator.
	// */
	// public static char[] normalizeCRs(char[] text, char[] buffer) {
	// final CharArrayBuffer result = new CharArrayBuffer();
	// int lineStart = 0;
	// final int length = text.length;
	// if (length == 0) {
	// return text;
	// }
	// final String lineSeparator = getLineSeparator(text, buffer);
	// char nextChar = text[0];
	// for (int i = 0; i < length; i++) {
	// final char currentChar = nextChar;
	// nextChar = (i < length - 1) ? text[i + 1] : ' ';
	// switch (currentChar) {
	// case '\n':
	// int lineLength = i - lineStart;
	// char[] line = new char[lineLength];
	// System.arraycopy(text, lineStart, line, 0, lineLength);
	// result.append(line);
	// result.append(lineSeparator);
	// lineStart = i + 1;
	// break;
	// case '\r':
	// lineLength = i - lineStart;
	// if (lineLength >= 0) {
	// line = new char[lineLength];
	// System.arraycopy(text, lineStart, line, 0, lineLength);
	// result.append(line);
	// result.append(lineSeparator);
	// if (nextChar == '\n') {
	// nextChar = ' ';
	// lineStart = i + 2;
	// } else {
	// // when line separator are mixed in the same file
	// // \r might not be followed by a \n. If not, we
	// // should increment
	// // lineStart by one and not by two.
	// lineStart = i + 1;
	// }
	// } else {
	// // when line separator are mixed in the same file
	// // we need to prevent NegativeArraySizeException
	// lineStart = i + 1;
	// }
	// break;
	// }
	// }
	// char[] lastLine;
	// if (lineStart > 0) {
	// final int lastLineLength = length - lineStart;
	// if (lastLineLength > 0) {
	// lastLine = new char[lastLineLength];
	// System.arraycopy(text, lineStart, lastLine, 0, lastLineLength);
	// result.append(lastLine);
	// }
	// return result.getContents();
	// }
	// return text;
	// }
	//
	// /**
	// * Normalizes the cariage returns in the given text. They are all changed
	// to
	// * use given buffer's line sepatator.
	// */
	// public static String normalizeCRs(String text, String buffer) {
	// return new String(
	// normalizeCRs(text.toCharArray(), buffer.toCharArray()));
	// }

	/**
	 * Returns the length of the common prefix between s1 and s2.
	 */
	public static int prefixLength(final char[] s1, final char[] s2) {
		int len = 0;
		final int max = Math.min(s1.length, s2.length);
		for (int i = 0; i < max && s1[i] == s2[i]; ++i) {
			++len;
		}
		return len;
	}

	/**
	 * Returns the length of the common prefix between s1 and s2.
	 */
	public static int prefixLength(final String s1, final String s2) {
		int len = 0;
		final int max = Math.min(s1.length(), s2.length());
		for (int i = 0; i < max && s1.charAt(i) == s2.charAt(i); ++i) {
			++len;
		}
		return len;
	}

	private static void quickSort(final char[][] list, int left, int right) {
		final int original_left = left;
		final int original_right = right;
		final char[] mid = list[left / 2 + right / 2];
		do {
			while (compare(list[left], mid) < 0) {
				left++;
			}
			while (compare(mid, list[right]) < 0) {
				right--;
			}
			if (left <= right) {
				final char[] tmp = list[left];
				list[left] = list[right];
				list[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(list, original_left, right);
		}
		if (left < original_right) {
			quickSort(list, left, original_right);
		}
	}

	/**
	 * Sort the comparable objects in the given collection.
	 */
	private static void quickSort(final Comparable[] sortedCollection,
			int left, int right) {
		final int original_left = left;
		final int original_right = right;
		final Comparable mid = sortedCollection[(left + right) >>> 1];
		do {
			while (sortedCollection[left].compareTo(mid) < 0) {
				left++;
			}
			while (mid.compareTo(sortedCollection[right]) < 0) {
				right--;
			}
			if (left <= right) {
				final Comparable tmp = sortedCollection[left];
				sortedCollection[left] = sortedCollection[right];
				sortedCollection[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(sortedCollection, original_left, right);
		}
		if (left < original_right) {
			quickSort(sortedCollection, left, original_right);
		}
	}

	private static void quickSort(final int[] list, int left, int right) {
		final int original_left = left;
		final int original_right = right;
		final int mid = list[(left + right) >>> 1];
		do {
			while (list[left] < mid) {
				left++;
			}
			while (mid < list[right]) {
				right--;
			}
			if (left <= right) {
				final int tmp = list[left];
				list[left] = list[right];
				list[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(list, original_left, right);
		}
		if (left < original_right) {
			quickSort(list, left, original_right);
		}
	}

	/**
	 * Sort the objects in the given collection using the given comparer.
	 */
	private static void quickSort(final Object[] sortedCollection, int left,
			int right, final Comparer comparer) {
		final int original_left = left;
		final int original_right = right;
		final Object mid = sortedCollection[(left + right) >>> 1];
		do {
			while (comparer.compare(sortedCollection[left], mid) < 0) {
				left++;
			}
			while (comparer.compare(mid, sortedCollection[right]) < 0) {
				right--;
			}
			if (left <= right) {
				final Object tmp = sortedCollection[left];
				sortedCollection[left] = sortedCollection[right];
				sortedCollection[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(sortedCollection, original_left, right, comparer);
		}
		if (left < original_right) {
			quickSort(sortedCollection, left, original_right, comparer);
		}
	}

	/**
	 * Sort the objects in the given collection using the given sort order.
	 */
	private static void quickSort(final Object[] sortedCollection, int left,
			int right, final int[] sortOrder) {
		final int original_left = left;
		final int original_right = right;
		final int mid = sortOrder[left / 2 + right / 2];
		do {
			while (sortOrder[left] < mid) {
				left++;
			}
			while (mid < sortOrder[right]) {
				right--;
			}
			if (left <= right) {
				final Object tmp = sortedCollection[left];
				sortedCollection[left] = sortedCollection[right];
				sortedCollection[right] = tmp;
				final int tmp2 = sortOrder[left];
				sortOrder[left] = sortOrder[right];
				sortOrder[right] = tmp2;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(sortedCollection, original_left, right, sortOrder);
		}
		if (left < original_right) {
			quickSort(sortedCollection, left, original_right, sortOrder);
		}
	}

	/**
	 * Sort the strings in the given collection.
	 */
	private static void quickSort(final String[] sortedCollection, int left,
			int right) {
		final int original_left = left;
		final int original_right = right;
		final String mid = sortedCollection[(left + right) >>> 1];
		do {
			while (sortedCollection[left].compareTo(mid) < 0) {
				left++;
			}
			while (mid.compareTo(sortedCollection[right]) < 0) {
				right--;
			}
			if (left <= right) {
				final String tmp = sortedCollection[left];
				sortedCollection[left] = sortedCollection[right];
				sortedCollection[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSort(sortedCollection, original_left, right);
		}
		if (left < original_right) {
			quickSort(sortedCollection, left, original_right);
		}
	}

	/**
	 * Sort the strings in the given collection in reverse alphabetical order.
	 */
	private static void quickSortReverse(final String[] sortedCollection,
			int left, int right) {
		final int original_left = left;
		final int original_right = right;
		final String mid = sortedCollection[(left + right) >>> 1];
		do {
			while (sortedCollection[left].compareTo(mid) > 0) {
				left++;
			}
			while (mid.compareTo(sortedCollection[right]) > 0) {
				right--;
			}
			if (left <= right) {
				final String tmp = sortedCollection[left];
				sortedCollection[left] = sortedCollection[right];
				sortedCollection[right] = tmp;
				left++;
				right--;
			}
		} while (left <= right);
		if (original_left < right) {
			quickSortReverse(sortedCollection, original_left, right);
		}
		if (left < original_right) {
			quickSortReverse(sortedCollection, left, original_right);
		}
	}

	/**
	 * Reads in a string from the specified data input stream. The string has
	 * been encoded using a modified UTF-8 format.
	 * <p>
	 * The first two bytes are read as if by <code>readUnsignedShort</code>.
	 * This value gives the number of following bytes that are in the encoded
	 * string, not the length of the resulting string. The following bytes are
	 * then interpreted as bytes encoding characters in the UTF-8 format and are
	 * converted into characters.
	 * <p>
	 * This method blocks until all the bytes are read, the end of the stream is
	 * detected, or an exception is thrown.
	 * 
	 * @param in
	 *            a data input stream.
	 * @return a Unicode string.
	 * @exception EOFException
	 *                if the input stream reaches the end before all the bytes.
	 * @exception IOException
	 *                if an I/O error occurs.
	 * @exception UTFDataFormatException
	 *                if the bytes do not represent a valid UTF-8 encoding of a
	 *                Unicode string.
	 * @see java.io.DataInputStream#readUnsignedShort()
	 */
	static final char[] readUTF(final DataInput in) throws IOException {
		final int utflen = in.readUnsignedShort();
		char[] str = new char[utflen];
		int count = 0;
		int strlen = 0;
		while (count < utflen) {
			final int c = in.readUnsignedByte();
			int char2, char3;
			switch (c >> 4) {
			case 0:
			case 1:
			case 2:
			case 3:
			case 4:
			case 5:
			case 6:
			case 7:
				// 0xxxxxxx
				count++;
				str[strlen++] = (char) c;
				break;
			case 12:
			case 13:
				// 110x xxxx 10xx xxxx
				count += 2;
				if (count > utflen) {
					throw new UTFDataFormatException();
				}
				char2 = in.readUnsignedByte();
				if ((char2 & 0xC0) != 0x80) {
					throw new UTFDataFormatException();
				}
				str[strlen++] = (char) ((c & 0x1F) << 6 | char2 & 0x3F);
				break;
			case 14:
				// 1110 xxxx 10xx xxxx 10xx xxxx
				count += 3;
				if (count > utflen) {
					throw new UTFDataFormatException();
				}
				char2 = in.readUnsignedByte();
				char3 = in.readUnsignedByte();
				if ((char2 & 0xC0) != 0x80 || (char3 & 0xC0) != 0x80) {
					throw new UTFDataFormatException();
				}
				str[strlen++] = (char) ((c & 0x0F) << 12 | (char2 & 0x3F) << 6 | (char3 & 0x3F) << 0);
				break;
			default:
				// 10xx xxxx, 1111 xxxx
				throw new UTFDataFormatException();
			}
		}
		if (strlen < utflen) {
			System.arraycopy(str, 0, str = new char[strlen], 0, strlen);
		}
		return str;
	}

	/**
	 * Creates a NLS catalog for the given locale.
	 */
	public static void relocalize() {
		try {
			bundle = ResourceBundle.getBundle(BUNDLE_NAME, Locale.getDefault());
		} catch (final MissingResourceException e) {
			System.out
					.println("Missing resource : " + BUNDLE_NAME.replace('.', '/') + ".properties for locale " + Locale.getDefault()); //$NON-NLS-1$//$NON-NLS-2$
			throw e;
		}
	}

	public static void sort(final char[][] list) {
		if (list.length > 1) {
			quickSort(list, 0, list.length - 1);
		}
	}

	/**
	 * Sorts an array of Comparable objects in place.
	 */
	public static void sort(final Comparable[] objects) {
		if (objects.length > 1) {
			quickSort(objects, 0, objects.length - 1);
		}
	}

	public static void sort(final int[] list) {
		if (list.length > 1) {
			quickSort(list, 0, list.length - 1);
		}
	}

	/**
	 * Sorts an array of objects in place. The given comparer compares pairs of
	 * items.
	 */
	public static void sort(final Object[] objects, final Comparer comparer) {
		if (objects.length > 1) {
			quickSort(objects, 0, objects.length - 1, comparer);
		}
	}

	/**
	 * Sorts an array of objects in place, using the sort order given for each
	 * item.
	 */
	public static void sort(final Object[] objects, final int[] sortOrder) {
		if (objects.length > 1) {
			quickSort(objects, 0, objects.length - 1, sortOrder);
		}
	}

	/**
	 * Sorts an array of strings in place using quicksort.
	 */
	public static void sort(final String[] strings) {
		if (strings.length > 1) {
			quickSort(strings, 0, strings.length - 1);
		}
	}

	/**
	 * Sorts an array of Comparable objects, returning a new array with the
	 * sorted items. The original array is left untouched.
	 */
	public static Comparable[] sortCopy(final Comparable[] objects) {
		final int len = objects.length;
		final Comparable[] copy = new Comparable[len];
		System.arraycopy(objects, 0, copy, 0, len);
		sort(copy);
		return copy;
	}

	/**
	 * Sorts an array of Strings, returning a new array with the sorted items.
	 * The original array is left untouched.
	 */
	public static Object[] sortCopy(final Object[] objects,
			final Comparer comparer) {
		final int len = objects.length;
		final Object[] copy = new Object[len];
		System.arraycopy(objects, 0, copy, 0, len);
		sort(copy, comparer);
		return copy;
	}

	/**
	 * Sorts an array of Strings, returning a new array with the sorted items.
	 * The original array is left untouched.
	 */
	public static String[] sortCopy(final String[] objects) {
		final int len = objects.length;
		final String[] copy = new String[len];
		System.arraycopy(objects, 0, copy, 0, len);
		sort(copy);
		return copy;
	}

	/**
	 * Sorts an array of strings in place using quicksort in reverse
	 * alphabetical order.
	 */
	public static void sortReverseOrder(final String[] strings) {
		if (strings.length > 1) {
			quickSortReverse(strings, 0, strings.length - 1);
		}
	}

	/**
	 * Converts a String[] to char[][].
	 */
	public static char[][] toCharArrays(final String[] a) {
		final int len = a.length;
		final char[][] result = new char[len][];
		for (int i = 0; i < len; ++i) {
			result[i] = toChars(a[i]);
		}
		return result;
	}

	/**
	 * Converts a String to char[].
	 */
	public static char[] toChars(final String s) {
		final int len = s.length();
		final char[] chars = new char[len];
		s.getChars(0, len, chars, 0);
		return chars;
	}

	/**
	 * Converts a String to char[][], where segments are separate by '.'.
	 */
	public static char[][] toCompoundChars(final String s) {
		final int len = s.length();
		if (len == 0) {
			return CharOperation.NO_CHAR_CHAR;
		}
		int segCount = 1;
		for (int off = s.indexOf('.'); off != -1; off = s.indexOf('.', off + 1)) {
			++segCount;
		}
		final char[][] segs = new char[segCount][];
		int start = 0;
		for (int i = 0; i < segCount; ++i) {
			final int dot = s.indexOf('.', start);
			final int end = dot == -1 ? s.length() : dot;
			segs[i] = new char[end - start];
			s.getChars(start, end, segs[i], 0);
			start = end + 1;
		}
		return segs;
	}

	/**
	 * Converts a char[] to String.
	 */
	public static String toString(final char[] c) {
		return new String(c);
	}

	/**
	 * Converts a char[][] to String, where segments are separated by '.'.
	 */
	public static String toString(final char[][] c) {
		final StringBuilder sb = new StringBuilder();
		for (int i = 0, max = c.length; i < max; ++i) {
			if (i != 0) {
				sb.append('.');
			}
			sb.append(c[i]);
		}
		return sb.toString();
	}

	/**
	 * Converts a char[][] and a char[] to String, where segments are separated
	 * by '.'.
	 */
	public static String toString(final char[][] c, final char[] d) {
		if (c == null) {
			return new String(d);
		}
		final StringBuilder sb = new StringBuilder();
		for (final char[] element : c) {
			sb.append(element);
			sb.append('.');
		}
		sb.append(d);
		return sb.toString();
	}

	public static void verbose(final String log) {
		verbose(log, System.out);
	}

	public static synchronized void verbose(final String log,
			final PrintStream printStream) {
		int start = 0;
		do {
			final int end = log.indexOf('\n', start);
			printStream.print(Thread.currentThread());
			printStream.print(" "); //$NON-NLS-1$
			printStream.print(log.substring(start, end == -1 ? log.length()
					: end + 1));
			start = end + 1;
		} while (start != 0);
		printStream.println();
	}

	/**
	 * Writes a string to the given output stream using UTF-8 encoding in a
	 * machine-independent manner.
	 * <p>
	 * First, two bytes are written to the output stream as if by the
	 * <code>writeShort</code> method giving the number of bytes to follow. This
	 * value is the number of bytes actually written out, not the length of the
	 * string. Following the length, each character of the string is output, in
	 * sequence, using the UTF-8 encoding for the character.
	 * 
	 * @param str
	 *            a string to be written.
	 * @return the number of bytes written to the stream.
	 * @exception IOException
	 *                if an I/O error occurs.
	 * 
	 */
	public static int writeUTF(final OutputStream out, final char[] str)
			throws IOException {
		final int strlen = str.length;
		int utflen = 0;
		for (int i = 0; i < strlen; i++) {
			final int c = str[i];
			if (c >= 0x0001 && c <= 0x007F) {
				utflen++;
			} else if (c > 0x07FF) {
				utflen += 3;
			} else {
				utflen += 2;
			}
		}
		if (utflen > 65535) {
			throw new UTFDataFormatException();
		}
		out.write(utflen >>> 8 & 0xFF);
		out.write(utflen >>> 0 & 0xFF);
		if (strlen == utflen) {
			for (int i = 0; i < strlen; i++) {
				out.write(str[i]);
			}
		} else {
			for (int i = 0; i < strlen; i++) {
				final int c = str[i];
				if (c >= 0x0001 && c <= 0x007F) {
					out.write(c);
				} else if (c > 0x07FF) {
					out.write(0xE0 | c >> 12 & 0x0F);
					out.write(0x80 | c >> 6 & 0x3F);
					out.write(0x80 | c >> 0 & 0x3F);
				} else {
					out.write(0xC0 | c >> 6 & 0x1F);
					out.write(0x80 | c >> 0 & 0x3F);
				}
			}
		}
		return utflen + 2; // the number of bytes written to the stream
	}

	public interface Displayable {

		String displayString(Object o);
	}

	public static final String LINE_SEPARATOR = System
			.getProperty("line.separator"); //$NON-NLS-1$

	static final char[] LINE_SEPARATOR_CHARS = LINE_SEPARATOR.toCharArray();

	private static final int DEFAULT_READING_SIZE = 8192;

	/**
	 * Returns the given bytes as a char array using a given encoding (null
	 * means platform default).
	 */
	public static char[] bytesToChar(final byte[] bytes, final String encoding)
			throws IOException {
		return getInputStreamAsCharArray(new ByteArrayInputStream(bytes),
				bytes.length, encoding);
	}

	/**
	 * Returns the contents of the given file as a byte array.
	 * 
	 * @throws IOException
	 *             if a problem occured reading the file.
	 */
	public static byte[] getFileByteContent(final File file) throws IOException {
		InputStream stream = null;
		try {
			stream = new BufferedInputStream(new FileInputStream(file));
			return getInputStreamAsByteArray(stream, (int) file.length());
		} finally {
			if (stream != null) {
				try {
					stream.close();
				} catch (final IOException e) {
					// ignore
				}
			}
		}
	}

	/**
	 * Returns the contents of the given file as a char array. When encoding is
	 * null, then the platform default one is used
	 * 
	 * @throws IOException
	 *             if a problem occured reading the file.
	 */
	public static char[] getFileCharContent(final File file,
			final String encoding) throws IOException {
		InputStream stream = null;
		try {
			stream = new BufferedInputStream(new FileInputStream(file));
			return getInputStreamAsCharArray(stream, (int) file.length(),
					encoding);
		} finally {
			if (stream != null) {
				try {
					stream.close();
				} catch (final IOException e) {
					// ignore
				}
			}
		}
	}

	/*
	 * NIO support to get input stream as byte array. Not used as with JDK 1.4.2
	 * this support is slower than standard IO one... Keep it as comment for
	 * future in case of next JDK versions improve performance in this area...
	 * 
	 * public static byte[] getInputStreamAsByteArray(FileInputStream stream,
	 * int length) throws IOException {
	 * 
	 * FileChannel channel = stream.getChannel(); int size =
	 * (int)channel.size(); if (length >= 0 && length < size) size = length;
	 * byte[] contents = new byte[size]; ByteBuffer buffer =
	 * ByteBuffer.wrap(contents); channel.read(buffer); return contents; }
	 */
	/**
	 * Returns the given input stream's contents as a byte array. If a length is
	 * specified (ie. if length != -1), only length bytes are returned.
	 * Otherwise all bytes in the stream are returned. Note this doesn't close
	 * the stream.
	 * 
	 * @throws IOException
	 *             if a problem occured reading the stream.
	 */
	public static byte[] getInputStreamAsByteArray(final InputStream stream,
			final int length) throws IOException {
		byte[] contents;
		if (length == -1) {
			contents = new byte[0];
			int contentsLength = 0;
			int amountRead = -1;
			do {
				final int amountRequested = Math.max(stream.available(),
						DEFAULT_READING_SIZE); // read
				// at
				// least
				// 8K

				// resize contents if needed
				if (contentsLength + amountRequested > contents.length) {
					System.arraycopy(contents, 0,
							contents = new byte[contentsLength
									+ amountRequested], 0, contentsLength);
				}

				// read as many bytes as possible
				amountRead = stream.read(contents, contentsLength,
						amountRequested);

				if (amountRead > 0) {
					// remember length of contents
					contentsLength += amountRead;
				}
			} while (amountRead != -1);

			// resize contents if necessary
			if (contentsLength < contents.length) {
				System.arraycopy(contents, 0,
						contents = new byte[contentsLength], 0, contentsLength);
			}
		} else {
			contents = new byte[length];
			int len = 0;
			int readSize = 0;
			while (readSize != -1 && len != length) {
				// See PR 1FMS89U
				// We record first the read size. In this case len is the actual
				// read size.
				len += readSize;
				readSize = stream.read(contents, len, length - len);
			}
		}

		return contents;
	}

	/*
	 * NIO support to get input stream as char array. Not used as with JDK 1.4.2
	 * this support is slower than standard IO one... Keep it as comment for
	 * future in case of next JDK versions improve performance in this area...
	 * public static char[] getInputStreamAsCharArray(FileInputStream stream,
	 * int length, String encoding) throws IOException {
	 * 
	 * FileChannel channel = stream.getChannel(); int size =
	 * (int)channel.size(); if (length >= 0 && length < size) size = length;
	 * Charset charset = encoding==null?systemCharset:Charset.forName(encoding);
	 * if (charset != null) { MappedByteBuffer bbuffer =
	 * channel.map(FileChannel.MapMode.READ_ONLY, 0, size); CharsetDecoder
	 * decoder = charset.newDecoder(); CharBuffer buffer =
	 * decoder.decode(bbuffer); char[] contents = new char[buffer.limit()];
	 * buffer.get(contents); return contents; } throw new
	 * UnsupportedCharsetException(SYSTEM_FILE_ENCODING); }
	 */
	/**
	 * Returns the given input stream's contents as a character array. If a
	 * length is specified (ie. if length != -1), only length chars are
	 * returned. Otherwise all chars in the stream are returned. Note this
	 * doesn't close the stream.
	 * 
	 * @throws IOException
	 *             if a problem occured reading the stream.
	 */
	public static char[] getInputStreamAsCharArray(final InputStream stream,
			final int length, final String encoding) throws IOException {
		InputStreamReader reader = null;
		reader = encoding == null ? new InputStreamReader(stream)
				: new InputStreamReader(stream, encoding);
		char[] contents;
		if (length == -1) {
			contents = CharOperation.NO_CHAR;
			int contentsLength = 0;
			int amountRead = -1;
			do {
				final int amountRequested = Math.max(stream.available(),
						DEFAULT_READING_SIZE); // read
				// at
				// least
				// 8K

				// resize contents if needed
				if (contentsLength + amountRequested > contents.length) {
					final char[] old = contents;
					contents = new char[contentsLength + amountRequested];
					System.arraycopy(old, 0, contents, 0, contentsLength);
				}

				// read as many chars as possible
				amountRead = reader.read(contents, contentsLength,
						amountRequested);

				if (amountRead > 0) {
					// remember length of contents
					contentsLength += amountRead;
				}
			} while (amountRead != -1);

			// Do not keep first character for UTF-8 BOM encoding
			int start = 0;
			if ("UTF-8".equals(encoding)) { //$NON-NLS-1$
				if (contents[0] == 0xFEFF) { // if BOM char then skip
					contentsLength--;
					start = 1;
				}
			}
			// resize contents if necessary
			if (contentsLength < contents.length) {
				final char[] old = contents;
				contents = new char[contentsLength];
				System.arraycopy(old, start, contents, 0, contentsLength);
			}
		} else {
			contents = new char[length];
			int len = 0;
			int readSize = 0;
			while (readSize != -1 && len != length) {
				// See PR 1FMS89U
				// We record first the read size. In this case len is the actual
				// read size.
				len += readSize;
				readSize = reader.read(contents, len, length - len);
			}
			// Do not keep first character for UTF-8 BOM encoding
			int start = 0;
			if ("UTF-8".equals(encoding)) { //$NON-NLS-1$
				if (contents[0] == 0xFEFF) { // if BOM char then skip
					len--;
					start = 1;
				}
			}
			// See PR 1FMS89U
			// Now we need to resize in case the default encoding used more than
			// one byte for each
			// character
			if (len != length) {
				contents = new char[len];
				System.arraycopy(contents, start, contents, 0, len);
			}
		}

		return contents;
	}

	/**
	 * Converts an array of Objects into String.
	 */
	public static String toString(final Object[] objects) {
		return toString(objects, new Displayable() {

			public String displayString(final Object o) {
				if (o == null) {
					return "null"; //$NON-NLS-1$
				}
				return o.toString();
			}
		});
	}

	/**
	 * Converts an array of Objects into String.
	 */
	public static String toString(final Object[] objects,
			final Displayable renderer) {
		if (objects == null) {
			return ""; //$NON-NLS-1$
		}
		final StringBuilder buffer = new StringBuilder(10);
		for (int i = 0; i < objects.length; i++) {
			if (i > 0) {
				buffer.append(", "); //$NON-NLS-1$
			}
			buffer.append(renderer.displayString(objects[i]));
		}
		return buffer.toString();
	}

	/**
	 * Converts a boolean value into Boolean.
	 * 
	 * @param bool
	 *            The boolean to convert
	 * @return The corresponding Boolean object (TRUE or FALSE).
	 */
	public static Boolean toBoolean(final boolean bool) {
		if (bool) {
			return Boolean.TRUE;
		}
		return Boolean.FALSE;
	}

	/**
	 * Get the string value of an Erlang string, empty if empty list
	 * 
	 * @param o
	 *            Erlang string or list
	 * @return string value
	 */
	public static String stringValue(final OtpErlangObject o) {
		if (o instanceof OtpErlangString) {
			final OtpErlangString s = (OtpErlangString) o;
			return s.stringValue();
		} else if (o instanceof OtpErlangList) {
			final OtpErlangList l = (OtpErlangList) o;
			if (l.arity() == 0) {
				return "";
			}
			try {
				return l.stringValue();
			} catch (final OtpErlangException e) {
				ErlLogger.error(e);
				return null;
			}
		} else if (o instanceof OtpErlangBinary) {
			final OtpErlangBinary b = (OtpErlangBinary) o;
			try {
				return new String(b.binaryValue(), "ISO-8859-1");
			} catch (final UnsupportedEncodingException e) {
				try {
					return new String(b.binaryValue(), "UTF8");
				} catch (final UnsupportedEncodingException e1) {
					return "";
				}
			}
		}
		return null;
	}

	/**
	 * Return true if it's the atom ok or a tuple {ok, ...}
	 * 
	 * @param o
	 *            atom or tuple
	 * @return true if ok
	 */
	public static boolean isOk(final OtpErlangObject o) {
		if (o instanceof OtpErlangAtom) {
			final OtpErlangAtom a = (OtpErlangAtom) o;
			return a.atomValue().compareTo("ok") == 0;
		} else if (o instanceof OtpErlangTuple) {
			final OtpErlangTuple t = (OtpErlangTuple) o;
			return isOk(t.elementAt(0));
		}
		return false;
	}

	/**
	 * return true if it's the atom error or a tuple {error, ...}
	 * 
	 * @param o
	 *            atom or tuple
	 * @return true if error
	 */
	public static boolean isError(final OtpErlangObject o) {
		if (o instanceof OtpErlangAtom) {
			final OtpErlangAtom a = (OtpErlangAtom) o;
			return a.atomValue().compareTo("error") == 0;
		} else if (o instanceof OtpErlangTuple) {
			final OtpErlangTuple t = (OtpErlangTuple) o;
			return isError(t.elementAt(0));
		}
		return false;
	}

	public static String ioListToString(final OtpErlangObject o) {
		final StringBuilder sb = new StringBuilder();
		ioListToStringBuilder(o, sb);
		return sb.toString();
	}

	private static void ioListToStringBuilder(final OtpErlangObject o,
			final StringBuilder sb) {
		if (o instanceof OtpErlangLong) {
			final OtpErlangLong l = (OtpErlangLong) o;
			try {
				sb.append(l.charValue());
			} catch (final OtpErlangRangeException e) {
			}
		} else if (o instanceof OtpErlangString) {
			final OtpErlangString s = (OtpErlangString) o;
			sb.append(s.stringValue());
		} else if (o instanceof OtpErlangList) {
			final OtpErlangList l = (OtpErlangList) o;
			for (final OtpErlangObject i : l) {
				ioListToStringBuilder(i, sb);
			}
			ioListToStringBuilder(l.getLastTail(), sb);
		} else if (o instanceof OtpErlangBinary) {
			final OtpErlangBinary b = (OtpErlangBinary) o;
			try {
				final String s = new String(b.binaryValue(), "ISO-8859-1");
				sb.append(s);
			} catch (final UnsupportedEncodingException e) {
			}
		} else {
			sb.append(o.toString());
		}
	}

}
