/*------------------------------------------------------------------------------
 * PACKAGE: com.freeware.inifiles
 * FILE   : iniFile.java
 * CREATED: Jun 30, 2004
 * AUTHOR : Prasad P. Khandekar
 */
package com.interpress_project.modernshare.model;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.sql.Timestamp;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;

import com.interpress_project.modernshare.ipcommon.SystemBase;

/**
 * INIFile class provides methods for manipulating (Read/Write) windows ini
 * files.
 * 
 * @author Prasad P. Khandekar
 * @version 1.0
 * @since 1.0
 */
public final class INIFile {
	private SystemBase sb = SystemBase.getInstance();
	private String linesep = sb.getLineSeparator();

	/** Variable to represent the date format */
	private String mstrDateFmt = "yyyy-MM-dd";

	/** Variable to represent the timestamp format */
	private String mstrTimeStampFmt = "yyyy-MM-dd HH:mm:ss";

	/** Variable to hold the ini file name and full path */
	private String mstrFile;

	/** Variable to hold the sections in an ini file. */
	private LinkedHashMap mhmapSections;

	/**
	 * Create a iniFile object from the file named in the parameter.
	 * 
	 * @param pstrPathAndName The full path and name of the ini file to be used.
	 */
	public INIFile(String pstrPathAndName) {
		this.mhmapSections = new LinkedHashMap();
		this.mstrFile = pstrPathAndName;

		// Load the specified INI file.
		if (checkFile(pstrPathAndName)) {
			loadFile();
		}
	}

	/*------------------------------------------------------------------------------
	 * Getters
	 ------------------------------------------------------------------------------*/
	/**
	 * Returns the ini file name being used.
	 * 
	 * @return the INI file name.
	 */
	public String getFileName() {
		return this.mstrFile;
	}

	/**
	 * Returns the specified string property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the string property value.
	 */
	public String getStringProperty(String pstrSection, String pstrProp) {
		String strRet = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			if (objProp != null) {
				strRet = objProp.getPropValue();
				objProp = null;
			}
			objSec = null;
		}
		return strRet;
	}

	/**
	 * Returns the specified boolean property from the specified section. This
	 * method considers the following values as boolean values.
	 * <ol>
	 * <li>YES/yes/Yes - boolean true</li>
	 * <li>NO/no/No - boolean false</li>
	 * <li>1 - boolean true</li>
	 * <li>0 - boolean false</li>
	 * <li>TRUE/True/true - boolean true</li>
	 * <li>FALSE/False/false - boolean false</li>
	 * </ol>
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the boolean value
	 */
	public Boolean getBooleanProperty(String pstrSection, String pstrProp) {
		boolean blnRet = false;
		String strVal = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			if (objProp != null) {
				strVal = objProp.getPropValue().toUpperCase();
				if (strVal.equals("YES") || strVal.equals("TRUE") || strVal.equals("1")) {
					blnRet = true;
				}
				objProp = null;
			}
			objSec = null;
		}
		return new Boolean(blnRet);
	}

	/**
	 * Returns the specified integer property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the integer property value.
	 */
	public Integer getIntegerProperty(String pstrSection, String pstrProp) {
		Integer intRet = null;
		String strVal = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			try {
				if (objProp != null) {
					strVal = objProp.getPropValue();
					if (strVal != null)
						intRet = new Integer(strVal);
				}
			}
			catch (NumberFormatException NFExIgnore) {
			}
			finally {
				if (objProp != null)
					objProp = null;
			}
			objSec = null;
		}
		return intRet;
	}

	/**
	 * Returns the specified long property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the long property value.
	 */
	public Long getLongProperty(String pstrSection, String pstrProp) {
		Long lngRet = null;
		String strVal = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			try {
				if (objProp != null) {
					strVal = objProp.getPropValue();
					if (strVal != null)
						lngRet = new Long(strVal);
				}
			}
			catch (NumberFormatException NFExIgnore) {
			}
			finally {
				if (objProp != null)
					objProp = null;
			}
			objSec = null;
		}
		return lngRet;
	}

	/**
	 * Returns the specified double property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the double property value.
	 */
	public Double getDoubleProperty(String pstrSection, String pstrProp) {
		Double dblRet = null;
		String strVal = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			try {
				if (objProp != null) {
					strVal = objProp.getPropValue();
					if (strVal != null)
						dblRet = new Double(strVal);
				}
			}
			catch (NumberFormatException NFExIgnore) {
			}
			finally {
				if (objProp != null)
					objProp = null;
			}
			objSec = null;
		}
		return dblRet;
	}

	/**
	 * Returns the specified date property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the date property value.
	 */
	public Date getDateProperty(String pstrSection, String pstrProp) {
		Date dtRet = null;
		String strVal = null;
		DateFormat dtFmt = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			try {
				if (objProp != null)
					strVal = objProp.getPropValue();
				if (strVal != null) {
					dtFmt = new SimpleDateFormat(this.mstrDateFmt);
					dtRet = dtFmt.parse(strVal);
				}
			}
			catch (ParseException PExIgnore) {
			}
			catch (IllegalArgumentException IAEx) {
			}
			finally {
				if (objProp != null)
					objProp = null;
			}
			objSec = null;
		}
		return dtRet;
	}

	/**
	 * Returns the specified date property from the specified section.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be retrieved.
	 * @return the date property value.
	 */
	public Date getTimestampProperty(String pstrSection, String pstrProp) {
		Timestamp tsRet = null;
		Date dtTmp = null;
		String strVal = null;
		DateFormat dtFmt = null;
		INIProperty objProp = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objProp = objSec.getProperty(pstrProp);
			try {
				if (objProp != null)
					strVal = objProp.getPropValue();
				if (strVal != null) {
					dtFmt = new SimpleDateFormat(this.mstrDateFmt);
					dtTmp = dtFmt.parse(strVal);
					tsRet = new Timestamp(dtTmp.getTime());
				}
			}
			catch (ParseException PExIgnore) {
			}
			catch (IllegalArgumentException IAEx) {
			}
			finally {
				if (objProp != null)
					objProp = null;
			}
			objSec = null;
		}
		return tsRet;
	}

	/*------------------------------------------------------------------------------
	 * Setters
	 ------------------------------------------------------------------------------*/
	/**
	 * Sets the comments associated with a section.
	 * 
	 * @param pstrSection the section name
	 * @param pstrComments the comments.
	 */
	@SuppressWarnings("unchecked")
	public void addSection(String pstrSection, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setSecComments(delRemChars(pstrComments));
		objSec = null;
	}

	/**
	 * Sets the specified string property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @pstrVal the string value to be persisted
	 */
	@SuppressWarnings("unchecked")
	public void setStringProperty(String pstrSection, String pstrProp, String pstrVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, pstrVal, pstrComments);
	}

	/**
	 * Sets the specified boolean property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param pblnVal the boolean value to be persisted
	 */
	@SuppressWarnings("unchecked")
	public void setBooleanProperty(String pstrSection, String pstrProp, boolean pblnVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		if (pblnVal)
			objSec.setProperty(pstrProp, "TRUE", pstrComments);
		else
			objSec.setProperty(pstrProp, "FALSE", pstrComments);
	}

	/**
	 * Sets the specified integer property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param pintVal the int property to be persisted.
	 */
	@SuppressWarnings("unchecked")
	public void setIntegerProperty(String pstrSection, String pstrProp, int pintVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, Integer.toString(pintVal), pstrComments);
	}

	/**
	 * Sets the specified long property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param plngVal the long value to be persisted.
	 */
	@SuppressWarnings("unchecked")
	public void setLongProperty(String pstrSection, String pstrProp, long plngVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, Long.toString(plngVal), pstrComments);
	}

	/**
	 * Sets the specified double property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param pdblVal the double value to be persisted.
	 */
	@SuppressWarnings("unchecked")
	public void setDoubleProperty(String pstrSection, String pstrProp, double pdblVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, Double.toString(pdblVal), pstrComments);
	}

	/**
	 * Sets the specified java.util.Date property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param pdtVal the date value to be persisted.
	 */
	@SuppressWarnings( { "unchecked", "unchecked" })
	public void setDateProperty(String pstrSection, String pstrProp, Date pdtVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, utilDateToStr(pdtVal, this.mstrDateFmt), pstrComments);
	}

	/**
	 * Sets the specified java.sql.Timestamp property.
	 * 
	 * @param pstrSection the INI section name.
	 * @param pstrProp the property to be set.
	 * @param ptsVal the timestamp value to be persisted.
	 */
	@SuppressWarnings("unchecked")
	public void setTimestampProperty(String pstrSection, String pstrProp, Timestamp ptsVal, String pstrComments) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec == null) {
			objSec = new INISection(pstrSection);
			this.mhmapSections.put(pstrSection, objSec);
		}
		objSec.setProperty(pstrProp, timeToStr(ptsVal, this.mstrTimeStampFmt), pstrComments);
	}

	/**
	 * Sets the format to be used to interpreat date values.
	 * 
	 * @param pstrDtFmt the format string
	 * @throws IllegalArgumentException if the if the given pattern is invalid
	 */
	public void setDateFormat(String pstrDtFmt) throws IllegalArgumentException {
		if (!checkDateTimeFormat(pstrDtFmt))
			throw new IllegalArgumentException("The specified date pattern is invalid!");
		this.mstrDateFmt = pstrDtFmt;
	}

	/**
	 * Sets the format to be used to interpreat timestamp values.
	 * 
	 * @param pstrTSFmt the format string
	 * @throws IllegalArgumentException if the if the given pattern is invalid
	 */
	public void setTimeStampFormat(String pstrTSFmt) {
		if (!checkDateTimeFormat(pstrTSFmt))
			throw new IllegalArgumentException("The specified timestamp pattern is invalid!");
		this.mstrTimeStampFmt = pstrTSFmt;
	}

	/*------------------------------------------------------------------------------
	 * Public methods
	 ------------------------------------------------------------------------------*/
	public int getTotalSections() {
		return this.mhmapSections.size();
	}

	/**
	 * Returns a string array containing names of all sections in INI file.
	 * 
	 * @return the string array of section names
	 */
	public String[] getAllSectionNames() {
		int iCntr = 0;
		Iterator iter = null;
		String[] arrRet = null;

		try {
			if (this.mhmapSections.size() > 0) {
				arrRet = new String[this.mhmapSections.size()];
				for (iter = this.mhmapSections.keySet().iterator();; iter.hasNext()) {
					arrRet[iCntr] = (String) iter.next();
					iCntr++;
				}
			}
		}
		catch (NoSuchElementException NSEExIgnore) {
		}
		finally {
			if (iter != null)
				iter = null;
		}
		return arrRet;
	}

	/**
	 * Returns a string array containing names of all the properties under
	 * specified section.
	 * 
	 * @param pstrSection the name of the section for which names of properties is
	 *        to be retrieved.
	 * @return the string array of property names.
	 */
	public String[] getPropertyNames(String pstrSection) {
		String[] arrRet = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			arrRet = objSec.getPropNames();
			objSec = null;
		}
		return arrRet;
	}

	/**
	 * Returns a map containing all the properties under specified section.
	 * 
	 * @param pstrSection the name of the section for which properties are to be
	 *        retrieved.
	 * @return the map of properties.
	 */
	public Map getProperties(String pstrSection) {
		Map hmRet = null;
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			hmRet = objSec.getProperties();
			objSec = null;
		}
		return hmRet;
	}

	/**
	 * Removed specified property from the specified section. If the specified
	 * section or the property does not exist, does nothing.
	 * 
	 * @param pstrSection the section name.
	 * @param pstrProp the name of the property to be removed.
	 */
	public void removeProperty(String pstrSection, String pstrProp) {
		INISection objSec = null;

		objSec = (INISection) this.mhmapSections.get(pstrSection);
		if (objSec != null) {
			objSec.removeProperty(pstrProp);
			objSec = null;
		}
	}

	/**
	 * Removes the specified section if one exists, otherwise does nothing.
	 * 
	 * @param pstrSection the name of the section to be removed.
	 */
	public void removeSection(String pstrSection) {
		if (this.mhmapSections.containsKey(pstrSection))
			this.mhmapSections.remove(pstrSection);
	}

	/**
	 * Flush changes back to the disk file. If the disk file does not exists then
	 * creates the new one.
	 */
	public boolean save() {
		boolean blnRet = false;
		File objFile = null;
		String strName = null;
		String strTemp = null;
		Iterator itrSec = null;
		INISection objSec = null;
		FileWriter objWriter = null;

		try {
			if (this.mhmapSections.size() == 0)
				return false;
			objFile = new File(this.mstrFile);
			if (objFile.exists())
				objFile.delete();
			objWriter = new FileWriter(objFile);
			itrSec = this.mhmapSections.keySet().iterator();
			while (itrSec.hasNext()) {
				strName = (String) itrSec.next();
				objSec = (INISection) this.mhmapSections.get(strName);
				strTemp = objSec.toString();
				objWriter.write(strTemp);
				objWriter.write(linesep);
				objSec = null;
			}
			blnRet = true;
		}
		catch (IOException IOExIgnore) {
		}
		finally {
			if (objWriter != null) {
				closeWriter(objWriter);
				objWriter = null;
			}
			if (objFile != null)
				objFile = null;
			if (itrSec != null)
				itrSec = null;
		}
		return blnRet;
	}

	/*------------------------------------------------------------------------------
	 * Helper functions
	 *----------------------------------------------------------------------------*/

	/**
	 * Helper function to check the date time formats.
	 * 
	 * @param pstrDtFmt the date time format string to be checked.
	 * @return true for valid date/time format, false otherwise.
	 */
	private boolean checkDateTimeFormat(String pstrDtFmt) {
		boolean blnRet = false;
		DateFormat objFmt = null;

		try {
			objFmt = new SimpleDateFormat(pstrDtFmt);
			blnRet = true;
		}
		catch (NullPointerException NPExIgnore) {
		}
		catch (IllegalArgumentException IAExIgnore) {
		}
		finally {
			if (objFmt != null)
				objFmt = null;
		}
		return blnRet;
	}

	/**
	 * Reads the INI file and load its contentens into a section collection after
	 * parsing the file line by line.
	 */
	@SuppressWarnings( { "unchecked", "unchecked" })
	private void loadFile() {
		int iPos = -1;
		String strLine = null;
		String strSection = null;
		String strRemarks = null;
		BufferedReader objBRdr = null;
		FileReader objFRdr = null;
		INISection objSec = null;

		try {
			objFRdr = new FileReader(this.mstrFile);
			if (objFRdr != null) {
				objBRdr = new BufferedReader(objFRdr);
				if (objBRdr != null) {
					while (objBRdr.ready()) {
						iPos = -1;
						strLine = null;
						strLine = objBRdr.readLine().trim();
						////
						strLine = strLine.replaceAll("\n\r", "");
						////
						if (strLine == null) {
						}
						else if (strLine.length() == 0) {
						}
						else if (strLine.startsWith("#")) {
							/*
							 if (strRemarks == null) {
							 strRemarks = strLine.substring(1).trim();
							 }
							 else if (strRemarks.length() == 0) {
							 strRemarks = strLine.substring(1).trim();
							 }
							 else {
							 strRemarks = strRemarks + linesep + strLine.substring(1).trim();
							 }
							 */
						}
						else if (strLine.startsWith("[") && strLine.endsWith("]")) {
							// Section start reached create new section
							if (objSec != null)
								this.mhmapSections.put(strSection.trim(), objSec);
							objSec = null;
							strSection = strLine.substring(1, strLine.length() - 1);
							objSec = new INISection(strSection.trim(), strRemarks);
							strRemarks = null;
						}
						else if ((iPos = strLine.indexOf("=")) > 0 && objSec != null) {
							// read the key value pair 012345=789
							objSec.setProperty(strLine.substring(0, iPos).trim(), strLine.substring(iPos + 1).trim(), strRemarks);
							strRemarks = null;
						}
					}
					if (objSec != null)
						this.mhmapSections.put(strSection.trim(), objSec);
				}
			}
		}
		catch (FileNotFoundException FNFExIgnore) {
			this.mhmapSections.clear();
		}
		catch (IOException IOExIgnore) {
			this.mhmapSections.clear();
		}
		catch (NullPointerException NPExIgnore) {
			this.mhmapSections.clear();
		}
		finally {
			if (objBRdr != null) {
				closeReader(objBRdr);
				objBRdr = null;
			}
			if (objFRdr != null) {
				closeReader(objFRdr);
				objFRdr = null;
			}
			if (objSec != null)
				objSec = null;
		}
	}

	/**
	 * Helper function to close a reader object.
	 * 
	 * @param pobjRdr the reader to be closed.
	 */
	private void closeReader(Reader pobjRdr) {
		if (pobjRdr == null)
			return;
		try {
			pobjRdr.close();
		}
		catch (IOException IOExIgnore) {
		}
	}

	/**
	 * Helper function to close a writer object.
	 * 
	 * @param pobjWriter the writer to be closed.
	 */
	private void closeWriter(Writer pobjWriter) {
		if (pobjWriter == null)
			return;

		try {
			pobjWriter.close();
		}
		catch (IOException IOExIgnore) {
		}
	}

	/**
	 * Helper method to check the existance of a file.
	 * 
	 * @param the full path and name of the file to be checked.
	 * @return true if file exists, false otherwise.
	 */
	private boolean checkFile(String pstrFile) {
		boolean blnRet = false;
		File objFile = null;

		try {
			objFile = new File(pstrFile);
			blnRet = (objFile.exists() && objFile.isFile());
		}
		catch (Exception e) {
			blnRet = false;
		}
		finally {
			if (objFile != null)
				objFile = null;
		}
		return blnRet;
	}

	/**
	 * Converts a java.util.date into String
	 * 
	 * @param pd Date that need to be converted to String
	 * @param pstrFmt The date format pattern.
	 * @return String
	 */
	private String utilDateToStr(Date pdt, String pstrFmt) {
		String strRet = null;
		SimpleDateFormat dtFmt = null;

		try {
			dtFmt = new SimpleDateFormat(pstrFmt);
			strRet = dtFmt.format(pdt);
		}
		catch (Exception e) {
			strRet = null;
		}
		finally {
			if (dtFmt != null)
				dtFmt = null;
		}
		return strRet;
	}

	/**
	 * Converts the given sql timestamp object to a string representation. The
	 * format to be used is to be obtained from the configuration file.
	 * 
	 * @param pobjTS the sql timestamp object to be converted.
	 * @param pblnGMT If true formats the string using GMT timezone otherwise
	 *        using local timezone.
	 * @return the formatted string representation of the timestamp.
	 */
	private String timeToStr(Timestamp pobjTS, String pstrFmt) {
		String strRet = null;
		SimpleDateFormat dtFmt = null;

		try {
			dtFmt = new SimpleDateFormat(pstrFmt);
			strRet = dtFmt.format(pobjTS);
		}
		catch (IllegalArgumentException iae) {
			strRet = "";
		}
		catch (NullPointerException npe) {
			strRet = "";
		}
		finally {
			if (dtFmt != null)
				dtFmt = null;
		}
		return strRet;
	}

	/**
	 * This function deletes the remark characters ';' from source string
	 * 
	 * @param pstrSrc the source string
	 * @return the converted string
	 */
	private String delRemChars(String pstrSrc) {
		int intPos = 0;

		if (pstrSrc == null)
			return null;
		while ((intPos = pstrSrc.indexOf("#")) >= 0) {
			if (intPos == 0)
				pstrSrc = pstrSrc.substring(intPos + 1);
			else if (intPos > 0)
				pstrSrc = pstrSrc.substring(0, intPos) + pstrSrc.substring(intPos + 1);
		}
		return pstrSrc;
	}

	/**
	 * This function adds a remark character '#' in source string.
	 * 
	 * @param pstrSrc source string
	 * @return converted string.
	 */
	private String addRemChars(String pstrSrc) {
		int intLen = 2;
		int intPos = 0;
		int intPrev = 0;

		String strLeft = null;
		String strRight = null;

		if (pstrSrc == null)
			return null;
		while (intPos >= 0) {
			intLen = 2;
			intPos = pstrSrc.indexOf(linesep, intPrev);
			if (intPos == 0) {
				pstrSrc = "#" + linesep + pstrSrc.substring(intPos + intLen);
				intPrev = intPos + intLen + 1;
			}
			else if (intPos > 0) {
				strLeft = pstrSrc.substring(0, intPos).trim();
				strRight = pstrSrc.substring(intPos + intLen).trim();
				if (strRight == null)
					pstrSrc = strLeft;
				else if (strRight.length() == 0)
					pstrSrc = strLeft;
				else
					pstrSrc = strLeft + linesep + "# " + strRight;
				intPrev = intPos + intLen + 1;
			}
		}
		try {
			if (!pstrSrc.substring(0, 1).equals("#")) {
				pstrSrc = "# " + pstrSrc;
			}
			pstrSrc = pstrSrc + linesep;
		}
		catch (Exception ex) {
		}
		return pstrSrc;
	}

	/*------------------------------------------------------------------------------
	 * Main entry point to test the functionality.
	 *----------------------------------------------------------------------------*/
	/**
	 * The main entry point for testing.
	 * 
	 * @param pstrArgs the command line arguments array if any.
	 */
	public static void main(String[] pstrArgs) {
		INIFile objINI = null;
		String strFile = null;

		strFile = "/Temp/abc.ini";

		// Following call will load the strFile if one exists.
		objINI = new INIFile(strFile);

		System.out.println(objINI.getStringProperty("Folders", "folder1"));
		System.out.println(objINI.getStringProperty("Folders", "folder2"));

		objINI.addSection("QADatabase", "QA database connection details\nUsed for QA Testing");
		objINI.setStringProperty("QADatabase", "SID", "ORCL", null);
		objINI.setStringProperty("QADatabase", "UserId", "System", null);
		objINI.setStringProperty("QADatabase", "Password", "Manager", null);
		objINI.setStringProperty("QADatabase", "HostName", "DBServer", null);
		objINI.setIntegerProperty("QADatabase", "Port", 1521, null);
		objINI.setStringProperty("QADatabase", "OracleHome", "%ORACLE_HOME%", null);

		objINI.setStringProperty("Folders", "folder1", "C:\\Temp", null);
		objINI.setStringProperty("Folders", "folder2", "C:\\Temp\\Backup", null);

		// Save changes back to strFile.
		objINI.save();
		objINI = null;
	}

	/*------------------------------------------------------------------------------
	 * Private class representing the INI Section.
	 *----------------------------------------------------------------------------*/
	/**
	 * Class to represent the individual ini file section.
	 * 
	 * @author Prasad P. Khandekar
	 * @version 1.0
	 * @since 1.0
	 */
	private class INISection {
		/** Variable to hold any comments associated with this section */
		private String mstrComment;

		/** Variable to hold the section name. */
		private String mstrName;

		/** Variable to hold the properties falling under this section. */
		private LinkedHashMap mhmapProps;

		/**
		 * Construct a new section object identified by the name specified in
		 * parameter.
		 * 
		 * @param pstrSection The new sections name.
		 */
		public INISection(String pstrSection) {
			this.mstrName = pstrSection;
			this.mhmapProps = new LinkedHashMap();
		}

		/**
		 * Construct a new section object identified by the name specified in
		 * parameter and associated comments.
		 * 
		 * @param pstrSection The new sections name.
		 * @param pstrComments the comments associated with this section.
		 */
		public INISection(String pstrSection, String pstrComments) {
			this.mstrName = pstrSection;
			this.mstrComment = delRemChars(pstrComments);
			this.mhmapProps = new LinkedHashMap();
		}

		/**
		 * Returns any comments associated with this section
		 * 
		 * @return the comments
		 */
		public String getSecComments() {
			return this.mstrComment;
		}

		/**
		 * Returns name of the section.
		 * 
		 * @return Name of the section.
		 */
		public String getSecName() {
			return this.mstrName;
		}

		/**
		 * Sets the comments associated with this section.
		 * 
		 * @param pstrComments the comments
		 */
		public void setSecComments(String pstrComments) {
			this.mstrComment = delRemChars(pstrComments);
		}

		/**
		 * Sets the section name.
		 * 
		 * @param pstrName the section name.
		 */
		public void setSecName(String pstrName) {
			this.mstrName = pstrName;
		}

		/**
		 * Removes specified property value from this section.
		 * 
		 * @param pstrProp The name of the property to be removed.
		 */
		public void removeProperty(String pstrProp) {
			if (this.mhmapProps.containsKey(pstrProp))
				this.mhmapProps.remove(pstrProp);
		}

		/**
		 * Creates or modifies the specified property value.
		 * 
		 * @param pstrProp The name of the property to be created or modified.
		 * @param pstrValue The new value for the property.
		 * @param pstrComments the associated comments
		 */
		@SuppressWarnings("unchecked")
		public void setProperty(String pstrProp, String pstrValue, String pstrComments) {
			this.mhmapProps.put(pstrProp, new INIProperty(pstrProp, pstrValue, pstrComments));
		}

		/**
		 * Returns a map of all properties.
		 * 
		 * @return a map of all properties
		 */
		@SuppressWarnings("unchecked")
		public Map getProperties() {
			return Collections.unmodifiableMap(this.mhmapProps);
		}

		/**
		 * Returns a string array containing names of all the properties under this
		 * section.
		 * 
		 * @return the string array of property names.
		 */
		public String[] getPropNames() {
			int iCntr = 0;
			String[] arrRet = null;
			Iterator iter = null;

			try {
				if (this.mhmapProps.size() > 0) {
					arrRet = new String[this.mhmapProps.size()];
					for (iter = this.mhmapProps.keySet().iterator(); iter.hasNext();) {
						arrRet[iCntr] = (String) iter.next();
						iCntr++;
					}
				}
			}
			catch (NoSuchElementException NSEExIgnore) {
				arrRet = null;
			}
			return arrRet;
		}

		/**
		 * Returns underlying value of the specified property.
		 * 
		 * @param pstrProp the property whose underlying value is to be etrieved.
		 * @return the property value.
		 */
		public INIProperty getProperty(String pstrProp) {
			INIProperty objRet = null;

			if (this.mhmapProps.containsKey(pstrProp))
				objRet = (INIProperty) this.mhmapProps.get(pstrProp);
			return objRet;
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		public String toString() {
			Set colKeys = null;
			String strRet = "";
			Iterator iter = null;
			INIProperty objProp = null;
			StringBuffer objBuf = new StringBuffer();

			if (this.mstrComment != null)
				objBuf.append(addRemChars(this.mstrComment));
			objBuf.append("[" + this.mstrName + "]" + linesep);
			colKeys = this.mhmapProps.keySet();
			if (colKeys != null) {
				iter = colKeys.iterator();
				if (iter != null) {
					while (iter.hasNext()) {
						objProp = (INIProperty) this.mhmapProps.get(iter.next());
						objBuf.append(objProp.toString());
						objBuf.append(linesep);
						objProp = null;
					}
				}
			}
			strRet = objBuf.toString();

			objBuf = null;
			iter = null;
			colKeys = null;
			return strRet;
		}
	}

	/*------------------------------------------------------------------------------
	 * Private class representing the INI Property.
	 *----------------------------------------------------------------------------*/
	/**
	 * This class represents a key value pair called property in an INI file.
	 * 
	 * @author Prasad P. Khandekar
	 * @version 1.0
	 * @since 1.0
	 */
	private class INIProperty {
		/** Variable to hold name of this property */
		private String mstrName;
		/** Variable to hold value of this property */
		private String mstrValue;
		/** Variable to hold comments associated with this property */
		private String mstrComments;

		/**
		 * Constructor
		 * 
		 * @param pstrName the name of this property.
		 * @param pstrValue the value of this property.
		 */
		public INIProperty(String pstrName, String pstrValue) {
			this.mstrName = pstrName;
			this.mstrValue = pstrValue;
		}

		/**
		 * Constructor
		 * 
		 * @param pstrName the name of this property.
		 * @param pstrValue the value of this property.
		 * @param pstrComments the comments associated with this property.
		 */
		public INIProperty(String pstrName, String pstrValue, String pstrComments) {
			this.mstrName = pstrName;
			this.mstrValue = pstrValue;
			this.mstrComments = delRemChars(pstrComments);
		}

		/**
		 * Returns the string identifier (key part) of this property.
		 * 
		 * @return the string identifier of this property.
		 */
		public String getPropName() {
			return this.mstrName;
		}

		/**
		 * Returns value of this property. If value contains a reference to
		 * environment avriable then this reference is replaced by actual value
		 * before the value is returned.
		 * 
		 * @return the value of this property.
		 */
		public String getPropValue() {
			return this.mstrValue;
		}

		/**
		 * Returns comments associated with this property.
		 * 
		 * @return the associated comments if any.
		 */
		public String getPropComments() {
			return this.mstrComments;
		}

		/**
		 * Sets the string identifier (key part) of a property
		 * 
		 * @param pstrName the string identifier of a property
		 */
		public void setPropName(String pstrName) {
			this.mstrName = pstrName;
		}

		/**
		 * Sets the property value
		 * 
		 * @param pstrValue the value for the property
		 */
		public void setPropValue(String pstrValue) {
			this.mstrValue = pstrValue;
		}

		/**
		 * Sets the comments for a property
		 * 
		 * @param pstrComments the comments
		 */
		public void setPropComments(String pstrComments) {
			this.mstrComments = delRemChars(pstrComments);
		}

		/*
		 * (non-Javadoc)
		 * 
		 * @see java.lang.Object#toString()
		 */
		public String toString() {
			String strRet = "";

			if (this.mstrComments != null)
				strRet = addRemChars(mstrComments);
			strRet = strRet + this.mstrName + " = " + this.mstrValue;
			return strRet;
		}
	}
}
