package routines;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Reader;
import java.io.StringBufferInputStream;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.security.SecureRandom;
import java.text.NumberFormat;
import java.util.BitSet;
import java.util.Locale;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
//

import com.sun.xml.messaging.saaj.packaging.mime.util.BASE64DecoderStream;
import com.sun.xml.messaging.saaj.packaging.mime.util.BASE64EncoderStream;

/*
 * user specification: the function's comment should contain keys as follows: 1. write about the function's comment.but
 * it must be before the "{talendTypes}" key.
 * 
 * 2. {talendTypes} 's value must be talend Type, it is required . its value should be one of: String, char | Character,
 * long | Long, int | Integer, boolean | Boolean, byte | Byte, Date, double | Double, float | Float, Object, short |
 * Short
 * 
 * 3. {Category} define a category for the Function. it is required. its value is user-defined .
 * 
 * 4. {param} 's format is: {param} <type>[(<default value or closed list values>)] <name>[ : <comment>]
 * 
 * <type> 's value should be one of: string, int, list, double, object, boolean, long, char, date. <name>'s value is the
 * Function's parameter name. the {param} is optional. so if you the Function without the parameters. the {param} don't
 * added. you can have many parameters for the Function.
 * 
 * 5. {example} gives a example for the Function. it is optional.
 */

public class StringUtil {

	static final int MAX_NUMBER_OF_UNICODE = 65536;
	static BitSet unicode_array_utf16;

	static {
		String allowedUnicodeCharsBase64Gzip = "H4sIAAAAAAAAAA==7ZlNaBNBFIBfMrE5ScSTh+LqwWs96KWUsOLRiwePBWsFDxWV0qZSQTMRhHoX\r\n"
				+ "vOauInrwnECRHHMQ7EFhCz3kkqaRHIx0m+d787O7SdOfILGI823CvJl5M+/Nm9lJZvfdNpxaXoIz\r\n"
				+ "9+cfz0+tFBYeTF1fKNy6V3i097I4ffXtRQGpWcjcXSgsFyA9e2N1cWVJpRcyn85+XHmVBlhdBMgA\r\n"
				+ "Ers4fp7GIpnbaB+7YQ8iqSetNIHgZUnQdXX1OYyKSRsIQktZUyLRVykQN02aJDJu0s7nPcRf5VaO\r\n"
				+ "bH4oPSl/ecj5tRb4WL2dnl7rbFN+t9KCNNYhNVNRedIH1q9Ol9exdu5rvtYk+1V4nq9t7VD9XtCi\r\n"
				+ "odTFnXzQ0fkmTGD9GuUbbJTyPZ/6Kz0LLlP0QolNmCTf5l7INRslgnzCGWy+77Uvveb8FfTn4jh6\r\n"
				+ "ahSBGQvXZ1VuOJ6Oin94XPvJyzzShyUbXC3kdX8No8dTAN4oPY8Gdw0pLYNkWwHCafrmYh1Blx/J\r\n"
				+ "EqOJNivDjjxHl+rPUwU/uMRSQ72YePGVkYfLcfVV34PrxzO9CWrFtE033T7Pt+L1p9yI2w/SBh7Z\r\n"
				+ "8fDJdfb+DUuBLpPfEXfqiQXQCZWLQvkqTfwOsi+Oa/pA7FxAYhxm0OOgN7x4Y1z29sFTKiiy+/cX\r\n"
				+ "GenIhH4vCrFeiaVUSaWSsBFTsoj7t+uN9/PkWh9Gjlv4+4pVnKJ9VmWU0qbNZiNPYwtJ9cPKHA7H\r\n"
				+ "nzHaL7LD4XA4HA6Hw6HRp8UjDoknhmfSo59x2DG0E0MBRDzowQmfwkFIDM15WZjnOYP6XCqwah1R\r\n"
				+ "HpWSNoh1rEQeclUneqKk67sAWQA6oxeLxcFzv0joOhwOh+NvoH8w9FbelYP78v/GiU6Fw/HP4w+k\r\n"
				+ "Gr63+NXMnETc7Eb/Y7/1KXVhxBdp5n4N2Zq9fUGqToKcxJ+xZrm/YfT+yzpynv6XToZhuPob2JT4\r\n" + "YkkgAAA=";

		try {
			ObjectInputStream stream = new ObjectInputStream(new GZIPInputStream(
					new BASE64DecoderStream(new StringBufferInputStream(allowedUnicodeCharsBase64Gzip))));
			unicode_array_utf16 = (BitSet) stream.readObject();
		} catch (Exception e) {
			e.printStackTrace(System.err);
			unicode_array_utf16 = new BitSet(MAX_NUMBER_OF_UNICODE);
		}
	}

	/**
	 * This class provides a method for reading a file containing one line of ones
	 * and zeroes, to build a BitSet from it.
	 * 
	 * @author rretzbach
	 *
	 */
	public static class Base64BitSetWriter {

		/**
		 * Read from ones and zeroes from a file and create a bitset
		 * 
		 * @param filepath relative or absolute filepath to file containing only ones
		 *                 and zeroes
		 * @return a bitset corresponding to the file
		 * @throws IOException
		 */
		public static BitSet readFile(String filepath) throws IOException {
			BitSet bitset = new BitSet(65536);

			BufferedReader reader = new BufferedReader(new FileReader(filepath));
			int chr;
			int index = 0;
			while ((chr = reader.read()) != -1) {
				bitset.set(index++, chr == '1');
			}
			reader.close();

			ObjectOutputStream stream = new ObjectOutputStream(
					new GZIPOutputStream(new BASE64EncoderStream(System.out)));
			stream.writeObject(bitset);
			stream.close();

			return bitset;
		}
	}

	/**
	 * Specifies a substring consisting of the first n characters of a string.
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("hello world!") string: String.
	 * 
	 * {param} int(5) index : index
	 * 
	 * {example} LEFT("hello world!",5) # hello
	 */
	public static String startFrom(String string, int index) {
		return string == null ? null : string.substring(0, Math.min(string.length(), index));
	}

	/**
	 * Retrieves a value from a URL query string.
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("url") url: String.
	 * 
	 * {param} string("key") key: String
	 * 
	 * {example}
	 * getValueFromUrl("http://host/query?key1=value1&key2=value2&key3=value3,"key2")
	 * # value2
	 */
	public static String getValueFromUrl(String url, String key) {
		if (url == null) {
			return "";
		}
		if (key == null) {
			return "";
		}
		String value = "";
		try {
			url = java.net.URLDecoder.decode(url.trim(), "ASCII");
		} catch (UnsupportedEncodingException e) {
			new RuntimeException("Wrong Encoding configured", e);
		}
		String lowerUrl = url.toLowerCase();
		key = key.trim().toLowerCase();
		int pos0 = lowerUrl.indexOf("&" + key);
		if (pos0 == -1) {
			pos0 = lowerUrl.indexOf('?' + key);
		}
		int pos1 = 0;
		if (pos0 != -1) {
			// an url need an = to start the value
			pos1 = lowerUrl.indexOf('&', pos0 + 1); // next parameter
			if (pos1 == -1) {
				pos1 = lowerUrl.length();
			}
			if (pos1 != -1) {
				value = url.substring(pos0 + 1 + key.length() + 1, pos1);
			}
		}
		return value;
	}

	/**
	 * Retrieves a value from a URL query string.
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("content") url: String.
	 * 
	 * {param} string("regex") key: String
	 * 
	 * {example} extractByRegexGroups("content","regex") # value2
	 */
	public static String extractByRegexGroups(String content, String regex) {
		if (regex != null) {
			if (content != null) {
				content = content.trim();
				final StringBuffer sb = new StringBuffer();
				Pattern pattern = Pattern.compile(regex);
				Matcher matcher = pattern.matcher(content);
				while (matcher.find()) {
					if (matcher.start() < matcher.end()) {
						for (int i = 1, n = matcher.groupCount(); i <= n; i++) {
							sb.append(matcher.group(i));
						}
					}
				}
				return sb.toString();
			} else {
				return null;
			}
		} else {
			return content;
		}
	}

	/**
	 * Tests a content by regex groups.
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("content") url: String.
	 * 
	 * {param} string("regex") key: String
	 * 
	 * {example} containsAllRegexGroups("content","regex") # value2
	 */
	public static boolean containsAllRegexGroups(String content, String regex) {
		if (regex != null) {
			if (content != null) {
				content = content.trim();
				boolean ok = false;
				Pattern pattern = Pattern.compile(regex);
				Matcher matcher = pattern.matcher(content);
				while (matcher.find()) {
					if (matcher.start() < matcher.end()) {
						for (int i = 1, n = matcher.groupCount(); i <= n; i++) {
							String test = matcher.group(i);
							if (isEmpty(test)) {
								ok = false;
								break;
							} else {
								ok = true;
							}
						}
					} else {
						ok = false;
						break;
					}
				}
				return ok;
			} else {
				return false;
			}
		} else {
			return false;
		}
	}

	/**
	 * Replace all occurences of a regex group with a constant string
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("content") content: String.
	 * 
	 * {param} string("regex") regex: String
	 * 
	 * {param} string(replacement) replacement: String
	 * 
	 * {example} replaceByRegexGroups("Europe Design Price 12,30 Eur","[1-9]+
	 * (Eur)", "Dollar") # value2
	 */
	public static String replaceByRegexGroups(String content, String regex, String replacement)
			throws java.util.regex.PatternSyntaxException {
		if (content != null) {
			StringBuilder result = new StringBuilder();
			java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
			java.util.regex.Matcher matcher = pattern.matcher(content);
			int prevEnd = 0;
			while (matcher.find()) {
				for (int i = 1; i <= matcher.groupCount(); i++) {
					int start = matcher.start(i);
					int end = matcher.end(i);
					if (start < end) {
						result.append(content.substring(prevEnd, start));
						if (replacement != null) {
							result.append(replacement);
						}
						prevEnd = end;
					}
				}
			}
			if (prevEnd < content.length()) {
				result.append(content.substring(prevEnd, content.length()));
			}
			return result.toString();
		} else {
			return null;
		}
	}

	/**
	 * Returns a empty not null String if input is null or equals "null", otherwise
	 * returns input
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(input) input: String.
	 * 
	 * {example} getNullSaveStr(null) # ""
	 */
	public static String getNullSaveStr(String input) {
		if (input == null) {
			return "";
		} else if ("null".equalsIgnoreCase(input)) {
			return "";
		} else {
			return input;
		}
	}

	/**
	 * Returns null strings for empty input strings, otherwise returns input
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(input) input: String.
	 * 
	 * {example} saveEmptyStringAsNull(null) # ""
	 */
	public static String saveEmptyStringAsNull(String input) {
		if (input == null || input.isEmpty()) {
			return null;
		} else {
			return input;
		}
	}

	/**
	 * returns the upper case string, is null save
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(input) input: String.
	 * 
	 * {example} toUpperCase("abc") # "ABC"
	 */
	public static String toUpperCase(String input) {
		String s = getNullSaveStr(input);
		return s.toUpperCase();
	}

	/**
	 * returns the lower case string, is null save
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(input) input: String.
	 * 
	 * {example} toLowerCase("AbC") # "abc"
	 */
	public static String toLowerCase(String input) {
		String s = getNullSaveStr(input);
		return s.toLowerCase();
	}

	/**
	 * Returns the relative path from a full path based on a base path
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(basePath) basePath: String.
	 * 
	 * {param} string(fullPath) fullPath: String.
	 * 
	 * {example} getRelativePath(basePath, fullPath) # ""
	 */
	public static String getRelativePath(String basePath, String fullPath) {
		String relativePath = null;
		int pos = fullPath.indexOf(basePath);
		if (pos == -1) {
			throw new IllegalArgumentException("basepath is not part of fullPath");
		} else {
			relativePath = fullPath.substring(basePath.length());
		}
		return relativePath;
	}

	/**
	 * Returns the name of file without path
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(fullPath) fullPath: String.
	 * 
	 * {example} getFileName(fullPath) # ""
	 */
	public static String getFileName(String filePath) {
		File f = new File(filePath);
		return f.getName();
	}

	/**
	 * Returns the directory of the file
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(fullPath) fullPath: String.
	 * 
	 * {example} getFileDir(fullPath) # ""
	 */
	public static String getFileDir(String filePath) {
		File f = new File(filePath);
		String parent = f.getParent();
		if (parent != null) {
			return parent;
		} else {
			return "";
		}
	}

	/**
	 * checks if the first string is in the list of following strings (case
	 * insensitive regardless of white spaces)
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} boolean | Boolean
	 * 
	 * {param} string("to_test") test: String.
	 * 
	 * {param} string("possibleValue") posibleValues: String.
	 * 
	 * {example} int(" test","hans"," TEST ","Tata") # true
	 */
	public static boolean in(String test, String... possibleValues) {
		if (test != null) {
			for (String v : possibleValues) {
				if (v != null && test.trim().equalsIgnoreCase(v.trim())) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * Retrieves the host from a URL
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(url) url: String.
	 * 
	 * {example} getHost(url) # DE
	 */
	public static String getHost(String urlString) {
		if (urlString == null) {
			return null;
		}
		urlString = urlString.trim().toLowerCase();
		if (urlString.startsWith("http://") == false) {
			urlString = "http://" + urlString;
		}
		String decUrl = null;
		try {
			decUrl = java.net.URLDecoder.decode(urlString, "ASCII");
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("getHost failed: " + e.getMessage(), e);
		}
		java.net.URL url = null;
		try {
			url = new java.net.URL(decUrl);
		} catch (MalformedURLException e) {
			throw new RuntimeException("getHost failed: " + e.getMessage(), e);
		}
		return url.getHost();
	}

	/**
	 * Retrieves the top level domain from a URL
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string(url) url: String.
	 * 
	 * {example} getTLD(url) # DE
	 */
	public static String getTLD(String urlString) {
		String host = getHost(urlString);
		String tld = null;
		if (host != null) {
			int pos = host.lastIndexOf('.');
			if (pos != -1) {
				tld = host.substring(pos + 1);
			} else {
				tld = host;
			}
		}
		return tld;
	}

	/**
	 * returns the first not empty string
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} string("testMe1","testMe2") strings: String.
	 * 
	 * {example} coalesce(" test","hans"," TEST ","Tata") # "test"
	 */
	public static String coalesce(String... strings) {
		for (String s : strings) {
			if (s != null) {
				s = s.trim();
				if (isEmpty(s) == false) {
					return s;
				}
			}
		}
		return null;
	}

	/**
	 * shows if string substring from another string
	 * 
	 */
	public static boolean isSubString(String small, String large) {
		int lastj = small.length() - 1;
		int lasti = large.length() - small.length();
		int i = 0;
		// char str0 = '\u0000';

		WHILE1: while (i <= lasti) {
			// System.out.println( "i:" + i );
			if (large.charAt(i) == small.charAt(0)) {
				int j = 1;
				while (j <= lastj) {
					// System.out.println( "j:" + j );
					if (large.charAt(i + j) != small.charAt(j)) {
						i++;
						continue WHILE1;
					} else {
						j++;
					}
				}
				return true;
			} else {
				i++;
			}
		}
		return false;
	}

	/**
	 * returns a string which works as hash based key
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1","value2") strings: String.
	 * 
	 * {example} getHashBasedKey(aString,aDouble,aDate,anObject) # 2323133_18
	 */
	public static String getHashBasedKey(Object... objects) {
		StringBuilder sb = new StringBuilder();
		for (Object o : objects) {
			if (o instanceof String) {
				sb.append((String) o);
				sb.append('_');
			} else if (o instanceof java.util.Date) {
				java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyyMMdd_HHmmss");
				sb.append(sdf.format((java.util.Date) o));
			} else if (o instanceof Number) {
				NumberFormat nf = NumberFormat.getInstance(Locale.ENGLISH);
				sb.append(nf.format((Number) o));
			} else if (o != null) {
				sb.append(o.toString());
			} else {
				sb.append("\\N");
			}
		}
		int sbLength = sb.length();
		// build hash code and add salt
		return sb.toString().hashCode() + "_" + sbLength;
	}

	/**
	 * returns true if the string is not filled or contains "null"
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1") strings: String.
	 * 
	 * {example} isEmpty(aString) # 2323133_18
	 */
	public static boolean isEmpty(String s) {
		if (s == null) {
			return true;
		}
		if (s.trim().isEmpty()) {
			return true;
		}
		if (s.trim().equalsIgnoreCase("null")) {
			return true;
		}
		return false;
	}

	/**
	 * returns NULL if the string is not filled or contains "null"
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1") strings: String.
	 * 
	 * {example} isEmpty(aString) # 2323133_18
	 */
	public static String convertEmptyToNull(String s) {
		if (isEmpty(s))
			return null;

		return s;
	}

	/**
	 * replaces all invalid Teradata UTF-8 characters in the given file by a
	 * question mark (?)
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value") string: String.
	 * 
	 * {example} validateTeraUTF8String("123�") # 123?
	 */
	public static String validateTeraUTF8String(String value) throws java.io.IOException {
		return cleanString_Utf8(value, "?");
	}

	/**
	 * replaces all invalid Teradata UTF-8 characters in the given string by a given
	 * replacement string
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1") string: String.
	 * 
	 * {param} object("replacement") string: String.
	 * 
	 * {example} cleanString_Utf8("123", "?") # 123 {example}
	 * cleanString_Utf8("123�", "(replace sign is ?)") # 123(replace sign is ?)
	 */
	public static String cleanString_Utf8(String value, String replacement) {
		if (value == null) {
			return null;
		}

		StringBuilder sb = new StringBuilder(value.length());
		for (int ii = 0; ii < value.length();) {
			int codePoint = value.codePointAt(ii);
			if (codePoint < MAX_NUMBER_OF_UNICODE && unicode_array_utf16.get(codePoint)) {
				sb.appendCodePoint(codePoint);
			} else {
				sb.append("?");
			}
			ii++;
		}
		return sb.toString();
	}

	/**
	 * returns a string filtered for 16 bit codepage positions
	 * 
	 * @param text text with potential UTF16 encoded chars
	 * @return text in UTF-8
	 *
	 *         {Category} StringUtil
	 *
	 *         {talendTypes} String
	 *
	 *         {param} String("value1") text: String.
	 *
	 *         {example} get16BitUnicodeOnly(aString) # 2323133_18
	 */
	public static String get16BitUnicodeOnly(String text) {
		if (text == null) {
			return null;
		}
		if (text.isEmpty()) {
			return "";
		}

		byte[] theBytes = text.getBytes();
		StringBuilder result = new StringBuilder();
		int cp = 0;
		// StringUtil.dumpString(String text);
		for (int sp = 0; sp < text.length(); sp++) {
			if (((theBytes[cp] & 0xE0) == 0xC0) || ((theBytes[cp] & 0x80) == 0x00)) {
				// this is a 8 or 16 bit char
				result.append(text.charAt(sp));
			}
			if ((theBytes[cp] & 0xF8) == 0xF0) // Start of 4 byte char
			{
				sp++;
			} // skip 1 char of input because java makes a second char here
			else if ((theBytes[cp] & 0xF0) == 0xE0) // Start of 4 byte char
			{
				sp++;
			} // skip 1 char of input because java makes a second char here

			cp++; // next byte
			while (cp < theBytes.length && (theBytes[cp] & 0xE0) == 0x80) {
				cp++;
			}
			; // skip follow up bytes
		} // for all characters in the incoming text

		return result.toString();
	}

	/**
	 * dumps debug infor of string to sysout
	 * 
	 * @param text
	 * @return void
	 *
	 *         {Category} StringUtil
	 *
	 *         {talendTypes} String
	 *
	 *         {param} String("value1") text: String.
	 *
	 *         {example} dumpString(aString)
	 */

	public static String dumpString(String text) {
		if (text == null) {
			return null;
		}
		if (text.isEmpty()) {
			return "";
		}

		char[] theChar = text.toCharArray();
		System.out.println("text length: " + text.length() + " theChar length:" + theChar.length);
		System.out.println(">" + text + "<" + StringUtil.toHexString(text.getBytes()));
		for (int i = 0; i < text.length(); i++) {
			byte[] bb = (theChar[i] + "").getBytes();
			System.out.println(">" + theChar[i] + "<" + StringUtil.toHexString(bb));
		}
		return text;
	}

	public static String toHexString(byte[] ba) {
		StringBuilder str = new StringBuilder();
		for (int i = 0; i < ba.length; i++)
			str.append(String.format("%x", ba[i]));
		return str.toString();
	}

	private static void replaceInvalidUnicodeChars(java.util.Scanner scanner, java.io.BufferedWriter bw,
			String lineSeparator, String... patternReplacementPairs) throws java.io.IOException {
		String line;

		while (scanner.hasNext()) {
			line = scanner.next();

			line = StringUtil.cleanString_Utf8(line, patternReplacementPairs[1]);

			bw.write(line + lineSeparator);
		}

		bw.flush();
	}

	private static void replacePatterns(java.util.Scanner scanner, java.io.BufferedWriter bw, String lineSeparator,
			String... patternReplacementPairs) throws java.io.IOException {
		if ((patternReplacementPairs.length % 2) != 0)
			throw new IllegalArgumentException("Invalid argument length! Pairs of patterns and replacements needed!");

		String[] patternRepArr = new String[2];
		java.util.Map<java.util.regex.Pattern, String[]> patternsMap = new java.util.HashMap<java.util.regex.Pattern, String[]>();
		// store pattern-replacement-pairs
		for (int i = 1; i <= patternReplacementPairs.length; i++) {
			if ((i % 2) == 0) {
				patternRepArr[1] = patternReplacementPairs[i - 1];
				patternsMap.put(java.util.regex.Pattern.compile(patternRepArr[0]), patternRepArr.clone());
			} else {
				patternRepArr[0] = patternReplacementPairs[i - 1];
			}
		}

		String line;

		// ...and replace, if matches
		while (scanner.hasNext()) {
			line = scanner.next();
			for (java.util.Map.Entry<java.util.regex.Pattern, String[]> entry : patternsMap.entrySet()) {
				if (entry.getKey().matcher(line).find()) {
					// System.out.println("ori:" + line);
					line = line.replaceAll(entry.getValue()[0], entry.getValue()[1]);
					// System.out.println("replaced:" + line);
				}
			}
			bw.write(line + lineSeparator);
			// bw.newLine();
		}

		bw.flush();
	}

	/**
	 * compute the MD5-Hash-Sum of the given string value
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1") string: String.
	 * 
	 * If the given value is null, null will be returned!
	 * 
	 * {example} getMD5Sum("string") # b45cffe084dd3d20d928bee85e7b0f21
	 */
	public static String getMD5Sum(String value) throws Exception {
		if (value == null)
			return null;

		java.security.MessageDigest md = java.security.MessageDigest.getInstance("MD5");
		byte[] array = md.digest(value.getBytes("UTF-8"));
		StringBuffer sb = new StringBuffer();
		for (int i = 0; i < array.length; ++i) {
			sb.append(Integer.toHexString((array[i] & 0xFF) | 0x100).substring(1, 3));
		}
		return sb.toString();
	}

	/**
	 * Cleans up keywords (removes brackets, braces, parentheses and quotation marks
	 * from the given keyword string and returns the replaced, trimmed, lowercase
	 * keyword)
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value1") string: String.
	 * 
	 * If the given value is null, null will be returned!
	 * 
	 * {example} getCleanKeyword(" [({"String"})] ") # string
	 */
	public static String getCleanKeyword(String value) throws Exception {
		if (value == null)
			return null;
		// replace all [](){}"
		return value.replaceAll("[\\[\\]\\(\\)\\{\\}\"]", "").trim().toLowerCase();
	}

	/**
	 * returns the substring of text
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("text") string: String. {param} object("beginIndex") string:
	 * String. starts with index 0 {param} object("length") string: Integer. number
	 * of returned characters
	 * 
	 */
	public static String substring(String text, int beginIndex, int length) {
		// check for null values
		if (text == null)
			return null;

		// must have reasonable value between 0 and last possible index
		int begin = Math.min(Math.max(beginIndex, 0), text.length() - 1);
		// must have reasonable value between 0 and text.length
		int len = Math.min(text.length() - begin, Math.max(length, 0));
		return text.substring(begin, begin + len);
	}

	/**
	 * returns the given text with MAC WIN LINUX linebreaks replaced by the given
	 * replacement string. this prevents the tpt load to report an error due to
	 * incompatible/broken data schemas
	 * 
	 * Author: rretzbach
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("text") string: String. {param} object("replacement") string:
	 * String.
	 * 
	 */
	public static String replaceLineBreaks(String text, String replacement) {
		if (text == null || replacement == null) {
			return null;
		}
		return text.replaceAll("\\r\\n|\\r|\\n", replacement);
	}

	/**
	 * getUnicodeRandomString : Return a randomly generated String
	 * 
	 * 
	 * {talendTypes} String
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} int(6) length: length of the String to return
	 * 
	 * {example} getUnicodeRandomString(6) # 日本�?��?�関係
	 */
	public static String getUnicodeRandomString(int length) {
		Random random = new SecureRandom();
		int cnt = 0;
		StringBuffer buffer = new StringBuffer();
		while (cnt < length) {
			buffer.appendCodePoint(random.nextInt(MAX_NUMBER_OF_UNICODE));
			++cnt;
		}
		return buffer.toString();
	}

	/**
	 * decodes a base64 encoded string
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value") string: String.
	 * 
	 * {example}
	 * decodeString_Base64("cG9vQGFpbC5jb21Acm1Acm1AZGFpbHktZW1haWxfc2FudGEtY3J1el9ybUA1Yzg2NWNhYy01M2I4LTRlMWMtOTU4Mi0xYmVmY2I2ZmQyZjI="))
	 * #
	 * poo@ail.com@rm@rm@daily-email_santa-cruz_rm@5c865cac-53b8-4e1c-9582-1befcb6fd2f2
	 */
	public static String decodeString_Base64(String value) throws java.io.IOException {
		if (value == null) {
			return null;
		}

		return new String(javax.xml.bind.DatatypeConverter.parseBase64Binary(value));
	}

	/**
	 * encodes a string using base64
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value") string: String.
	 * 
	 * {example}
	 * encodeString_Base64("poo@ail.com@rm@rm@daily-email_santa-cruz_rm@5c865cac-53b8-4e1c-9582-1befcb6fd2f2"))
	 * #
	 * cG9vQGFpbC5jb21Acm1Acm1AZGFpbHktZW1haWxfc2FudGEtY3J1el9ybUA1Yzg2NWNhYy01M2I4LTRlMWMtOTU4Mi0xYmVmY2I2ZmQyZjI=
	 */
	public static String encodeString_Base64(String value) throws java.io.IOException {
		if (value == null) {
			return null;
		}

		return javax.xml.bind.DatatypeConverter.printBase64Binary(value.getBytes());
	}

	/**
	 * Specifies a substring not exceeding the given number of maximum bytes.
	 * 
	 * {talendTypes} String
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("hello world!") string: String.
	 * 
	 * {param} int(5) maxbytes : maxbytes
	 * 
	 * {example} LEFT_bytes_utf8("hello world!",5) # hello
	 */
	public static String LEFT_bytes_utf8(String string, int maxbytes) {
		if (string == null || maxbytes < 1) {
			return null;
		}

		try {
			String shorterString = string;
			Integer currentByteSize;

			currentByteSize = string.getBytes("UTF-8").length;

			Integer targetCharLength = maxbytes;

			while (currentByteSize > maxbytes) {
				shorterString = shorterString.substring(0, Math.min(targetCharLength, shorterString.length()));
				currentByteSize = shorterString.getBytes("UTF-8").length;

				// remove a character from the current string for every 4th excessive byte,
				// because chars can be up to 4 bytes wide in utf-8
				Integer removeCharCount = new Double(Math.ceil(((double) currentByteSize) - maxbytes) / 4).intValue();
				targetCharLength = targetCharLength - (removeCharCount > 0 ? removeCharCount : 1);
			}
			return shorterString;
		} catch (UnsupportedEncodingException e) {
			return null;
		}
	}

	/**
	 * check if the given value is empty, null or "null"
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("value") string: String.
	 * 
	 * {example} isArgumentUnset("null")) # true
	 */
	public static boolean isArgumentUnset(String value) {
		if (value != null) {
			String trimmed = value.trim();
			if (!trimmed.isEmpty() && !trimmed.equals("null")) {
				return false;
			}
		}

		return true;
	}

	/**
	 * Joins the elements of the provided Collection into a single String containing
	 * the provided elements.
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("encoded") string: String.
	 */
	public static String join(java.util.Collection<String> collection, String separator) {
		String result = "";
		boolean isFirstIteration = true;

		for (String element : collection) {
			if (isFirstIteration) {
				isFirstIteration = false;
			} else {
				result += separator;
			}

			result += element;
		}

		return result;
	}

	/**
	 * Joins the elements of the provided Collection into a single String containing
	 * the toString-representation of provided elements.
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("encoded") string: String.
	 */
	public static String joinAsString(Iterable<?> in, String separator) {
		StringBuilder sb = new StringBuilder();
		String sep = "";
		for (Object o : in)
			if (o != null) {
				sb.append(sep).append(o.toString());
				sep = separator;
			}
		return sb.toString();
	}

	/**
	 * Decodes an GZipped, Base64-encoded string.
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("encoded") string: String.
	 */
	public static String decodeMessage(String encoded) throws Exception {
		if (encoded == null)
			return null;

		if (encoded.length() == 0)
			return encoded;

		GZIPInputStream gis = new GZIPInputStream(
				new BASE64DecoderStream(new ByteArrayInputStream(encoded.getBytes())));

		final char[] buffer = new char[1024];
		final StringBuilder out = new StringBuilder();
		try {
			final Reader in = new InputStreamReader(gis);
			try {
				for (;;) {
					int rsz = in.read(buffer, 0, buffer.length);
					if (rsz < 0)
						break;
					out.append(buffer, 0, rsz);
				}
			} finally {
				in.close();
			}
		} catch (Exception ex) {
			throw ex;
		}

		return out.toString();
	}

	/**
	 * Encodes a plain message to a GZipped, Base64-encoded string.
	 * 
	 * {Category} StringUtil
	 * 
	 * {talendTypes} String
	 * 
	 * {param} object("plain") string: String.
	 */
	public static String encodeMessage(String plain) throws Exception {

		if (plain == null)
			return null;

		if (plain.length() == 0)
			return plain;

		ByteArrayOutputStream bos = new ByteArrayOutputStream();
		try {
			GZIPOutputStream gos = new GZIPOutputStream(new BASE64EncoderStream(bos));
			try {
				gos.write(plain.getBytes());
			} finally {
				gos.close();
			}
		} catch (Exception e) {
			throw e;
		}

		return bos.toString();
	}

	/**
	 * Determines whether the expression is numeric or not.
	 * 
	 * {talendTypes} boolean | Boolean
	 * 
	 * {Category} StringUtil
	 * 
	 * {param} string("abcdefg") input: String need to be known whether is numeric
	 * or not.
	 * 
	 * {example} isNumeric("abcdefg") # false
	 */
	public static boolean isNumeric(String input) {
		if (input != null && !input.isEmpty()) {
			char[] val = input.toCharArray();

			for (int i = 0; i < val.length - 1; i++) {
				if (val[i] > '9' || val[i] < '0') {
					return false;
				}
			}

			return true;
		}
		return false;
	}

}
