text:
// Author: abi
// Project: DungeonQuest
// Path: C:\code\Xna\DungeonQuest\Helpers
// Creation date: 28.03.2007 01:01
// Last modified: 31.07.2007 04:41

#region Using directives
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.Text;
#endregion

namespace DungeonQuest.Helpers
{
	/// <summary>
	/// StringHelper: Provides additional or simplified string functions.
	/// This class does also offer a lot of powerful functions and
	/// allows complicated string operations.
	/// Easy functions at the beginning, harder ones later.
	/// </summary>
	public class StringHelper
	{
		#region Constants
		public const string NewLine = "\r\n";
		#endregion

		#region Constructor (it is not allowed to instantiate)
		/// <summary>
		/// Don't allow instantiating this class.
		/// </summary>
		private StringHelper()
		{
		} // StringHelper()
		#endregion

		#region Comparing, Counting and Extraction
		/// <summary>
		/// Check if a string (s1, can be longer as s2) begins with another
		/// string (s2), only if s1 begins with the same string data as s2,
		/// true is returned, else false. The string compare is case insensitive.
		/// </summary>
		static public bool BeginsWith(string s1, string s2)
		{
			return String.Compare(s1, 0, s2, 0, s2.Length, true,
				CultureInfo.CurrentCulture) == 0;
		} // BeginsWith(s1, s2)

		/// <summary>
		/// Helps to compare strings, uses case insensitive comparison.
		/// String.Compare is also gay because we have always to check for == 0.
		/// </summary>
		static public bool Compare(string s1, string s2)
		{
			return String.Compare(s1, s2, true,
				CultureInfo.CurrentCulture) == 0;
		} // Compare(s1, s2)

		/// <summary>
		/// Helps to compare strings, uses case insensitive comparison.
		/// String.Compare is also gay because we have always to check for == 0.
		/// This overload allows multiple strings to be checked, if any of
		/// them matches we are good to go (e.g. ("hi", {"hey", "hello", "hi"})
		/// will return true).
		/// </summary>
		static public bool Compare(string s1, string[] anyMatch)
		{
			if (anyMatch == null)
				throw new ArgumentNullException("anyMatch",
					"Unable to execute method without valid anyMatch array.");

			foreach (string match in anyMatch)
				if (String.Compare(s1, match, true,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // Compare(s1, anyMatch)

		/// <summary>
		/// Is a specific name in a list of strings?
		/// </summary>
		static public bool IsInList(
			string name,
			ArrayList list,
			bool ignoreCase)
		{
			if (list == null)
				throw new ArgumentNullException("list",
					"Unable to execute method without valid list.");

			foreach (string listEntry in list)
				if (String.Compare(name, listEntry, ignoreCase,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // IsInList(name, list, ignoreCase)

		/// <summary>
		/// Is a specific name in a list of strings?
		/// </summary>
		static public bool IsInList(
			string name,
			string[] list,
			bool ignoreCase)
		{
			if (list == null)
				throw new ArgumentNullException("list",
					"Unable to execute method without valid list.");

			foreach (string listEntry in list)
				if (String.Compare(name, listEntry, ignoreCase,
					CultureInfo.CurrentCulture) == 0)
					return true;
			return false;
		} // IsInList(name, list, ignoreCase)

		/// <summary>
		/// Count words in a text (words are only separated by ' ' (spaces))
		/// </summary>
		static public int CountWords(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			return text.Split(new char[] { ' ' }).Length;
		} // CountWords(text)

		/// <summary>
		/// Compare char case insensitive
		/// </summary>
		/// <param name="c1">C 1</param>
		/// <param name="c2">C 2</param>
		/// <returns>Bool</returns>
		public static bool CompareCharCaseInsensitive(char c1, char c2)
		{
			return char.ToLower(c1) == char.ToLower(c2);
			// Another way (slower):
			// return String.Compare("" + c1, "" + c2, true) == 0;
		} // CompareCharCaseInsensitive(c1, c2)

		/// <summary>
		/// Get last word
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		public static string GetLastWord(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			string[] words = text.Split(new char[] { ' ' });
			if (words.Length > 0)
				return words[words.Length - 1];
			return text;
		} // GetLastWord(text)

		/// <summary>
		/// Remove last word
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		public static string RemoveLastWord(string text)
		{
			string lastWord = GetLastWord(text);
			// Fix 2004-10-08: new length can be 0 for killing first word
			if (text == lastWord)
				return "";
			else if (lastWord.Length == 0 || text.Length == 0 ||
				text.Length - lastWord.Length - 1 <= 0)
				return text;
			else
				return text.Substring(0, text.Length - lastWord.Length - 1);
		} // RemoveLastWord(text)

		/// <summary>
		/// Get all spaces and tabs at beginning
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>String</returns>
		static public string GetAllSpacesAndTabsAtBeginning(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			StringBuilder ret = new StringBuilder();
			for (int pos = 0; pos < text.Length; pos++)
			{
				if (text[pos] == ' ' ||
					text[pos] == '\t')
					ret.Append(text[pos]);
				else
					break;
			} // for (pos)
			return ret.ToString();
		} // GetAllSpacesAndTabsAtBeginning(text)

		/// <summary>
		/// Get tab depth
		/// </summary>
		/// <param name="text">Text</param>
		/// <returns>Int</returns>
		static public int GetTabDepth(string text)
		{
			for (int textPos = 0; textPos < text.Length; textPos++)
				if (text[textPos] != '\t')
					return textPos;
			return text.Length;
		} // GetTabDepth(text)

		/// <summary>
		/// Check string word length
		/// </summary>
		public static string CheckStringWordLength(
			string originalText, int maxLength)
		{
			if (originalText == null)
				throw new ArgumentNullException("originalText",
					"Unable to execute method without valid text.");

			string[] splitted = originalText.Split(new char[] { ' ' });
			string ret = "";
			foreach (string word in splitted)
			{
				if (word.Length <= maxLength)
					ret += word + " ";
				else
				{
					for (int i = 0; i < word.Length / maxLength; i++)
						ret += word.Substring(i * maxLength, maxLength) + " ";
				} // else
			} // foreach (word, splitted)
			return ret.TrimEnd();
		} // CheckStringWordLength(originalText, maxLength)
		#endregion

		#region String contains (for case insensitive compares)
		/// <summary>
		/// Is searchName contained in textToCheck, will check case insensitive,
		/// for a normal case sensitive test use textToCheck.Contains(searchName)
		/// </summary>
		/// <param name="textToCheck">Text to check</param>
		/// <param name="searchName">Search name</param>
		/// <returns>Bool</returns>
		public static bool Contains(string textToCheck, string searchName)
		{
			return textToCheck.ToLower().Contains(searchName.ToLower());
		} // Contains(textToCheck, searchName)

		/// <summary>
		/// Is any of the names in searchNames contained in textToCheck,
		/// will check case insensitive, for a normal case sensitive test
		/// use textToCheck.Contains(searchName).
		/// </summary>
		/// <param name="textToCheck">String to check</param>
		/// <param name="searchNames">Search names</param>
		/// <returns>Bool</returns>
		public static bool Contains(string textToCheck, string[] searchNames)
		{
			string stringToCheckLower = textToCheck.ToLower();
			foreach (string name in searchNames)
				if (stringToCheckLower.Contains(name.ToLower()))
					return true;
			// Nothing found, no searchNames is contained in textToCheck
			return false;
		} // Contains(textToCheck, searchNames)
		#endregion

		#region Write data
		/// <summary>
		/// Returns a string with the array data, byte array version.
		/// </summary>
		static public string WriteArrayData(byte[] byteArray)
		{
			StringBuilder ret = new StringBuilder();
			if (byteArray != null)
				for (int i = 0; i < byteArray.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						byteArray[i].ToString(CultureInfo.InvariantCulture.NumberFormat));
			return ret.ToString();
		} // WriteArrayData(byteArray)

		/// <summary>
		/// Returns a string with the array data, int array version.
		/// </summary>
		static public string WriteArrayData(int[] intArray)
		{
			StringBuilder ret = new StringBuilder();
			if (intArray != null)
				for (int i = 0; i < intArray.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						intArray[i].ToString(CultureInfo.InvariantCulture.NumberFormat));
			return ret.ToString();
		} // WriteArrayData(intArray)

		/// <summary>
		/// Returns a string with the array data, general array version.
		/// </summary>
		static public string WriteArrayData(Array array)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				for (int i = 0; i < array.Length; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						(array.GetValue(i) == null ?
						"null" : array.GetValue(i).ToString()));
			return ret.ToString();
		} // WriteArrayData(array)

		/// <summary>
		/// Returns a string with the array data, general array version
		/// with maxLength bounding (will return string with max. this
		/// number of entries).
		/// </summary>
		static public string WriteArrayData(Array array, int maxLength)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				for (int i = 0; i < array.Length && i < maxLength; i++)
					ret.Append((ret.Length == 0 ? "" : ", ") +
						array.GetValue(i).ToString());
			return ret.ToString();
		} // WriteArrayData(array, maxLength)

		/// <summary>
		/// Returns a string with the array data, ArrayList version.
		/// </summary>
		static public string WriteArrayData(ArrayList array)
		{
			StringBuilder ret = new StringBuilder();
			if (array != null)
				foreach (object obj in array)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(array)

		/// <summary>
		/// Returns a string with the array data, CollectionBase version.
		/// </summary>
		static public string WriteArrayData(CollectionBase collection)
		{
			StringBuilder ret = new StringBuilder();
			if (collection != null)
				foreach (object obj in collection)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(collection)

#if !XBOX360
		/// <summary>
		/// Returns a string with the array data, StringCollection version.
		/// </summary>
		static public string WriteArrayData(StringCollection textCollection)
		{
			StringBuilder ret = new StringBuilder();
			if (textCollection != null)
				foreach (string s in textCollection)
					ret.Append((ret.Length == 0 ? "" : ", ") + s);
			return ret.ToString();
		} // WriteArrayData(textCollection)
#endif

		/// <summary>
		/// Returns a string with the array data, enumerable class version.
		/// </summary>
		static public string WriteArrayData(IEnumerable enumerableClass)
		{
			StringBuilder ret = new StringBuilder();
			if (enumerableClass != null)
				foreach (object obj in enumerableClass)
					ret.Append((ret.Length == 0 ? "" : ", ") + obj.ToString());
			return ret.ToString();
		} // WriteArrayData(enumerableClass)

		/// <summary>
		/// Write into space string, useful for writing parameters without
		/// knowing the length of each string, e.g. when writing numbers
		/// (-1, 1.45, etc.). You can use this function to give all strings
		/// the same width in a table. Maybe there is already a string function
		/// for this, but I don't found any useful stuff.
		/// </summary>
		static public string WriteIntoSpaceString(string message, int spaces)
		{
			if (message == null)
				throw new ArgumentNullException("message",
					"Unable to execute method without valid text.");

			// Msg is already that long or longer?
			if (message.Length >= spaces)
				return message;

			// Create string with number of specified spaces
			char[] ret = new char[spaces];

			// Copy data
			int i;
			for (i = 0; i < message.Length; i++)
				ret[i] = message[i];
			// Fill rest with spaces
			for (i = message.Length; i < spaces; i++)
				ret[i] = ' ';

			// Return result
			return new string(ret);
		} // WriteIntoSpaceString(message, spaces)

		/// <summary>
		/// Write Iso Date (Year-Month-Day)
		/// </summary>
		public static string WriteIsoDate(DateTime date)
		{
			return date.Year + "-" +
				date.Month.ToString("00") + "-" +
				date.Day.ToString("00");
		} // WriteIsoDate(date)

		/// <summary>
		/// Write Iso Date and time (Year-Month-Day Hour:Minute)
		/// </summary>
		public static string WriteIsoDateAndTime(DateTime date)
		{
			return date.Year + "-" +
				date.Month.ToString("00") + "-" +
				date.Day.ToString("00") + " " +
				date.Hour.ToString("00") + ":" +
				date.Minute.ToString("00");
		} // WriteIsoDateAndTime(date)

		/// <summary>
		/// Write internet time
		/// </summary>
		/// <param name="time">Time</param>
		/// <param name="daylightSaving">Daylight saving</param>
		/// <returns>String</returns>
		public static string WriteInternetTime(
			DateTime time,
			bool daylightSaving)
		{
			return "@" + ((float)((int)(time.ToUniversalTime().AddHours(
				daylightSaving ? 1 : 0).TimeOfDay.
				TotalSeconds * 100000 / (24 * 60 * 60))) / 100.0f).ToString(
				NumberFormatInfo.InvariantInfo);
		} // WriteInternetTime(time, daylightSaving)
		#endregion

		#region Convert methods
		/// <summary>
		/// Convert string data to int array, string must be in the form
		/// "1, 3, 8, 7", etc. WriteArrayData is the complementar function.
		/// </summary>
		/// <returns>int array, will be null if string is invalid!</returns>
		static public int[] ConvertStringToIntArray(string s)
		{
			// Invalid?
			if (s == null || s.Length == 0)
				return null;

			string[] splitted = s.Split(new char[] { ' ' });
			List<int> ret = new List<int>();
			for (int i = 0; i < splitted.Length; i++)
				if (String.IsNullOrEmpty(splitted[i]) == false)
				{
					try
					{
						ret.Add(Convert.ToInt32(splitted[i]));
					} // try
					catch { } // ignore
				} // for (i)
			return ret.ToArray();
		} // ConvertStringToIntArray(str)

		/// <summary>
		/// Convert string data to float array, string must be in the form
		/// "1.5, 3.534, 8.76, 7.49", etc. WriteArrayData is the complementar
		/// function.
		/// </summary>
		/// <returns>float array, will be null if string is invalid!</returns>
		static public float[] ConvertStringToFloatArray(string s)
		{
			// Invalid?
			if (s == null || s.Length == 0)
				return null;

			/*first try
			string[] splitted = s.Split(new char[] { ' ' });
			List<float> ret = new List<float>();
			for (int i = 0; i < splitted.Length; i++)
				if (String.IsNullOrEmpty(splitted[i]) == false)
				{
					try
					{
						ret.Add(Convert.ToSingle(splitted[i],
							CultureInfo.InvariantCulture));
					} // try
					catch { } // ignore
				} // for (i)
			return ret.ToArray();
			/*/

			// Use quick conversion.
			// NOTE: The input string must be well formed, else this won't work,
			// only 0-9, . and spaces are allowed!
			List<float> ret = new List<float>();
			float numberSoFar = 0.0f;
			// Decimal factor, goes to 10, 100, etc. and to 0.1, 0.01, etc.
			float decimalFactor = 1;
			bool afterPoint = false;
			bool negativeNumber = false;
			// Go through every letter
			for (int i = 0; i < s.Length; i++)
			{
				Char ch = s[i];
				// Is this a digit?
				if (ch >= '0' && ch <= '9')
				{
					int digit = ch - '0';
					// Add either before or after decimal point
					if (afterPoint == false)
					{
						//Log.Write("before: numberSoFar=" + numberSoFar +
						//	", decimalFactor=" + decimalFactor);
						// Reverse engeneering ^^
						numberSoFar = numberSoFar * decimalFactor + digit;
						decimalFactor = 10;
						//Log.Write("numberSoFar=" + numberSoFar +
						//	", decimalFactor=" + decimalFactor);
					} // if (afterPoint == false)
					else
					{
						//Log.Write("digit before: numberSoFar=" + numberSoFar +
						//	", decimalFactor=" + decimalFactor);
						// Its a little bit easier for digits after the point
						numberSoFar += digit * decimalFactor;
						decimalFactor /= 10.0f;
						//Log.Write("digit: numberSoFar=" + numberSoFar +
						//	", decimalFactor=" + decimalFactor);
					} // else
				} // if (ch)
				else if (ch == '-')
				{
					negativeNumber = true;
				} // else if
				else if (ch == '.')
				{
					decimalFactor = 0.1f;
					afterPoint = true;
				} // else if
				else if (ch == ' ')
				{
					// Flush
					if (negativeNumber)
						numberSoFar = -numberSoFar;
					ret.Add(numberSoFar);
					numberSoFar = 0;
					decimalFactor = 1;
					afterPoint = false;
					negativeNumber = false;
				} // else if
			} // for (int)

			// Add the last number we got
			ret.Add(numberSoFar);

			//Log.Write("s=" + s);
			//Log.Write("ret=" + StringHelper.WriteArrayData(ret));

			return ret.ToArray();
			//*/
		} // ConvertStringToIntArray(str)
		#endregion

		#region File stuff
		/// <summary>
		/// Extracts filename from full path+filename, cuts of extension
		/// if cutExtension is true. Can be also used to cut of directories
		/// from a path (only last one will remain).
		/// </summary>
		static public string ExtractFilename(string pathFile, bool cutExtension)
		{
			if (pathFile == null)
				return "";

			// Update 2006-09-29: also checking for normal slashes, needed
			// for support reading 3ds max stuff.
			string[] fileName = pathFile.Split(new char[] { '\\', '/' });
			if (fileName.Length == 0)
			{
				if (cutExtension)
					return CutExtension(pathFile);
				return pathFile;
			} // if (fileName.Length)

			if (cutExtension)
				return CutExtension(fileName[fileName.Length - 1]);
			return fileName[fileName.Length - 1];
		} // ExtractFilename(pathFile, cutExtension)

		/// <summary>
		/// Get directory of path+File, if only a path is given we will cut off
		/// the last sub path!
		/// </summary>
		static public string GetDirectory(string pathFile)
		{
			if (pathFile == null)
				return "";
			int i = pathFile.LastIndexOf("\\");
			if (i >= 0 && i < pathFile.Length)
				// Return directory
				return pathFile.Substring(0, i);
			// No sub directory found (parent of some dir is "")
			return "";
		} // GetDirectory(pathFile)

		/// <summary>
		/// Same as GetDirectory(): Get directory of path+File,
		/// if only a path is given we will cut of the last sub path!
		/// </summary>
		static public string CutOneFolderOff(string path)
		{
			// GetDirectory does exactly what we need!
			return GetDirectory(path);
		} // CutOneFolderOff(path)

		/// <summary>
		/// Splits a path into all parts of its directories,
		/// e.g. "maps\\sub\\kekse" becomes
		/// {"maps\\sub\\kekse","maps\\sub","maps"}
		/// </summary>
		static public string[] SplitDirectories(string path)
		{
			ArrayList localList = new ArrayList();
			localList.Add(path);
			do
			{
				path = CutOneFolderOff(path);
				if (path.Length > 0)
					localList.Add(path);
			} while (path.Length > 0);

			return (string[])localList.ToArray(typeof(string));
		} // SplitDirectories(path)

		/// <summary>
		/// Remove first directory of path (if one exists).
		/// e.g. "maps\\mymaps\\hehe.map" becomes "mymaps\\hehe.map"
		/// Also used to cut first folder off, especially useful for relative
		/// paths. e.g. "maps\\test" becomes "test"
		/// </summary>
		static public string RemoveFirstDirectory(string path)
		{
			int i = path.IndexOf("\\");
			if (i >= 0 && i < path.Length)
				// Return rest of path
				return path.Substring(i + 1);
			// No first directory found, just return original path
			return path;
		} // RemoveFirstDirectory(path)

		/// <summary>
		/// Check if a folder is a direct sub folder of a main folder.
		/// True is only returned if this is a direct sub folder, not if
		/// it is some sub folder few levels below.
		/// </summary>
		static public bool IsDirectSubfolder(string subfolder, string mainFolder)
		{
			// First check if subFolder is really a sub folder of mainFolder
			if (subfolder != null &&
				subfolder.StartsWith(mainFolder))
			{
				// Same order?
				if (subfolder.Length < mainFolder.Length + 1)
					// Then it ain't a sub folder!
					return false;
				// Ok, now check if this is direct sub folder or some sub folder
				// of mainFolder sub folder
				string folder = subfolder.Remove(0, mainFolder.Length + 1);
				// Check if this is really a direct sub folder
				for (int i = 0; i < folder.Length; i++)
					if (folder[i] == '\\')
						// No, this is a sub folder of mainFolder sub folder
						return false;
				// Ok, this is a direct sub folder of mainFolder!
				return true;
			} // if (subFolder)
			// Not even any sub folder!
			return false;
		} // IsDirectSubFolder(subFolder, mainFolder)

		/// <summary>
		/// Cut of extension, e.g. "hi.txt" becomes "hi"
		/// </summary>
		static public string CutExtension(string file)
		{
			if (file == null)
				return "";
			int l = file.LastIndexOf('.');
			if (l > 0)
				return file.Remove(l, file.Length - l);
			return file;
		} // CutExtension(file)

		/// <summary>
		/// Get extension (the stuff behind that '.'),
		/// e.g. "test.bmp" will return "bmp"
		/// </summary>
		static public string GetExtension(string file)
		{
			if (file == null)
				return "";
			int l = file.LastIndexOf('.');
			if (l > 0 && l < file.Length)
				return file.Remove(0, l + 1);
			return "";
		} // GetExtension(file)
		#endregion

		#region String splitting and getting it back together
		/// <summary>
		/// Performs basically the same job as String.Split, but does
		/// trim all parts, no empty parts are returned, e.g.
		/// "hi  there" returns "hi", "there", String.Split would return
		/// "hi", "", "there".
		/// </summary>
		public static string[] SplitAndTrim(string text, char separator)
		{
			ArrayList ret = new ArrayList();
			string[] splitted = text.Split(new char[] { separator });
			foreach (string s in splitted)
				if (s.Length > 0)
					ret.Add(s.Trim());
			return (string[])ret.ToArray(typeof(string));
		} // SplitAndTrim(text, separator)

		/// <summary>
		/// Splits a multi line string to several strings and
		/// returns the result as a string array.
		/// Will also remove any \r, \n or space character
		/// at the end of each line!
		/// </summary>
		public static string[] SplitMultilineText(string text)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			ArrayList ret = new ArrayList();
			// Supports any format, only \r, only \n, normal \n\r,
			// crazy \r\n or even mixed \n\r with any format
			string[] splitted1 = text.Split(new char[] { '\n' });
			string[] splitted2 = text.Split(new char[] { '\r' });
			string[] splitted =
				splitted1.Length >= splitted2.Length ?
			splitted1 : splitted2;

			foreach (string s in splitted)
			{
				// Never add any \r or \n to the single lines
				if (s.EndsWith("\r") ||
					s.EndsWith("\n"))
					ret.Add(s.Substring(0, s.Length - 1));
				else if (s.StartsWith("\n") ||
					s.StartsWith("\r"))
					ret.Add(s.Substring(1));
				else
					ret.Add(s);
			} // foreach (s, splitted)

			return (string[])ret.ToArray(typeof(string));
		} // SplitMultiLineText(text)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <param name="startLine">Start line</param>
		/// <param name="startOffset">Start offset</param>
		/// <param name="endLine">End line</param>
		/// <param name="endOffset">End offset</param>
		/// <param name="separator">Separator</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(
			string[] lines,
			int startLine, int startOffset,
			int endLine, int endOffset,
			string separator)
		{
			if (lines == null)
				throw new ArgumentNullException("lines",
					"Unable to execute method without valid lines.");

			// Check if all values are in range (correct if not)
			if (startLine >= lines.Length)
				startLine = lines.Length - 1;
			if (endLine >= lines.Length)
				endLine = lines.Length - 1;
			if (startLine < 0)
				startLine = 0;
			if (endLine < 0)
				endLine = 0;
			if (startOffset >= lines[startLine].Length)
				startOffset = lines[startLine].Length - 1;
			if (endOffset >= lines[endLine].Length)
				endOffset = lines[endLine].Length - 1;
			if (startOffset < 0)
				startOffset = 0;
			if (endOffset < 0)
				endOffset = 0;

			StringBuilder builder = new StringBuilder((endLine - startLine) * 80);
			for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
			{
				if (lineNumber == startLine)
					builder.Append(lines[lineNumber].Substring(startOffset));
				else if (lineNumber == endLine)
					builder.Append(lines[lineNumber].Substring(0, endOffset + 1));
				else
					builder.Append(lines[lineNumber]);

				if (lineNumber != endLine)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, startLine, startOffset)

		static public string BuildStringFromLines(
			string[] lines, string separator)
		{
			StringBuilder builder = new StringBuilder(lines.Length * 80);
			for (int lineNumber = 0; lineNumber < lines.Length; lineNumber++)
			{
				builder.Append(lines[lineNumber]);
				if (lineNumber != lines.Length - 1)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, separator)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(string[] lines)
		{
			return BuildStringFromLines(lines, NewLine);
		} // BuildStringFromLines(lines)

		/// <summary>
		/// Build string from lines
		/// </summary>
		/// <param name="lines">Lines</param>
		/// <param name="startLine">Start line</param>
		/// <param name="endLine">End line</param>
		/// <param name="separator">Separator</param>
		/// <returns>String</returns>
		static public string BuildStringFromLines(
			string[] lines,
			int startLine,
			int endLine,
			string separator)
		{
			// Check if all values are in range (correct if not)
			if (startLine < 0)
				startLine = 0;
			if (endLine < 0)
				endLine = 0;
			if (startLine >= lines.Length)
				startLine = lines.Length - 1;
			if (endLine >= lines.Length)
				endLine = lines.Length - 1;

			StringBuilder builder = new StringBuilder((endLine - startLine) * 80);
			for (int lineNumber = startLine; lineNumber <= endLine; lineNumber++)
			{
				builder.Append(lines[lineNumber]);
				if (lineNumber != endLine)
					builder.Append(separator);
			} // for (lineNumber)
			return builder.ToString();
		} // BuildStringFromLines(lines, startLine, endLine)

		/// <summary>
		/// Cut modes
		/// </summary>
		public enum CutMode
		{
			Begin,
			End,
			BothEnds
		} // enum CutMode

		/// <summary>
		/// Maximum string length
		/// </summary>
		/// <param name="originalText">Original text</param>
		/// <param name="maxLength">Maximum length</param>
		/// <param name="cutMode">Cut mode</param>
		/// <returns>String</returns>
		public static string MaxStringLength(string originalText,
			int maxLength, CutMode cutMode)
		{
			if (originalText.Length <= maxLength)
				return originalText;

			if (cutMode == CutMode.Begin)
				return originalText.Substring(
					originalText.Length - maxLength, maxLength);
			else if (cutMode == CutMode.End)
				return originalText.Substring(0, maxLength);
			else // logic: if ( cutMode == CutModes.BothEnds )
				return originalText.Substring(
					(originalText.Length - maxLength) / 2, maxLength);
		} // MaxStringLength(originalText, maxLength, cutMode)

		/// <summary>
		/// Get left part of everything to the left of the first
		/// occurrence of a character.
		/// </summary>
		public static string GetLeftPartAtFirstOccurence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.IndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(0, index);
		} // GetLeftPartAtFirstOccurence(sourceText, ch)

		/// <summary>
		/// Get right part of everything to the right of the first
		/// occurrence of a character.
		/// </summary>
		public static string GetRightPartAtFirstOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.IndexOf(ch);
			if (index == -1)
				return "";

			return sourceText.Substring(index + 1);
		} // GetRightPartAtFirstOccurrence(sourceText, ch)

		/// <summary>
		/// Get left part of everything to the left of the last
		/// occurrence of a character.
		/// </summary>
		public static string GetLeftPartAtLastOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.LastIndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(0, index);
		} // GetLeftPartAtLastOccurrence(sourceText, ch)

		/// <summary>
		/// Get right part of everything to the right of the last
		/// occurrence of a character.
		/// </summary>
		public static string GetRightPartAtLastOccurrence(
			string sourceText, char ch)
		{
			if (sourceText == null)
				throw new ArgumentNullException("sourceText",
					"Unable to execute this method without valid string.");

			int index = sourceText.LastIndexOf(ch);
			if (index == -1)
				return sourceText;

			return sourceText.Substring(index + 1);
		} // GetRightPartAtLastOccurrence(sourceText, ch)

		/// <summary>
		/// Create password string
		/// </summary>
		/// <param name="originalText">Original text</param>
		/// <returns>String</returns>
		public static string CreatePasswordString(string originalText)
		{
			if (originalText == null)
				throw new ArgumentNullException("originalText",
					"Unable to execute this method without valid string.");

			string passwordString = "";
			for (int i = 0; i < originalText.Length; i++)
				passwordString += "*";
			return passwordString;
		} // CreatePasswordString(originalText)

		/// <summary>
		/// Helper function to convert letter to lowercase. Could someone
		/// tell me the reason why there is no function for that in char?
		/// </summary>
		public static char ToLower(char letter)
		{
			return (char)letter.ToString().ToLower(
				CultureInfo.InvariantCulture)[0];
		} // ToLower(letter)

		/// <summary>
		/// Helper function to convert letter to uppercase. Could someone
		/// tell me the reason why there is no function for that in char?
		/// </summary>
		public static char ToUpper(char letter)
		{
			return (char)letter.ToString().ToUpper(
				CultureInfo.InvariantCulture)[0];
		} // ToUpper(letter)

		/// <summary>
		/// Helper function to check if this is an lowercase letter.
		/// </summary>
		public static bool IsLowercaseLetter(char letter)
		{
			return letter == ToLower(letter);
		} // IsLowercaseLetter(letter)

		/// <summary>
		/// Helper function to check if this is an uppercase letter.
		/// </summary>
		public static bool IsUppercaseLetter(char letter)
		{
			return letter == ToUpper(letter);
		} // IsUppercaseLetter(letter)

		/// <summary>
		/// Helper function for SplitFunctionNameToWordString to detect
		/// abbreviations in the function name
		/// </summary>
		private static int GetAbbreviationLengthInFunctionName(
			string functionName, int startPos)
		{
			StringBuilder abbreviation = new StringBuilder();
			// Go through string until we reach a lower letter or it ends
			for (int pos = startPos; pos < functionName.Length; pos++)
			{
				// Quit if its not an uppercase letter
				if (StringHelper.IsUppercaseLetter(functionName[pos]) == false)
					break;
				// Else just add letter
				abbreviation.Append(functionName[pos]);
			} // for (pos)

			// Abbreviation has to be at least 2 letters long.
			if (abbreviation.Length >= 2)
			{
				// If not at end of functionName, last letter belongs to next name,
				// e.g. "TW" is not a abbreviation in "HiMrTWhatsUp",
				// "AB" isn't one either in "IsABall",
				// but "PQ" is in "PQList" and "AB" is in "MyAB"
				if (startPos + abbreviation.Length >= functionName.Length)
					// Ok, then return full abbreviation length
					return abbreviation.Length;
				// Else return length - 1 because of next word
				return abbreviation.Length - 1;
			} // if (abbreviation.Length)

			// No Abbreviation, just return 1
			return 1;
		} // GetAbbreviationLengthInFunctionName(functionName, startPos)

		/// <summary>
		/// Checks if letter is space ' ' or any punctuation (. , : ; ' " ! ?)
		/// </summary>
		public static bool IsSpaceOrPunctuation(char letter)
		{
			return
				letter == ' ' ||
				letter == '.' ||
				letter == ',' ||
				letter == ':' ||
				letter == ';' ||
				letter == '\'' ||
				letter == '\"' ||
				letter == '!' ||
				letter == '?' ||
				letter == '*';
		} // IsSpaceOrPunctuation(letter)

		/// <summary>
		/// Splits a function name to words, e.g.
		/// "MakeDamageOnUnit" gets "Make damage on unit".
		/// Will also detect abbreviation like TCP and leave them
		/// intact, e.g. "CreateTCPListener" gets "Create TCP listener".
		/// </summary>
		public static string SplitFunctionNameToWordString(string functionName)
		{
			if (functionName == null ||
				functionName.Length == 0)
				return "";

			string ret = "";
			// Go through functionName and find big letters!
			for (int pos = 0; pos < functionName.Length; pos++)
			{
				char letter = functionName[pos];
				// First letter is always big!
				if (pos == 0 ||
					pos == 1 && StringHelper.IsUppercaseLetter(functionName[1]) &&
					StringHelper.IsUppercaseLetter(functionName[0]) ||
					pos == 2 && StringHelper.IsUppercaseLetter(functionName[2]) &&
					StringHelper.IsUppercaseLetter(functionName[1]) &&
					StringHelper.IsUppercaseLetter(functionName[0]))
					ret += StringHelper.ToUpper(letter);
					// Found uppercase letter?
				else if (StringHelper.IsUppercaseLetter(letter) &&
					//also support numbers and other symbols not lower/upper letter:
					//StringHelper.IsLowercaseLetter(letter) == false &&
					// But don't allow space or any punctuation (. , : ; ' " ! ?)
					StringHelper.IsSpaceOrPunctuation(letter) == false &&
					ret.EndsWith(" ") == false)
				{
					// Could be new word, but we have to check if its an abbreviation
					int abbreviationLength = GetAbbreviationLengthInFunctionName(
						functionName, pos);
					// Found valid abbreviation?
					if (abbreviationLength > 1)
					{
						// Then add it
						ret += " " + functionName.Substring(pos, abbreviationLength);
						// And advance pos (abbreviation is longer than 1 letter)
						pos += abbreviationLength - 1;
					} // if (abbreviationLength)
					// Else just add new word (in lower letter)
					else
						ret += " " +
							//StringHelper.ToLower(letter);
							// For this game we always want big letters
							StringHelper.ToUpper(letter);
				} // else if
				else
					// Just add letter
					ret += letter;
			} // for (pos)
			return ret;
		} // SplitFunctionNameToWordString(functionName)
		#endregion

		#region Remove character
		/// <summary>
		/// Remove character from text.
		/// </summary>
		/// <param name="text">Text</param>
		/// <param name="characterToBeRemoved">Character to be removed</param>
		public static void RemoveCharacter(ref string text,
			char characterToBeRemoved)
		{
			if (text == null)
				throw new ArgumentNullException("text",
					"Unable to execute method without valid text.");

			if (text.Contains(characterToBeRemoved.ToString()))
				text = text.Replace(characterToBeRemoved.ToString(), "");
		} // RemoveCharacter(text, characterToBeRemoved)
		#endregion

		#region Kb/mb name generator
		/// <summary>
		/// Write bytes, KB, MB, GB, TB message.
		/// 1 KB = 1024 Bytes
		/// 1 MB = 1024 KB = 1048576 Bytes
		/// 1 GB = 1024 MB = 1073741824 Bytes
		/// 1 TB = 1024 GB = 1099511627776 Bytes
		/// E.g. 100 will return "100 Bytes"
		/// 2048 will return "2.00 KB"
		/// 2500 will return "2.44 KB"
		/// 1534905 will return "1.46 MB"
		/// 23045904850904 will return "20.96 TB"
		/// </summary>
		public static string WriteBigByteNumber(
			long bigByteNumber, string decimalSeperator)
		{
			if (bigByteNumber < 0)
				return "-" + WriteBigByteNumber(-bigByteNumber);

			if (bigByteNumber <= 999)
				return bigByteNumber + " Bytes";
			if (bigByteNumber <= 999 * 1024)
			{
				double fKB = (double)bigByteNumber / 1024.0;
				return (int)fKB + decimalSeperator +
					((int)(fKB * 100.0f) % 100).ToString("00") + " KB";
			} // if
			if (bigByteNumber <= 999 * 1024 * 1024)
			{
				double fMB = (double)bigByteNumber / (1024.0 * 1024.0);
				return (int)fMB + decimalSeperator +
					((int)(fMB * 100.0f) % 100).ToString("00") + " MB";
			} // if
			// this is very big ^^ will not fit into int
			if (bigByteNumber <= 999L * 1024L * 1024L * 1024L)
			{
				double fGB = (double)bigByteNumber / (1024.0 * 1024.0 * 1024.0);
				return (int)fGB + decimalSeperator +
					((int)(fGB * 100.0f) % 100).ToString("00") + " GB";
			} // if
			//if ( num <= 999*1024*1024*1024*1024 )
			//{
			double fTB = (double)bigByteNumber / (1024.0 * 1024.0 * 1024.0 * 1024.0);
			return (int)fTB + decimalSeperator +
				((int)(fTB * 100.0f) % 100).ToString("00") + " TB";
			//} // if
		} // WriteBigByteNumber(num, decimalSeperator)

		/// <summary>
		/// Write bytes, KB, MB, GB, TB message.
		/// 1 KB = 1024 Bytes
		/// 1 MB = 1024 KB = 1048576 Bytes
		/// 1 GB = 1024 MB = 1073741824 Bytes
		/// 1 TB = 1024 GB = 1099511627776 Bytes
		/// E.g. 100 will return "100 Bytes"
		/// 2048 will return "2.00 KB"
		/// 2500 will return "2.44 KB"
		/// 1534905 will return "1.46 MB"
		/// 23045904850904 will return "20.96 TB"
		/// </summary>
		public static string WriteBigByteNumber(long bigByteNumber)
		{
			string decimalSeperator = CultureInfo.CurrentCulture.
				NumberFormat.CurrencyDecimalSeparator;
			return WriteBigByteNumber(bigByteNumber, decimalSeperator);
		} // WriteBigByteNumber(num)
		#endregion

		#region Try parse methods that are not available on the XBox360!
		/// <summary>
		/// Is numeric float
		/// </summary>
		/// <param name="str">Str</param>
		/// <returns>Bool</returns>
		public static bool IsNumericFloat(string str)
		{
			return IsNumericFloat(str, CultureInfo.InvariantCulture.NumberFormat);
		} // IsNumericFloat(str)

		/// <summary>
		/// Allow only one decimal point, used for IsNumericFloat.
		/// </summary>
		/// <param name="str">Input string to check</param>
		/// <param name="numberFormat">Used number format, e.g.
		/// CultureInfo.InvariantCulture.NumberFormat</param>
		/// <return>True if check succeeded, false otherwise</return>
		private static bool AllowOnlyOneDecimalPoint(string str,
			NumberFormatInfo numberFormat)
		{
			char[] strInChars = str.ToCharArray();
			bool hasGroupSeperator = false;
			int decimalSeperatorCount = 0;
			for (int i = 0; i < strInChars.Length; i++)
			{
				if (numberFormat.CurrencyDecimalSeparator.IndexOf(strInChars[i]) == 0)
				{
					decimalSeperatorCount++;
				} // if (numberFormat.CurrencyDecimalSeparator.IndexOf)

				// has float group seperators  ?
				if (numberFormat.CurrencyGroupSeparator.IndexOf(strInChars[i]) == 0)
				{
					hasGroupSeperator = true;
				} // if (numberFormat.CurrencyGroupSeparator.IndexOf)
			} // for (int)

			if (hasGroupSeperator)
			{
				// If first digit is the group seperator or begins with 0,
				// there is something wrong, the group seperator is used as a comma.
				if (str.StartsWith(numberFormat.CurrencyGroupSeparator) ||
					strInChars[0] == '0')
					return false;

				// look only at the digits in front of the decimal point
				string[] splittedByDecimalSeperator = str.Split(
					numberFormat.CurrencyDecimalSeparator.ToCharArray());

				#region Invert the digits for modulo check
				//   ==> 1.000 -> 000.1  ==> only after 3 digits
				char[] firstSplittedInChars = splittedByDecimalSeperator[0].ToCharArray();
				int arrayLength = firstSplittedInChars.Length;
				char[] firstSplittedInCharsInverted = new char[arrayLength];
				for (int i = 0; i < arrayLength; i++)
				{
					firstSplittedInCharsInverted[i] =
						firstSplittedInChars[arrayLength - 1 - i];
				} // for (int)
				#endregion

				// group seperators are only allowed between 3 digits -> 1.000.000
				for (int i = 0; i < arrayLength; i++)
				{
					if (i % 3 != 0 && numberFormat.CurrencyGroupSeparator.IndexOf(
						firstSplittedInCharsInverted[i]) == 0)
					{
						return false;
					} // if (i)
				} // for (int)
			} // if (hasGroupSeperator)
			if (decimalSeperatorCount > 1)
				return false;

			return true;
		} // AllowOnlyOneDecimalPoint(str, numberFormat)

		/// <summary>
		/// Checks if string is numeric float value
		/// </summary>
		/// <param name="str">Input string</param>
		/// <param name="numberFormat">Used number format, e.g.
		/// CultureInfo.InvariantCulture.NumberFormat</param>
		/// <returns>True if str can be converted to a float,
		/// false otherwise</returns>
		public static bool IsNumericFloat(string str,
			NumberFormatInfo numberFormat)
		{
			// Can't be a float if string is not valid!
			if (String.IsNullOrEmpty(str))
				return false;

			//not supported by Convert.ToSingle:
			//if (str.EndsWith("f"))
			//	str = str.Substring(0, str.Length - 1);

			// Only 1 decimal point is allowed
			if (AllowOnlyOneDecimalPoint(str, numberFormat) == false)
				return false;

			// + allows in the first,last,don't allow in middle of the string
			// - allows in the first,last,don't allow in middle of the string
			// $ allows in the first,last,don't allow in middle of the string
			// , allows in the last,middle,don't allow in first char of the string
			// . allows in the first,last,middle, allows in all the indexs
			bool retVal = false;

			// If string is just 1 letter, don't allow it to be a sign
			if (str.Length == 1 &&
				"+-$.,".IndexOf(str[0]) >= 0)
				return false;

			for (int i = 0; i < str.Length; i++)
			{
				// For first indexchar
				char pChar =
					//char.Parse(str.Substring(i, 1));
					Convert.ToChar(str.Substring(i, 1));

				if (retVal)
					retVal = false;

				if ((!retVal) && (str.IndexOf(pChar) == 0))
				{
					retVal = ("+-$.0123456789".IndexOf(pChar) >= 0) ? true : false;
				} // if ()
				// For middle characters
				if ((!retVal) && (str.IndexOf(pChar) > 0) &&
					(str.IndexOf(pChar) < (str.Length - 1)))
				{
					retVal = (",.0123456789".IndexOf(pChar) >= 0) ? true : false;
				} // if ()
				// For last characters
				if ((!retVal) && (str.IndexOf(pChar) == (str.Length - 1)))
				{
					retVal = ("+-$,.0123456789".IndexOf(pChar) >= 0) ? true : false;
				} // if ()

				if (!retVal)
					break;
			} // for (int)

			return retVal;
		} // IsNumericFloat(str, numberFormat)

		/// <summary>
		/// Try to convert to float. Will not modify value if that does not work.
		/// This uses also always the invariant culture.
		/// </summary>
		/// <param name="value">Value</param>
		/// <param name="textToConvert">Text to convert</param>
		public static void TryToConvertToFloat(
			ref float value, string textToConvert)
		{
			TryToConvertToFloat(ref value, textToConvert,
				System.Globalization.NumberFormatInfo.InvariantInfo);
		} // TryToConvertToFloat(value, textToConvert)

		/// <summary>
		/// Try to convert to float. Will not modify value if that does not work.
		/// </summary>
		/// <param name="value">Value</param>
		/// <param name="textToConvert">Text to convert</param>
		/// <param name="format">Format for converting</param>
		public static void TryToConvertToFloat(
			ref float value, string textToConvert, NumberFormatInfo format)
		{
			// Basically the same as float.TryParse(), but faster!
			if (IsNumericFloat(textToConvert, format))
			{
				value = Convert.ToSingle(textToConvert, format);
			} // if (IsNumericFloat)
		} // TryToConvertToFloat(value, textToConvert, format)

		/// <summary>
		/// Check if string is numeric integer. A decimal point is not accepted.
		/// </summary>
		/// <param name="str">String to check</param>
		public static bool IsNumericInt(string str)
		{
			// Can't be an int if string is not valid!
			if (String.IsNullOrEmpty(str))
				return false;

			// Go through every letter in str
			int strPos = 0;
			foreach (char ch in str)
			{
				// Only 0-9 are allowed
				if ("0123456789".IndexOf(ch) < 0 &&
					// Allow +/- for first char
					(strPos > 0 || (ch != '-' && ch != '+')))
					return false;
				strPos++;
			} // foreach (ch in str)

			// All fine, return true, this is a number!
			return true;
		} // IsNumericInt(str)
		#endregion

		#region Unit Testing
#if DEBUG
		/// <summary>
		/// Test convert string to float array
		/// </summary>
		public static void TestConvertStringToFloatArray()
		{
			if (StringHelper.Compare(
				StringHelper.WriteArrayData(ConvertStringToFloatArray(
				"0 1 2 3 4")),
				"0, 1, 2, 3, 4") == false)
				Log.Write("Converting simple float array failed!");

			if (StringHelper.Compare(
				StringHelper.WriteArrayData(ConvertStringToFloatArray(
				"0.124 -1.35 0.0034 305 -94.4534")),
				"0.124, -1.35, 0.0034, 305, -94.4534") == false)
				Log.Write("Converting complex float array failed!");
		} // TestConvertStringToFloatArray()

		/*include NUnit.Framework for this
		/// <summary>
		/// String helper tests
		/// </summary>
		//[TestFixture]
		public class StringHelperTests
		{
			/// <summary>
			/// Test is in list
			/// </summary>
			//[Test]
			public void TestIsInList()
			{
				Assert.IsTrue(IsInList("whats",
					new string[] { "hi", "whats", "up?" }, false));
				Assert.IsFalse(IsInList("no way",
					new string[] { "omg", "no no", "there is no way!" }, false));
			} // TestIsInList()

			/// <summary>
			/// Test write array
			/// </summary>
			//[Test]
			public void TestWriteArray()
			{
				Assert.AreEqual(
					"3, 5, 10",
					WriteArrayData(new int[] { 3, 5, 10 }));
				Assert.AreEqual(
					"one, after, another",
					WriteArrayData(new string[] { "one", "after", "another" }));
				List<string> genericList = new List<string>();
				genericList.Add("whats");
				genericList.AddRange(new string[] { "going", "on" });
				Assert.AreEqual(
					"whats, going, on",
					WriteArrayData(genericList));
			} // TestWriteArray()

			/// <summary>
			/// Test split string and combine again
			/// </summary>
			//[Test]
			public void TestSplitStringAndCombineAgain()
			{
				string testText =
					@"using System;

namespace TestVs2003
{
	public class Form1 : System.Windows.Forms.Form
	{
		/// <summary>
		/// The main entry point for the application.
		/// </summary>
		[STAThread]
		static void Main()
		{
			Application.Run(new Form1());
		}
	}
}";
				// Test splitting lines and combine them back together
				string[] lines = SplitMultilineText(testText);
				Assert.IsTrue(lines.Length > 0, "no lines were returned");
				string reconstructedText = BuildStringFromLines(lines);
				Assert.AreEqual(testText, reconstructedText,
					"testText is not equal to reconstructedText");

				// Also test other functions
				string[] words = SplitAndTrim(testText, ' ');
				Assert.AreEqual(words.Length, CountWords(testText), "words.Length");
				Assert.AreEqual(testText, BuildStringFromLines(words, " "));
				// Call with some invalid values
				BuildStringFromLines(words, 1, -3, 4, 6, " ");
				BuildStringFromLines(words, 1, 3, 400, 6, " ");
				BuildStringFromLines(words, 1, 3, 4, 600, " ");
				// Check is we can build a part back together
				// Check if we can build a part back together
				Assert.AreEqual(
					testText.Substring(10, 20),
					BuildStringFromLines(lines, 0, 10, 2, 12, NewLine));

				// Get section code and check if we got everything right
				Assert.AreEqual(
					@"static void Main()
		{
			Application.Run(new Form1());
		}",
					BuildStringFromLines(lines, 10, 2, 13, 3, NewLine));

				// Also check if we change some line we still got the same
				// number of resulting lines.
				lines[13] += " // Test comment";
				Assert.AreEqual(lines.Length,
					SplitMultilineText(BuildStringFromLines(lines, NewLine)).Length);
			} // TestSplitStringAndCombineAgain()

			/// <summary>
			/// Test split multiline text
			/// </summary>
			//[Test]
			public void TestSplitMultilineText()
			{
				Assert.AreEqual(4,
					StringHelper.SplitMultilineText(
					@"test
now an empty lie

thats it").Length);
				// Test few empty lines
				Assert.AreEqual(6,
					StringHelper.SplitMultilineText(
					"hi\n\n\n\n\rthere\n").Length);
				// Test all combination of '\r' and '\n'
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + '\r' + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + "\n\r" + "test2").Length);
				Assert.AreEqual(2,
					StringHelper.SplitMultilineText(
					"test1" + "\r\n" + "test2").Length);
				// Also test if mixing normal "\n\r" with '\r' or '\n'
				Assert.AreEqual(3,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "" + '\r' + "test2" +
					'\r' + "test3").Length);
				Assert.AreEqual(3,
					StringHelper.SplitMultilineText(
					"test1" + '\n' + "" + '\r' + "test2" +
					'\n' + "test3").Length);
			} // TestSplitMultilineText()

			/// <summary>
			/// Test get tab depth
			/// </summary>
			//[Test]
			public void TestGetTabDepth()
			{
				Assert.AreEqual(4,
					StringHelper.GetTabDepth("\t\t\t\tWhats up?"));
			} // TestGetTabDepth()

			/// <summary>
			/// Test is lowercase or uppercase letter
			/// </summary>
			//[Test]
			public void TestIsLowercaseOrUppercaseLetter()
			{
				Assert.AreEqual(true, IsLowercaseLetter('u'));
				Assert.AreEqual(true, IsUppercaseLetter('U'));
				// Well, numbers don't care if they upper or lower letters.
				Assert.AreEqual(true, IsLowercaseLetter('3'));
				Assert.AreEqual(true, IsUppercaseLetter('3'));
			} // TestIsLowercaseOrUppercaseLetter()

			/// <summary>
			/// Test split function name to word string
			/// </summary>
			//[Test]
			public void TestSplitFunctionNameToWordString()
			{
				Assert.AreEqual(
					"Make damage on unit",
					SplitFunctionNameToWordString("MakeDamageOnUnit"));
				Assert.AreEqual(
					"This is a test",
					SplitFunctionNameToWordString("ThisIsATest"));
				Assert.AreEqual(
					"Create TCP listener",
					SplitFunctionNameToWordString("CreateTCPListener"));
				Assert.AreEqual(
					"Lower letter test",
					SplitFunctionNameToWordString("lowerLetterTest"));
				Assert.AreEqual(
					"Test 3D render function",
					SplitFunctionNameToWordString("Test3DRenderFunction"));
				Assert.AreEqual(
					"Check if CD is inserted",
					SplitFunctionNameToWordString("CheckIfCDIsInserted"));

				// Test with text that already got spaces (we expect same stuff
				// returned)
				Assert.AreEqual(
					"Ha ha, I have spaces already",
					SplitFunctionNameToWordString("ha ha, I have spaces already"));
				// Test mixed text
				Assert.AreEqual(
					"Hey what means What the hell?",
					SplitFunctionNameToWordString("Hey what means WhatTheHell?"));

				// Test some crazy strings to test if we get no exceptions
				SplitFunctionNameToWordString("\0\t\n\raoeu\b\t");
				SplitFunctionNameToWordString("@#%#@$<RRROEU<Y$G SINDTBRPI\"'45453'");
			} // TestSplitFunctionNameToWordString()

			#region Test get extension
			/// <summary>
			/// Test the GetExtension method.
			/// </summary>
			//[Test]
			public void TestGetExtension()
			{
				Assert.AreEqual("bmp",
					StringHelper.GetExtension("SomeBitmap.bmp"));
				Assert.AreEqual("xml",
					StringHelper.GetExtension("SomeApplication.config.xml"));
			} // TestGetExtension()
			#endregion
		} // class StringHelperTests
		 */
#endif
		#endregion
	} // class StringHelper
} // namespace DungeonQuest.Helpers


text:
package org.apache.hadoop.hive.ql.plan;

import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.hive.common.JavaUtils;
import org.apache.hadoop.hive.conf.HiveConf;
import org.apache.hadoop.hive.conf.HiveConf.ConfVars;
import org.apache.hadoop.hive.llap.LlapOutputFormat;
import org.apache.hadoop.hive.metastore.MetaStoreUtils;
import org.apache.hadoop.hive.metastore.api.FieldSchema;
import org.apache.hadoop.hive.ql.exec.ColumnInfo;
import org.apache.hadoop.hive.ql.exec.RowSchema;
import org.apache.hadoop.hive.ql.exec.TableScanOperator;
import org.apache.hadoop.hive.ql.exec.Utilities;
import org.apache.hadoop.hive.ql.hooks.ReadEntity;
import org.apache.hadoop.hive.ql.io.AcidUtils;
import org.apache.hadoop.hive.ql.io.HiveOutputFormat;
import org.apache.hadoop.hive.ql.io.IgnoreKeyTextOutputFormat;
import org.apache.hadoop.hive.ql.io.RCFileInputFormat;
import org.apache.hadoop.hive.ql.io.RCFileOutputFormat;
import org.apache.hadoop.hive.ql.metadata.Hive;
import org.apache.hadoop.hive.ql.metadata.HiveException;
import org.apache.hadoop.hive.ql.metadata.HiveStorageHandler;
import org.apache.hadoop.hive.ql.metadata.HiveUtils;
import org.apache.hadoop.hive.ql.metadata.Partition;
import org.apache.hadoop.hive.ql.metadata.Table;
import org.apache.hadoop.hive.ql.parse.ParseContext;
import org.apache.hadoop.hive.ql.parse.SemanticAnalyzer;
import org.apache.hadoop.hive.ql.parse.SemanticException;
import org.apache.hadoop.hive.ql.parse.TypeCheckProcFactory;
import org.apache.hadoop.hive.ql.session.SessionState;
import org.apache.hadoop.hive.serde.serdeConstants;
import org.apache.hadoop.hive.serde2.DelimitedJSONSerDe;
import org.apache.hadoop.hive.serde2.Deserializer;
import org.apache.hadoop.hive.serde2.MetadataTypedColumnsetSerDe;
import org.apache.hadoop.hive.serde2.binarysortable.BinarySortableSerDe;
import org.apache.hadoop.hive.serde2.columnar.ColumnarSerDe;
import org.apache.hadoop.hive.serde2.lazy.LazySerDeParameters;
import org.apache.hadoop.hive.serde2.lazy.LazySimpleSerDe;
import org.apache.hadoop.hive.serde2.lazybinary.LazyBinarySerDe;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo;
import org.apache.hadoop.hive.serde2.typeinfo.TypeInfoFactory;
import org.apache.hadoop.mapred.InputFormat;
import org.apache.hadoop.mapred.JobConf;
import org.apache.hadoop.mapred.SequenceFileInputFormat;
import org.apache.hadoop.mapred.SequenceFileOutputFormat;
import org.apache.hadoop.mapred.TextInputFormat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * PlanUtils.
 *
 */
public final class PlanUtils {

  protected static final Logger LOG = LoggerFactory.getLogger("org.apache.hadoop.hive.ql.plan.PlanUtils");

  private static long countForMapJoinDumpFilePrefix = 0;

  /**
   * ExpressionTypes.
   *
   */
  public static enum ExpressionTypes {
    FIELD, JEXL
  };

  public static synchronized long getCountForMapJoinDumpFilePrefix() {
    return countForMapJoinDumpFilePrefix++;
  }

  @SuppressWarnings("nls")
  public static MapredWork getMapRedWork() {
    return new MapredWork();
  }

  public static TableDesc getDefaultTableDesc(CreateTableDesc directoryDesc,
      String cols, String colTypes ) {
    TableDesc ret = getDefaultTableDesc(Integer.toString(Utilities.ctrlaCode), cols,
        colTypes, false);;
    if (directoryDesc == null) {
      return ret;
    }

    try {
      Properties properties = ret.getProperties();

      if (directoryDesc.getFieldDelim() != null) {
        properties.setProperty(
            serdeConstants.FIELD_DELIM, directoryDesc.getFieldDelim());
        properties.setProperty(
            serdeConstants.SERIALIZATION_FORMAT, directoryDesc.getFieldDelim());
      }
      if (directoryDesc.getLineDelim() != null) {
        properties.setProperty(
            serdeConstants.LINE_DELIM, directoryDesc.getLineDelim());
      }
      if (directoryDesc.getCollItemDelim() != null) {
        properties.setProperty(
            serdeConstants.COLLECTION_DELIM, directoryDesc.getCollItemDelim());
      }
      if (directoryDesc.getMapKeyDelim() != null) {
        properties.setProperty(
            serdeConstants.MAPKEY_DELIM, directoryDesc.getMapKeyDelim());
      }
      if (directoryDesc.getFieldEscape() !=null) {
        properties.setProperty(
            serdeConstants.ESCAPE_CHAR, directoryDesc.getFieldEscape());
      }
      if (directoryDesc.getSerName() != null) {
        properties.setProperty(
            serdeConstants.SERIALIZATION_LIB, directoryDesc.getSerName());
      }
      if (directoryDesc.getSerdeProps() != null) {
        properties.putAll(directoryDesc.getSerdeProps());
      }
      if (directoryDesc.getOutputFormat() != null){
        ret.setOutputFileFormatClass(JavaUtils.loadClass(directoryDesc.getOutputFormat()));
      }
      if (directoryDesc.getNullFormat() != null) {
        properties.setProperty(serdeConstants.SERIALIZATION_NULL_FORMAT,
              directoryDesc.getNullFormat());
      }
      if (directoryDesc.getTblProps() != null) {
        properties.putAll(directoryDesc.getTblProps());
      }

    } catch (ClassNotFoundException e) {
      // mimicking behaviour in CreateTableDesc tableDesc creation
      // returning null table description for output.
      LOG.warn("Unable to find class in getDefaultTableDesc: " + e.getMessage(), e);
      return null;
    }
    return ret;
  }

  /**
   * Generate the table descriptor of MetadataTypedColumnsetSerDe with the
   * separatorCode and column names (comma separated string).
   */
  public static TableDesc getDefaultTableDesc(String separatorCode,
      String columns) {
    return getDefaultTableDesc(separatorCode, columns, false);
  }

  /**
   * Generate the table descriptor of given serde with the separatorCode and
   * column names (comma separated string).
   */
  public static TableDesc getTableDesc(
      Class<? extends Deserializer> serdeClass, String separatorCode,
      String columns) {
    return getTableDesc(serdeClass, separatorCode, columns, false);
  }

  /**
   * Generate the table descriptor of MetadataTypedColumnsetSerDe with the
   * separatorCode and column names (comma separated string), and whether the
   * last column should take the rest of the line.
   */
  public static TableDesc getDefaultTableDesc(String separatorCode,
      String columns, boolean lastColumnTakesRestOfTheLine) {
    return getDefaultTableDesc(separatorCode, columns, null,
        lastColumnTakesRestOfTheLine);
  }

  /**
   * Generate the table descriptor of the serde specified with the separatorCode
   * and column names (comma separated string), and whether the last column
   * should take the rest of the line.
   */
  public static TableDesc getTableDesc(
      Class<? extends Deserializer> serdeClass, String separatorCode,
      String columns, boolean lastColumnTakesRestOfTheLine) {
    return getTableDesc(serdeClass, separatorCode, columns, null,
        lastColumnTakesRestOfTheLine);
  }

  /**
   * Generate the table descriptor of MetadataTypedColumnsetSerDe with the
   * separatorCode and column names (comma separated string), and whether the
   * last column should take the rest of the line.
   */
  public static TableDesc getDefaultTableDesc(String separatorCode,
      String columns, String columnTypes, boolean lastColumnTakesRestOfTheLine) {
    return getTableDesc(LazySimpleSerDe.class, separatorCode, columns,
        columnTypes, lastColumnTakesRestOfTheLine);
  }

  public static TableDesc getTableDesc(
      Class<? extends Deserializer> serdeClass, String separatorCode,
      String columns, String columnTypes, boolean lastColumnTakesRestOfTheLine) {
    return getTableDesc(serdeClass, separatorCode, columns, columnTypes,
        lastColumnTakesRestOfTheLine, false);
  }

  public static TableDesc getTableDesc(
      Class<? extends Deserializer> serdeClass, String separatorCode,
      String columns, String columnTypes, boolean lastColumnTakesRestOfTheLine,
      boolean useDelimitedJSON) {

    return getTableDesc(serdeClass, separatorCode, columns, columnTypes,
        lastColumnTakesRestOfTheLine, useDelimitedJSON, "TextFile");
 }

  public static TableDesc getTableDesc(
      Class<? extends Deserializer> serdeClass, String separatorCode,
      String columns, String columnTypes, boolean lastColumnTakesRestOfTheLine,
      boolean useDelimitedJSON, String fileFormat) {

    Properties properties = Utilities.makeProperties(
        serdeConstants.SERIALIZATION_FORMAT, separatorCode, serdeConstants.LIST_COLUMNS,
        columns);

    if (!separatorCode.equals(Integer.toString(Utilities.ctrlaCode))) {
      properties.setProperty(serdeConstants.FIELD_DELIM, separatorCode);
    }

    if (columnTypes != null) {
      properties.setProperty(serdeConstants.LIST_COLUMN_TYPES, columnTypes);
    }

    if (lastColumnTakesRestOfTheLine) {
      properties.setProperty(serdeConstants.SERIALIZATION_LAST_COLUMN_TAKES_REST,
          "true");
    }

    // It is not a very clean way, and should be modified later - due to
    // compatibility reasons,
    // user sees the results as json for custom scripts and has no way for
    // specifying that.
    // Right now, it is hard-coded in the code
    if (useDelimitedJSON) {
      serdeClass = DelimitedJSONSerDe.class;
    }

    Class inputFormat, outputFormat;
    // get the input & output file formats
    if ("SequenceFile".equalsIgnoreCase(fileFormat)) {
      inputFormat = SequenceFileInputFormat.class;
      outputFormat = SequenceFileOutputFormat.class;
    } else if ("RCFile".equalsIgnoreCase(fileFormat)) {
      inputFormat = RCFileInputFormat.class;
      outputFormat = RCFileOutputFormat.class;
      assert serdeClass == ColumnarSerDe.class;
    } else if ("Llap".equalsIgnoreCase(fileFormat)) {
      inputFormat = TextInputFormat.class;
      outputFormat = LlapOutputFormat.class;
      properties.setProperty(
          org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_STORAGE,
          "org.apache.hadoop.hive.llap.LlapStorageHandler");
    } else { // use TextFile by default
      inputFormat = TextInputFormat.class;
      outputFormat = IgnoreKeyTextOutputFormat.class;
    }
    properties.setProperty(serdeConstants.SERIALIZATION_LIB, serdeClass.getName());
    return new TableDesc(inputFormat, outputFormat, properties);
  }

  public static TableDesc getDefaultQueryOutputTableDesc(String cols, String colTypes,
      String fileFormat, Class<? extends Deserializer> serdeClass) {
    TableDesc tblDesc =
        getTableDesc(serdeClass, "" + Utilities.ctrlaCode, cols, colTypes, false, false, fileFormat);
    // enable escaping
    tblDesc.getProperties().setProperty(serdeConstants.ESCAPE_CHAR, "\\");
    tblDesc.getProperties().setProperty(serdeConstants.SERIALIZATION_ESCAPE_CRLF, "true");
    // enable extended nesting levels
    tblDesc.getProperties().setProperty(
        LazySerDeParameters.SERIALIZATION_EXTEND_ADDITIONAL_NESTING_LEVELS, "true");
    return tblDesc;
  }

 /**
   * Generate a table descriptor from a createTableDesc.
   */
  public static TableDesc getTableDesc(CreateTableDesc crtTblDesc, String cols,
      String colTypes) {

    Class<? extends Deserializer> serdeClass = LazySimpleSerDe.class;
    String separatorCode = Integer.toString(Utilities.ctrlaCode);
    String columns = cols;
    String columnTypes = colTypes;
    boolean lastColumnTakesRestOfTheLine = false;
    TableDesc ret;

    try {
      if (crtTblDesc.getSerName() != null) {
        Class c = JavaUtils.loadClass(crtTblDesc.getSerName());
        serdeClass = c;
      }

      if (crtTblDesc.getFieldDelim() != null) {
        separatorCode = crtTblDesc.getFieldDelim();
      }

      ret = getTableDesc(serdeClass, separatorCode, columns, columnTypes,
          lastColumnTakesRestOfTheLine, false);

      // set other table properties
      Properties properties = ret.getProperties();

      if (crtTblDesc.getCollItemDelim() != null) {
        properties.setProperty(serdeConstants.COLLECTION_DELIM, crtTblDesc
            .getCollItemDelim());
      }

      if (crtTblDesc.getMapKeyDelim() != null) {
        properties.setProperty(serdeConstants.MAPKEY_DELIM, crtTblDesc
            .getMapKeyDelim());
      }

      if (crtTblDesc.getFieldEscape() != null) {
        properties.setProperty(serdeConstants.ESCAPE_CHAR, crtTblDesc
            .getFieldEscape());
      }

      if (crtTblDesc.getLineDelim() != null) {
        properties.setProperty(serdeConstants.LINE_DELIM, crtTblDesc.getLineDelim());
      }

      if (crtTblDesc.getNullFormat() != null) {
        properties.setProperty(serdeConstants.SERIALIZATION_NULL_FORMAT,
              crtTblDesc.getNullFormat());
      }

      if (crtTblDesc.getTableName() != null && crtTblDesc.getDatabaseName() != null) {
        properties.setProperty(org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_NAME,
            crtTblDesc.getTableName());
      }

      if (crtTblDesc.getTblProps() != null) {
        properties.putAll(crtTblDesc.getTblProps());
      }
      if (crtTblDesc.getSerdeProps() != null) {
        properties.putAll(crtTblDesc.getSerdeProps());
      }

      // replace the default input & output file format with those found in
      // crtTblDesc
      Class c1 = JavaUtils.loadClass(crtTblDesc.getInputFormat());
      Class c2 = JavaUtils.loadClass(crtTblDesc.getOutputFormat());
      Class<? extends InputFormat> in_class = c1;
      Class<? extends HiveOutputFormat> out_class = c2;

      ret.setInputFileFormatClass(in_class);
      ret.setOutputFileFormatClass(out_class);
    } catch (ClassNotFoundException e) {
      throw new RuntimeException("Unable to find class in getTableDesc: " + e.getMessage(), e);
    }
    return ret;
  }

  /**
   * Generate the table descriptor of MetadataTypedColumnsetSerDe with the
   * separatorCode. MetaDataTypedColumnsetSerDe is used because LazySimpleSerDe
   * does not support a table with a single column "col" with type
   * "array<string>".
   */
  public static TableDesc getDefaultTableDesc(String separatorCode) {
    return new TableDesc(
        TextInputFormat.class, IgnoreKeyTextOutputFormat.class, Utilities
        .makeProperties(
            org.apache.hadoop.hive.serde.serdeConstants.SERIALIZATION_FORMAT,separatorCode,
            serdeConstants.SERIALIZATION_LIB,MetadataTypedColumnsetSerDe.class.getName()));
  }

  /**
   * Generate the table descriptor for reduce key.
   */
  public static TableDesc getReduceKeyTableDesc(List<FieldSchema> fieldSchemas,
      String order, String nullOrder) {
    return new TableDesc(
        SequenceFileInputFormat.class, SequenceFileOutputFormat.class,
        Utilities.makeProperties(serdeConstants.LIST_COLUMNS, MetaStoreUtils
        .getColumnNamesFromFieldSchema(fieldSchemas),
        serdeConstants.LIST_COLUMN_TYPES, MetaStoreUtils
        .getColumnTypesFromFieldSchema(fieldSchemas),
        serdeConstants.SERIALIZATION_SORT_ORDER, order,
        serdeConstants.SERIALIZATION_NULL_SORT_ORDER, nullOrder,
        serdeConstants.SERIALIZATION_LIB, BinarySortableSerDe.class.getName()));
  }

  /**
   * Generate the table descriptor for Map-side join key.
   */
  public static TableDesc getMapJoinKeyTableDesc(Configuration conf,
      List<FieldSchema> fieldSchemas) {
    if (HiveConf.getVar(conf, ConfVars.HIVE_EXECUTION_ENGINE).equals("tez")) {
      // In tez we use a different way of transmitting the hash table.
      // We basically use ReduceSinkOperators and set the transfer to
      // be broadcast (instead of partitioned). As a consequence we use
      // a different SerDe than in the MR mapjoin case.
      StringBuilder order = new StringBuilder();
      StringBuilder nullOrder = new StringBuilder();
      for (FieldSchema f: fieldSchemas) {
        order.append("+");
        nullOrder.append("a");
      }
      return new TableDesc(
          SequenceFileInputFormat.class, SequenceFileOutputFormat.class,
          Utilities.makeProperties(serdeConstants.LIST_COLUMNS, MetaStoreUtils
              .getColumnNamesFromFieldSchema(fieldSchemas),
              serdeConstants.LIST_COLUMN_TYPES, MetaStoreUtils
              .getColumnTypesFromFieldSchema(fieldSchemas),
              serdeConstants.SERIALIZATION_SORT_ORDER, order.toString(),
              serdeConstants.SERIALIZATION_NULL_SORT_ORDER, nullOrder.toString(),
              serdeConstants.SERIALIZATION_LIB, BinarySortableSerDe.class.getName()));
    } else {
      return new TableDesc(SequenceFileInputFormat.class,
          SequenceFileOutputFormat.class, Utilities.makeProperties("columns",
              MetaStoreUtils.getColumnNamesFromFieldSchema(fieldSchemas),
              "columns.types", MetaStoreUtils
              .getColumnTypesFromFieldSchema(fieldSchemas),
              serdeConstants.ESCAPE_CHAR, "\\",
              serdeConstants.SERIALIZATION_LIB,LazyBinarySerDe.class.getName()));
    }
  }

  /**
   * Generate the table descriptor for Map-side join value.
   */
  public static TableDesc getMapJoinValueTableDesc(
      List<FieldSchema> fieldSchemas) {
      return new TableDesc(SequenceFileInputFormat.class,
          SequenceFileOutputFormat.class, Utilities.makeProperties(
              serdeConstants.LIST_COLUMNS, MetaStoreUtils
              .getColumnNamesFromFieldSchema(fieldSchemas),
              serdeConstants.LIST_COLUMN_TYPES, MetaStoreUtils
              .getColumnTypesFromFieldSchema(fieldSchemas),
              serdeConstants.ESCAPE_CHAR, "\\",
              serdeConstants.SERIALIZATION_LIB,LazyBinarySerDe.class.getName()));
  }

  /**
   * Generate the table descriptor for intermediate files.
   */
  public static TableDesc getIntermediateFileTableDesc(
      List<FieldSchema> fieldSchemas) {
    return new TableDesc(SequenceFileInputFormat.class,
        SequenceFileOutputFormat.class, Utilities.makeProperties(
        serdeConstants.LIST_COLUMNS, MetaStoreUtils
        .getColumnNamesFromFieldSchema(fieldSchemas),
        serdeConstants.LIST_COLUMN_TYPES, MetaStoreUtils
        .getColumnTypesFromFieldSchema(fieldSchemas),
        serdeConstants.ESCAPE_CHAR, "\\",
        serdeConstants.SERIALIZATION_LIB,LazyBinarySerDe.class.getName()));
  }

  /**
   * Generate the table descriptor for intermediate files.
   */
  public static TableDesc getReduceValueTableDesc(List<FieldSchema> fieldSchemas) {
    return new TableDesc(SequenceFileInputFormat.class,
        SequenceFileOutputFormat.class, Utilities.makeProperties(
        serdeConstants.LIST_COLUMNS, MetaStoreUtils
        .getColumnNamesFromFieldSchema(fieldSchemas),
        serdeConstants.LIST_COLUMN_TYPES, MetaStoreUtils
        .getColumnTypesFromFieldSchema(fieldSchemas),
        serdeConstants.ESCAPE_CHAR, "\\",
        serdeConstants.SERIALIZATION_LIB,LazyBinarySerDe.class.getName()));
  }

  /**
   * Convert the ColumnList to FieldSchema list.
   *
   * Adds union type for distinctColIndices.
   */
  public static List<FieldSchema> getFieldSchemasFromColumnListWithLength(
      List<ExprNodeDesc> cols, List<List<Integer>> distinctColIndices,
      List<String> outputColumnNames, int length,
      String fieldPrefix) {
    // last one for union column.
    List<FieldSchema> schemas = new ArrayList<FieldSchema>(length + 1);
    for (int i = 0; i < length; i++) {
      schemas.add(MetaStoreUtils.getFieldSchemaFromTypeInfo(
          fieldPrefix + outputColumnNames.get(i), cols.get(i).getTypeInfo()));
    }

    List<TypeInfo> unionTypes = new ArrayList<TypeInfo>();
    for (List<Integer> distinctCols : distinctColIndices) {
      List<String> names = new ArrayList<String>();
      List<TypeInfo> types = new ArrayList<TypeInfo>();
      int numExprs = 0;
      for (int i : distinctCols) {
        names.add(HiveConf.getColumnInternalName(numExprs));
        types.add(cols.get(i).getTypeInfo());
        numExprs++;
      }
      unionTypes.add(TypeInfoFactory.getStructTypeInfo(names, types));
    }
    if (outputColumnNames.size() - length > 0) {
      schemas.add(MetaStoreUtils.getFieldSchemaFromTypeInfo(
          fieldPrefix + outputColumnNames.get(length),
          TypeInfoFactory.getUnionTypeInfo(unionTypes)));
    }

    return schemas;
  }

  /**
   * Convert the ColumnList to FieldSchema list.
   */
  public static List<FieldSchema> getFieldSchemasFromColumnList(
      List<ExprNodeDesc> cols, List<String> outputColumnNames, int start,
      String fieldPrefix) {
    List<FieldSchema> schemas = new ArrayList<FieldSchema>(cols.size());
    for (int i = 0; i < cols.size(); i++) {
      schemas.add(MetaStoreUtils.getFieldSchemaFromTypeInfo(fieldPrefix
          + outputColumnNames.get(i + start), cols.get(i).getTypeInfo()));
    }
    return schemas;
  }

  /**
   * Convert the ColumnList to FieldSchema list.
   */
  public static List<FieldSchema> getFieldSchemasFromColumnList(
      List<ExprNodeDesc> cols, String fieldPrefix) {
    List<FieldSchema> schemas = new ArrayList<FieldSchema>(cols.size());
    for (int i = 0; i < cols.size(); i++) {
      schemas.add(MetaStoreUtils.getFieldSchemaFromTypeInfo(fieldPrefix + i,
          cols.get(i).getTypeInfo()));
    }
    return schemas;
  }

  /**
   * Convert the RowSchema to FieldSchema list.
   */
  public static List<FieldSchema> getFieldSchemasFromRowSchema(RowSchema row,
      String fieldPrefix) {
    ArrayList<ColumnInfo> c = row.getSignature();
    return getFieldSchemasFromColumnInfo(c, fieldPrefix);
  }

  /**
   * Convert the ColumnInfo to FieldSchema.
   */
  public static List<FieldSchema> getFieldSchemasFromColumnInfo(
      ArrayList<ColumnInfo> cols, String fieldPrefix) {
    if ((cols == null) || (cols.size() == 0)) {
      return new ArrayList<FieldSchema>();
    }

    List<FieldSchema> schemas = new ArrayList<FieldSchema>(cols.size());
    for (int i = 0; i < cols.size(); i++) {
      String name = cols.get(i).getInternalName();
      if (name.equals(String.valueOf(i))) {
        name = fieldPrefix + name;
      }
      schemas.add(MetaStoreUtils.getFieldSchemaFromTypeInfo(name, cols.get(i)
          .getType()));
    }
    return schemas;
  }

  public static List<FieldSchema> sortFieldSchemas(List<FieldSchema> schema) {
    Collections.sort(schema, new Comparator<FieldSchema>() {

      @Override
      public int compare(FieldSchema o1, FieldSchema o2) {
        return o1.getName().compareTo(o2.getName());
      }

    });
    return schema;
  }

  /**
   * Create the reduce sink descriptor.
   *
   * @param keyCols
   *          The columns to be stored in the key
   * @param valueCols
   *          The columns to be stored in the value
   * @param outputColumnNames
   *          The output columns names
   * @param tag
   *          The tag for this reducesink
   * @param partitionCols
   *          The columns for partitioning.
   * @param numReducers
   *          The number of reducers, set to -1 for automatic inference based on
   *          input data size.
   * @param writeType Whether this is an Acid write, and if so whether it is insert, update,
   *                  or delete.
   * @return The reduceSinkDesc object.
   */
  public static ReduceSinkDesc getReduceSinkDesc(
      ArrayList<ExprNodeDesc> keyCols, ArrayList<ExprNodeDesc> valueCols,
      List<String> outputColumnNames, boolean includeKeyCols, int tag,
      ArrayList<ExprNodeDesc> partitionCols, String order, String nullOrder,
      int numReducers, AcidUtils.Operation writeType) {
    return getReduceSinkDesc(keyCols, keyCols.size(), valueCols,
        new ArrayList<List<Integer>>(),
        includeKeyCols ? outputColumnNames.subList(0, keyCols.size()) :
          new ArrayList<String>(),
        includeKeyCols ? outputColumnNames.subList(keyCols.size(),
            outputColumnNames.size()) : outputColumnNames,
        includeKeyCols, tag, partitionCols, order, nullOrder, numReducers, writeType);
  }

  /**
   * Create the reduce sink descriptor.
   *
   * @param keyCols
   *          The columns to be stored in the key
   * @param numKeys
   *          number of distribution key numbers. Equals to group-by-key
   *          numbers usually.
   * @param valueCols
   *          The columns to be stored in the value
   * @param distinctColIndices
   *          column indices for distinct aggregate parameters
   * @param outputKeyColumnNames
   *          The output key columns names
   * @param outputValueColumnNames
   *          The output value columns names
   * @param tag
   *          The tag for this reducesink
   * @param partitionCols
   *          The columns for partitioning.
   * @param numReducers
   *          The number of reducers, set to -1 for automatic inference based on
   *          input data size.
   * @param writeType Whether this is an Acid write, and if so whether it is insert, update,
   *                  or delete.
   * @return The reduceSinkDesc object.
   */
  public static ReduceSinkDesc getReduceSinkDesc(
      final ArrayList<ExprNodeDesc> keyCols, int numKeys,
      ArrayList<ExprNodeDesc> valueCols,
      List<List<Integer>> distinctColIndices,
      List<String> outputKeyColumnNames,
      List<String> outputValueColumnNames,
      boolean includeKeyCols, int tag,
      ArrayList<ExprNodeDesc> partitionCols, String order, String nullOrder,
      int numReducers, AcidUtils.Operation writeType) {
    TableDesc keyTable = null;
    TableDesc valueTable = null;
    ArrayList<String> outputKeyCols = new ArrayList<String>();
    ArrayList<String> outputValCols = new ArrayList<String>();
    if (includeKeyCols) {
      List<FieldSchema> keySchema = getFieldSchemasFromColumnListWithLength(
          keyCols, distinctColIndices, outputKeyColumnNames, numKeys, "");
      if (order.length() < outputKeyColumnNames.size()) {
        order = order + "+";
      }
      if (nullOrder.length() < outputKeyColumnNames.size()) {
        nullOrder = nullOrder + "a";
      }
      keyTable = getReduceKeyTableDesc(keySchema, order, nullOrder);
      outputKeyCols.addAll(outputKeyColumnNames);
    } else {
      keyTable = getReduceKeyTableDesc(getFieldSchemasFromColumnList(
          keyCols, "reducesinkkey"), order, nullOrder);
     for (int i = 0; i < keyCols.size(); i++) {
        outputKeyCols.add("reducesinkkey" + i);
      }
    }
    valueTable = getReduceValueTableDesc(getFieldSchemasFromColumnList(
        valueCols, outputValueColumnNames, 0, ""));
    outputValCols.addAll(outputValueColumnNames);
    return new ReduceSinkDesc(keyCols, numKeys, valueCols, outputKeyCols,
        distinctColIndices, outputValCols,
        tag, partitionCols, numReducers, keyTable,
        valueTable, writeType);
  }

  /**
   * Create the reduce sink descriptor.
   *
   * @param keyCols
   *          The columns to be stored in the key
   * @param valueCols
   *          The columns to be stored in the value
   * @param outputColumnNames
   *          The output columns names
   * @param tag
   *          The tag for this reducesink
   * @param numPartitionFields
   *          The first numPartitionFields of keyCols will be partition columns.
   *          If numPartitionFields=-1, then partition randomly.
   * @param numReducers
   *          The number of reducers, set to -1 for automatic inference based on
   *          input data size.
   * @param writeType Whether this is an Acid write, and if so whether it is insert, update,
   *                  or delete.
   * @return The reduceSinkDesc object.
   */
  public static ReduceSinkDesc getReduceSinkDesc(
      ArrayList<ExprNodeDesc> keyCols, ArrayList<ExprNodeDesc> valueCols,
      List<String> outputColumnNames, boolean includeKey, int tag,
      int numPartitionFields, int numReducers, AcidUtils.Operation writeType)
      throws SemanticException {
    return getReduceSinkDesc(keyCols, keyCols.size(), valueCols,
        new ArrayList<List<Integer>>(),
        includeKey ? outputColumnNames.subList(0, keyCols.size()) :
          new ArrayList<String>(),
        includeKey ?
            outputColumnNames.subList(keyCols.size(), outputColumnNames.size())
            : outputColumnNames,
        includeKey, tag, numPartitionFields, numReducers, writeType);
  }

  /**
   * Create the reduce sink descriptor.
   *
   * @param keyCols
   *          The columns to be stored in the key
   * @param numKeys  number of distribution keys. Equals to group-by-key
   *        numbers usually.
   * @param valueCols
   *          The columns to be stored in the value
   * @param distinctColIndices
   *          column indices for distinct aggregates
   * @param outputKeyColumnNames
   *          The output key columns names
   * @param outputValueColumnNames
   *          The output value columns names
   * @param tag
   *          The tag for this reducesink
   * @param numPartitionFields
   *          The first numPartitionFields of keyCols will be partition columns.
   *          If numPartitionFields=-1, then partition randomly.
   * @param numReducers
   *          The number of reducers, set to -1 for automatic inference based on
   *          input data size.
   * @param writeType Whether this is an Acid write, and if so whether it is insert, update,
   *                  or delete.
   * @return The reduceSinkDesc object.
   */
  public static ReduceSinkDesc getReduceSinkDesc(
      ArrayList<ExprNodeDesc> keyCols, int numKeys,
      ArrayList<ExprNodeDesc> valueCols,
      List<List<Integer>> distinctColIndices,
      List<String> outputKeyColumnNames, List<String> outputValueColumnNames,
      boolean includeKey, int tag,
      int numPartitionFields, int numReducers, AcidUtils.Operation writeType)
      throws SemanticException {

    ArrayList<ExprNodeDesc> partitionCols = new ArrayList<ExprNodeDesc>();
    if (numPartitionFields >= keyCols.size()) {
      partitionCols.addAll(keyCols);
    } else if (numPartitionFields >= 0) {
      partitionCols.addAll(keyCols.subList(0, numPartitionFields));
    } else {
      // numPartitionFields = -1 means random partitioning
      partitionCols.add(TypeCheckProcFactory.DefaultExprProcessor.getFuncExprNodeDesc("rand"));
    }

    StringBuilder order = new StringBuilder();
    StringBuilder nullOrder = new StringBuilder();
    for (int i = 0; i < keyCols.size(); i++) {
      order.append("+");
      nullOrder.append("a");
    }
    return getReduceSinkDesc(keyCols, numKeys, valueCols, distinctColIndices,
        outputKeyColumnNames, outputValueColumnNames, includeKey, tag,
        partitionCols, order.toString(), nullOrder.toString(), numReducers, writeType);
  }

  /**
   * Loads the storage handler (if one exists) for the given table
   * and invokes {@link HiveStorageHandler#configureInputJobProperties(TableDesc, java.util.Map)}.
   *
   * @param tableDesc table descriptor
   */
  public static void configureInputJobPropertiesForStorageHandler(TableDesc tableDesc) {
      configureJobPropertiesForStorageHandler(true,tableDesc);
  }

  /**
   * Loads the storage handler (if one exists) for the given table
   * and invokes {@link HiveStorageHandler#configureOutputJobProperties(TableDesc, java.util.Map)}.
   *
   * @param tableDesc table descriptor
   */
  public static void configureOutputJobPropertiesForStorageHandler(TableDesc tableDesc) {
      configureJobPropertiesForStorageHandler(false,tableDesc);
  }

  private static void configureJobPropertiesForStorageHandler(boolean input,
    TableDesc tableDesc) {

    if (tableDesc == null) {
      return;
    }

    try {
      HiveStorageHandler storageHandler =
        HiveUtils.getStorageHandler(
          Hive.get().getConf(),
          tableDesc.getProperties().getProperty(
            org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_STORAGE));
      if (storageHandler != null) {
        Map<String, String> jobProperties = new LinkedHashMap<String, String>();
        if(input) {
            try {
                storageHandler.configureInputJobProperties(
                  tableDesc,
                  jobProperties);
            } catch(AbstractMethodError e) {
                LOG.info("configureInputJobProperties not found "+
                    "using configureTableJobProperties",e);
                storageHandler.configureTableJobProperties(tableDesc, jobProperties);
            }
        }
        else {
            try {
                storageHandler.configureOutputJobProperties(
                  tableDesc,
                  jobProperties);
            } catch(AbstractMethodError e) {
                LOG.info("configureOutputJobProperties not found"+
                    "using configureTableJobProperties",e);
                storageHandler.configureTableJobProperties(tableDesc, jobProperties);
            }
        }
        // Job properties are only relevant for non-native tables, so
        // for native tables, leave it null to avoid cluttering up
        // plans.
        if (!jobProperties.isEmpty()) {
          tableDesc.setJobProperties(jobProperties);
        }
      }
    } catch (HiveException ex) {
      throw new RuntimeException(ex);
    }
  }

  public static void configureJobConf(TableDesc tableDesc, JobConf jobConf) {
    String handlerClass = tableDesc.getProperties().getProperty(
        org.apache.hadoop.hive.metastore.api.hive_metastoreConstants.META_TABLE_STORAGE);
    try {
      HiveStorageHandler storageHandler = HiveUtils.getStorageHandler(jobConf, handlerClass);
      if (storageHandler != null) {
        storageHandler.configureJobConf(tableDesc, jobConf);
      }
    } catch (HiveException e) {
      throw new RuntimeException(e);
    }
  }

  public static String stripQuotes(String val) {
    if ((val.charAt(0) == '\'' && val.charAt(val.length() - 1) == '\'')
        || (val.charAt(0) == '\"' && val.charAt(val.length() - 1) == '\"')) {
      val = val.substring(1, val.length() - 1);
    }
    return val;
  }

  /**
   * Remove prefix from "Path -> Alias"
   * This is required for testing.
   * In order to verify that path is right, we need to display it in expected test result.
   * But, mask pattern masks path with some patterns.
   * So, we need to remove prefix from path which triggers mask pattern.
   * @param origiKey
   * @return
   */
  public static String removePrefixFromWarehouseConfig(String origiKey) {
    String prefix = SessionState.get().getConf().getVar(HiveConf.ConfVars.METASTOREWAREHOUSE);
    if ((prefix != null) && (prefix.length() > 0)) {
      //Local file system is using pfile:/// {@link ProxyLocalFileSystem}
      prefix = prefix.replace("pfile:///", "pfile:/");
      int index = origiKey.indexOf(prefix);
      if (index > -1) {
        origiKey = origiKey.substring(index + prefix.length());
      }
    }
    return origiKey;
  }

  private PlanUtils() {
    // prevent instantiation
  }

  public static ReadEntity addInput(Set<ReadEntity> inputs, ReadEntity newInput) {
    return addInput(inputs,newInput,false);
  }

  // Add the input 'newInput' to the set of inputs for the query.
  // The input may or may not be already present.
  // The ReadEntity also contains the parents from it is derived (only populated
  // in case of views). The equals method for ReadEntity does not compare the parents
  // so that the same input with different parents cannot be added twice. If the input
  // is already present, make sure the parents are added.
  // Consider the query:
  // select * from (select * from V2 union all select * from V3) subq;
  // where both V2 and V3 depend on V1 (eg V2 : select * from V1, V3: select * from V1),
  // addInput would be called twice for V1 (one with parent V2 and the other with parent V3).
  // When addInput is called for the first time for V1, V1 (parent V2) is added to inputs.
  // When addInput is called for the second time for V1, the input V1 from inputs is picked up,
  // and it's parents are enhanced to include V2 and V3
  // The inputs will contain: (V2, no parent), (V3, no parent), (V1, parents(V2, v3))
  //
  // If the ReadEntity is already present and another ReadEntity with same name is
  // added, then the isDirect flag is updated to be the OR of values of both.
  // mergeIsDirectFlag, need to merge isDirect flag even newInput does not have parent
  public static ReadEntity addInput(Set<ReadEntity> inputs, ReadEntity newInput, boolean mergeIsDirectFlag) {
    // If the input is already present, make sure the new parent is added to the input.
    LOG.info("++++++++++++++ inputs.contains(newInput):" + inputs.contains(newInput) + ", mergeIsDirectFlag:" + mergeIsDirectFlag);
    if (inputs.contains(newInput)) {
      for (ReadEntity input : inputs) {
        if (input.equals(newInput)) {
          if ((newInput.getParents() != null) && (!newInput.getParents().isEmpty())) {
            input.getParents().addAll(newInput.getParents());
            input.setDirect(input.isDirect() || newInput.isDirect());
          } else if (mergeIsDirectFlag) {
            input.setDirect(input.isDirect() || newInput.isDirect());
          }
          return input;
        }
      }
      assert false;
    } else {
      inputs.add(newInput);
      return newInput;
    }
    // make compile happy
    return null;
  }

  public static String getExprListString(Collection<? extends ExprNodeDesc> exprs) {
    return getExprListString(exprs, false);
  }

  public static String getExprListString(Collection<?  extends ExprNodeDesc> exprs, boolean userLevelExplain) {
    StringBuilder sb = new StringBuilder();
    boolean first = true;
    for (ExprNodeDesc expr: exprs) {
      if (!first) {
        sb.append(", ");
      } else {
        first = false;
      }
      addExprToStringBuffer(expr, sb, userLevelExplain);
    }

    return sb.length() == 0 ? null : sb.toString();
  }

  public static void addExprToStringBuffer(ExprNodeDesc expr, Appendable sb, boolean userLevelExplain) {
    try {
      sb.append(expr.getExprString());
      if (!userLevelExplain) {
        sb.append(" (type: ");
        sb.append(expr.getTypeString());
        sb.append(")");
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
  }

  public static void addPartitionInputs(Collection<Partition> parts, Collection<ReadEntity> inputs,
      ReadEntity parentViewInfo, boolean isDirectRead) {
    // Store the inputs in a HashMap since we can't get a ReadEntity from inputs since it is
    // implemented as a set.ReadEntity is used as the key so that the HashMap has the same behavior
    // of equals and hashCode
    Map<ReadEntity, ReadEntity> readEntityMap =
        new LinkedHashMap<ReadEntity, ReadEntity>(inputs.size());
    for (ReadEntity input : inputs) {
      readEntityMap.put(input, input);
    }

    for (Partition part : parts) {
      ReadEntity newInput = null;
      if (part.getTable().isPartitioned()) {
        newInput = new ReadEntity(part, parentViewInfo, isDirectRead);
      } else {
        newInput = new ReadEntity(part.getTable(), parentViewInfo, isDirectRead);
      }

      if (readEntityMap.containsKey(newInput)) {
        ReadEntity input = readEntityMap.get(newInput);
        if ((newInput.getParents() != null) && (!newInput.getParents().isEmpty())) {
          input.getParents().addAll(newInput.getParents());
          input.setDirect(input.isDirect() || newInput.isDirect());
        }
      } else {
        readEntityMap.put(newInput, newInput);
      }
    }

    // Add the new ReadEntity that were added to readEntityMap in PlanUtils.addInput
    if (inputs.size() != readEntityMap.size()) {
      inputs.addAll(readEntityMap.keySet());
    }
  }

  public static void addInputsForView(ParseContext parseCtx) throws HiveException {
    Set<ReadEntity> inputs = parseCtx.getSemanticInputs();
    for (Map.Entry<String, TableScanOperator> entry : parseCtx.getTopOps().entrySet()) {
      String alias = entry.getKey();
      TableScanOperator topOp = entry.getValue();
      ReadEntity parentViewInfo = getParentViewInfo(alias, parseCtx.getViewAliasToInput());

      // Adds tables only for create view (PPD filter can be appended by outer query)
      Table table = topOp.getConf().getTableMetadata();
      PlanUtils.addInput(inputs, new ReadEntity(table, parentViewInfo));
    }
  }

  public static ReadEntity getParentViewInfo(String alias_id,
      Map<String, ReadEntity> viewAliasToInput) {
    String[] aliases = alias_id.split(":");

    String currentAlias = null;
    ReadEntity currentInput = null;
    // Find the immediate parent possible.
    // For eg: for a query like 'select * from V3', where V3 -> V2, V2 -> V1, V1 -> T
    // -> implies depends on.
    // T's parent would be V1
    for (int pos = 0; pos < aliases.length; pos++) {
      currentAlias = currentAlias == null ? aliases[pos] : currentAlias + ":" + aliases[pos];
      LOG.info("++++++++++= currentAlias:" + currentAlias);
      currentAlias = currentAlias.replace(SemanticAnalyzer.SUBQUERY_TAG_1, "")
          .replace(SemanticAnalyzer.SUBQUERY_TAG_2, "");
      LOG.info("++++++++++>> currentAlias:" + currentAlias);
      ReadEntity input = viewAliasToInput.get(currentAlias);
      LOG.info("+++ input is null:" + (input == null) + ", currentInput is null:" + (currentInput == null));
      if (input == null && currentInput != null) {
        // To handle the case of - select * from (select * from V1) A;
        // the currentInput != null check above is needed.
        // the alias list that case would be A:V1:T. Lookup on A would return null,
        // we need to go further to find the view inside it.
        return currentInput;
      }
      currentInput = input;
    }

    return currentInput;
  }
}

redpajama_set_name: RedPajamaGithub


text:
layout: post
date: 2016-03-29
title: "Elbeth Gillis Anna Long Sleeves"
category: Elbeth Gillis
tags: [Elbeth Gillis,Elbeth Gillis ,V-neck,Long Sleeves]
---
### Elbeth Gillis Anna
Just **$299.99**
###   Long Sleeves

BRANDS	Elbeth Gillis

Neckline	V-neck

Sleeve	Long Sleeves

Buy it: [https://www.readybrides.com/en/elbeth-gillis/5838-elbeth-gillis-anna.html](https://www.readybrides.com/en/elbeth-gillis/5838-elbeth-gillis-anna.html)


text: This tutorial discussed factors affecting the options price and introduced a famous option pricing model including input parameters, assumptions and the formula.


text:
package br.icarwash.dao;

import br.icarwash.model.Endereco;
import br.icarwash.model.Endereco.EnderecoBuilder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;

public class EnderecoDAO {

    private final Connection conexao;
    private static final String INSERT = "insert into endereco(cep, estado, cidade, bairro, endereco, numero, nome) values(?,?,?,?,?,?,?)";
    private static final String UPDATE = "update endereco set cep = ?, estado = ?, cidade = ?, bairro = ?, endereco = ?, numero = ?, nome = ? WHERE id = ?";
    private static final String DELETE_BY_ID = "delete from endereco where id = ?";
    private static final String SELECT_BY_ID = "select * from endereco where id = ?";

    public EnderecoDAO(Connection conexao) {
        this.conexao = conexao;
    }

    public Endereco cadastrar(Endereco endereco) {
        try {
            PreparedStatement pstmt = conexao.prepareStatement(INSERT, Statement.RETURN_GENERATED_KEYS);
            pstmt.setString(1, endereco.getCEP());
            pstmt.setString(2, endereco.getEstado());
            pstmt.setString(3, endereco.getCidade());
            pstmt.setString(4, endereco.getBairro());
            pstmt.setString(5, endereco.getEndereco());
            pstmt.setInt(6, endereco.getNumero());
            pstmt.setString(7, endereco.getNome());

            pstmt.execute();

            final ResultSet rs = pstmt.getGeneratedKeys();
            if (rs.next()) {
                endereco.setId(rs.getInt(1));
            }

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return endereco;
    }

    public Endereco localizarPorId(Endereco endereco) {
        try {
            PreparedStatement pstmt = conexao.prepareStatement(SELECT_BY_ID);
            pstmt.setString(1, Integer.toString(endereco.getId()));
            ResultSet rs = pstmt.executeQuery();
            if (rs.next()) {
                endereco = new EnderecoBuilder()
                        .withId(rs.getInt("id"))
                        .withCep(rs.getString("cep"))
                        .withEstado(rs.getString("estado"))
                        .withCidade(rs.getString("cidade"))
                        .withBairro(rs.getString("bairro"))
                        .withEndereco(rs.getString("endereco"))
                        .withNumero(rs.getInt("numero"))
                        .withNome(rs.getString("nome"))
                        .build();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        return endereco;
    }

    public void atualizar(Endereco endereco) {
        try {
            PreparedStatement pstmt = conexao.prepareStatement(UPDATE);
            pstmt.setString(1, endereco.getCEP());
            pstmt.setString(2, endereco.getEstado());
            pstmt.setString(3, endereco.getCidade());
            pstmt.setString(4, endereco.getBairro());
            pstmt.setString(5, endereco.getEndereco());
            pstmt.setInt(6, endereco.getNumero());
            pstmt.setString(7, endereco.getNome());
            pstmt.setInt(8, endereco.getId());
            pstmt.execute();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void excluir(Endereco endereco) {
        try {
            PreparedStatement pstmt = conexao.prepareStatement(DELETE_BY_ID);
            pstmt.setInt(1, endereco.getId());
            pstmt.execute();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}