001/*------------------------------------------------------------------------------
002 * PACKAGE: com.freeware.IniFiles
003 * FILE   : IniFile.java
004 * CREATED: Jun 30, 2004
005 * AUTHOR : Prasad P. Khandekar
006 *------------------------------------------------------------------------------
007 * Change Log:
008 * 05/07/2004    - Added support for date time formats.
009 *                 Added support for environment variables.
010 * 07/07/2004    - Added support for data type specific getters and setters.
011 *                 Updated main method to reflect above changes.
012 * 26/08/2004    - Added support for section level and property level comments.
013 *                 Introduction of seperate class for property values.
014 *                 Added addSection method.
015 *                 Sections and properties now retail their order (LinkedHashMap)
016 *                 Method implementation changes.
017 *-----------------------------------------------------------------------------*/
018package org.hl7.fhir.utilities;
019
020import java.io.BufferedReader;
021import java.io.ByteArrayInputStream;
022import java.io.File;
023import java.io.FileNotFoundException;
024import java.io.FileReader;
025import java.io.FileWriter;
026import java.io.IOException;
027import java.io.InputStream;
028import java.io.InputStreamReader;
029import java.io.OutputStream;
030import java.io.OutputStreamWriter;
031import java.io.Reader;
032import java.io.Writer;
033import java.sql.Timestamp;
034import java.text.DateFormat;
035import java.text.ParseException;
036import java.text.SimpleDateFormat;
037import java.util.Collections;
038import java.util.Date;
039import java.util.Iterator;
040import java.util.LinkedHashMap;
041import java.util.Map;
042import java.util.NoSuchElementException;
043import java.util.Properties;
044import java.util.Set;
045
046
047/**
048 * IniFile class provides methods for manipulating (Read/Write) windows ini files.
049 * 
050 * @author Prasad P. Khandekar
051 * @version 1.0
052 * @since 1.0
053 */
054public final class IniFile
055{
056    /** Variable to represent the date format */
057    private String mstrDateFmt = "yyyy-MM-dd";
058
059    /** Variable to represent the timestamp format */
060    private String mstrTimeStampFmt = "yyyy-MM-dd HH:mm:ss";
061
062    /** Variable to denote the successful load operation. */
063    @SuppressWarnings("unused")
064    private boolean mblnLoaded = false;
065
066    /** Variable to hold the ini file name and full path */
067    private String mstrFile;
068
069    /** Variable to hold the sections in an ini file. */
070    private LinkedHashMap<String, INISection> mhmapSections;
071
072    /** Variable to hold environment variables **/
073    private Properties mpropEnv;
074
075    /**
076     * Create a IniFile object from the file named in the parameter.
077     * @param pstrPathAndName The full path and name of the ini file to be used.
078     */
079    public IniFile(String pstrPathAndName)
080    {
081        this.mpropEnv = getEnvVars();
082        this.mhmapSections = new LinkedHashMap<String, INISection>();
083        this.mstrFile = pstrPathAndName;
084        // Load the specified INI file.
085        if (checkFile(pstrPathAndName)) loadFile();
086    }
087
088    public IniFile(InputStream stream) {
089      this.mpropEnv = getEnvVars();
090      this.mhmapSections = new LinkedHashMap<String, INISection>();
091      this.mstrFile = null;
092      // Load the specified INI file.
093      loadStream(stream);
094    }
095
096    /*------------------------------------------------------------------------------
097 * Getters
098------------------------------------------------------------------------------*/
099    /**
100     * Returns the ini file name being used.
101     * @return the INI file name.
102     */
103    public String getFileName()
104    {
105        return this.mstrFile;
106    }
107
108    /**
109     * Returns the specified string property from the specified section.
110     * @param pstrSection the INI section name.
111     * @param pstrProp the property to be retrieved.
112     * @return the string property value.
113     */
114    public String getStringProperty(String pstrSection, String pstrProp)
115    {
116        String      strRet   = null;
117        INIProperty objProp  = null;
118        INISection  objSec   = null;
119
120        objSec = (INISection) this.mhmapSections.get(pstrSection);
121        if (objSec != null)
122        {
123            objProp = objSec.getProperty(pstrProp);
124            if (objProp != null)
125            {
126                strRet = objProp.getPropValue();
127                objProp = null;
128            }
129            objSec = null;
130        }
131        return strRet;
132    }
133
134    /**
135     * Returns the specified boolean property from the specified section.
136     * This method considers the following values as boolean values.
137     * <ol>
138     *      <li>YES/yes/Yes - boolean true</li>
139     *      <li>NO/no/No  - boolean false</li>
140     *      <li>1 - boolean true</li>
141     *      <li>0 - boolean false</li>
142     *      <li>TRUE/True/true - boolean true</li>
143     *      <li>FALSE/False/false - boolean false</li>
144     * </ol>
145     * @param pstrSection the INI section name.
146     * @param pstrProp the property to be retrieved.
147     * @return the boolean value
148     */
149    public Boolean getBooleanProperty(String pstrSection, String pstrProp)
150    {
151        boolean     blnRet  = false;
152        String      strVal  = null;
153        INIProperty objProp = null;
154        INISection  objSec  = null;
155
156        objSec = (INISection) this.mhmapSections.get(pstrSection);
157        if (objSec != null)
158        {
159            objProp = objSec.getProperty(pstrProp);
160            if (objProp != null)
161            {
162                strVal = objProp.getPropValue().toUpperCase();
163                if (strVal.equals("YES") || strVal.equals("TRUE") ||
164                    strVal.equals("1"))
165                {
166                    blnRet = true;
167                }
168                objProp = null;
169            }
170            objSec = null;
171        }
172        return new Boolean(blnRet);
173    }
174
175    /**
176     * Returns the specified integer property from the specified section.
177     * @param pstrSection the INI section name.
178     * @param pstrProp the property to be retrieved.
179     * @return the integer property value.
180     */
181    public Integer getIntegerProperty(String pstrSection, String pstrProp)
182    {
183        Integer     intRet  = null;
184        String      strVal  = null;
185        INIProperty objProp = null;
186        INISection  objSec  = null;
187
188        objSec = (INISection) this.mhmapSections.get(pstrSection);
189        if (objSec != null)
190        {
191            objProp = objSec.getProperty(pstrProp);
192            try
193            {
194                if (objProp != null)
195                {
196                    strVal = objProp.getPropValue();
197                    if (strVal != null) intRet = new Integer(strVal);
198                }
199            }
200            catch (NumberFormatException NFExIgnore)
201            {
202            }
203            finally
204            {
205                if (objProp != null) objProp = null;
206            }
207            objSec = null;
208        }
209        return intRet;
210    }
211
212    /**
213     * Returns the specified long property from the specified section.
214     * @param pstrSection the INI section name.
215     * @param pstrProp the property to be retrieved.
216     * @return the long property value.
217     */
218    public Long getLongProperty(String pstrSection, String pstrProp)
219    {
220        Long        lngRet  = null;
221        String      strVal  = null;
222        INIProperty objProp = null;
223        INISection  objSec  = null;
224
225        objSec = (INISection) this.mhmapSections.get(pstrSection);
226        if (objSec != null)
227        {
228            objProp = objSec.getProperty(pstrProp);
229            try
230            {
231                if (objProp != null)
232                {
233                    strVal = objProp.getPropValue();
234                    if (strVal != null) lngRet = new Long(strVal);
235                }
236            }
237            catch (NumberFormatException NFExIgnore)
238            {
239            }
240            finally
241            {
242                if (objProp != null) objProp = null;
243            }
244            objSec = null;
245        }
246        return lngRet;
247    }
248
249    /**
250     * Returns the specified double property from the specified section.
251     * @param pstrSection the INI section name.
252     * @param pstrProp the property to be retrieved.
253     * @return the double property value.
254     */
255    public Double getDoubleProperty(String pstrSection, String pstrProp)
256    {
257        Double      dblRet  = null;
258        String      strVal  = null;
259        INIProperty objProp = null;
260        INISection  objSec  = null;
261
262        objSec = (INISection) this.mhmapSections.get(pstrSection);
263        if (objSec != null)
264        {
265            objProp = objSec.getProperty(pstrProp);
266            try
267            {
268                if (objProp != null)
269                {
270                    strVal = objProp.getPropValue();
271                    if (strVal != null) dblRet = new Double(strVal);
272                }
273            }
274            catch (NumberFormatException NFExIgnore)
275            {
276            }
277            finally
278            {
279                if (objProp != null) objProp = null;
280            }
281            objSec = null;
282        }
283        return dblRet;
284    }
285
286    /**
287     * Returns the specified date property from the specified section.
288     * @param pstrSection the INI section name.
289     * @param pstrProp the property to be retrieved.
290     * @return the date property value.
291     */
292    public Date getDateProperty(String pstrSection, String pstrProp)
293    {
294        Date        dtRet   = null;
295        String      strVal  = null;
296        DateFormat  dtFmt   = null;
297        INIProperty objProp = null;
298        INISection  objSec  = null;
299
300        objSec = (INISection) this.mhmapSections.get(pstrSection);
301        if (objSec != null)
302        {
303            objProp = objSec.getProperty(pstrProp);
304            try
305            {
306                if (objProp != null) strVal = objProp.getPropValue();
307                if (strVal != null)
308                {
309                    dtFmt = new SimpleDateFormat(this.mstrDateFmt);
310                    dtRet = dtFmt.parse(strVal);
311                }
312            }
313            catch (ParseException PExIgnore)
314            {
315            }
316            catch (IllegalArgumentException IAEx)
317            {
318            }
319            finally
320            {
321                if (objProp != null) objProp = null;
322            }
323            objSec = null;
324        }
325        return dtRet;
326    }
327
328    /**
329     * Returns the specified date property from the specified section.
330     * @param pstrSection the INI section name.
331     * @param pstrProp the property to be retrieved.
332     * @return the date property value.
333     */
334    public Date getTimestampProperty(String pstrSection, String pstrProp)
335    {
336        Timestamp   tsRet   = null;
337        Date        dtTmp   = null;
338        String      strVal  = null;
339        DateFormat  dtFmt   = null;
340        INIProperty objProp = null;
341        INISection  objSec  = null;
342
343        objSec = (INISection) this.mhmapSections.get(pstrSection);
344        if (objSec != null)
345        {
346            objProp = objSec.getProperty(pstrProp);
347            try
348            {
349                if (objProp != null) strVal = objProp.getPropValue();
350                if (strVal != null)
351                {
352                    dtFmt = new SimpleDateFormat(this.mstrDateFmt);
353                    dtTmp = dtFmt.parse(strVal);
354                    tsRet = new Timestamp(dtTmp.getTime());
355                }
356            }
357            catch (ParseException PExIgnore)
358            {
359            }
360            catch (IllegalArgumentException IAEx)
361            {
362            }
363            finally
364            {
365                if (objProp != null) objProp = null;
366            }
367            objSec = null;
368        }
369        return tsRet;
370    }
371
372/*------------------------------------------------------------------------------
373 * Setters
374------------------------------------------------------------------------------*/
375    /**
376     * Sets the comments associated with a section.
377     * @param pstrSection the section name
378     * @param pstrComments the comments.
379     */
380    public void addSection(String pstrSection, String pstrComments)
381    {
382        INISection objSec   = null;
383
384        objSec = (INISection) this.mhmapSections.get(pstrSection);
385        if (objSec == null)
386        {
387            objSec = new INISection(pstrSection);
388            this.mhmapSections.put(pstrSection, objSec);
389        }
390        objSec.setSecComments(delRemChars(pstrComments));
391        objSec = null;
392    }
393
394    /**
395     * Sets the specified string property.
396     * @param pstrSection the INI section name.
397     * @param pstrProp the property to be set.
398     * @pstrVal the string value to be persisted
399     */
400    public void setStringProperty(String pstrSection, String pstrProp, 
401                                                String pstrVal, String pstrComments)
402    {
403        INISection objSec   = null;
404
405        objSec = (INISection) this.mhmapSections.get(pstrSection);
406        if (objSec == null)
407        {
408            objSec = new INISection(pstrSection);
409            this.mhmapSections.put(pstrSection, objSec);
410        }
411        objSec.setProperty(pstrProp, pstrVal, pstrComments);
412    }
413
414    /**
415     * Sets the specified boolean property.
416     * @param pstrSection the INI section name.
417     * @param pstrProp the property to be set.
418     * @param pblnVal the boolean value to be persisted
419     */
420    public void setBooleanProperty(String pstrSection, String pstrProp, 
421                                                boolean pblnVal, String pstrComments)
422    {
423        INISection objSec   = null;
424
425        objSec = (INISection) this.mhmapSections.get(pstrSection);
426        if (objSec == null)
427        {
428            objSec = new INISection(pstrSection);
429            this.mhmapSections.put(pstrSection, objSec);
430        }
431        if (pblnVal)
432            objSec.setProperty(pstrProp, "TRUE", pstrComments);
433        else
434            objSec.setProperty(pstrProp, "FALSE", pstrComments);
435    }
436
437    /**
438     * Sets the specified integer property.
439     * @param pstrSection the INI section name.
440     * @param pstrProp the property to be set.
441     * @param pintVal the int property to be persisted.
442     */
443    public void setIntegerProperty(String pstrSection, String pstrProp, 
444                                                int pintVal, String pstrComments)
445    {
446        INISection objSec   = null;
447
448        objSec = (INISection) this.mhmapSections.get(pstrSection);
449        if (objSec == null)
450        {
451            objSec = new INISection(pstrSection);
452            this.mhmapSections.put(pstrSection, objSec);
453        }
454        objSec.setProperty(pstrProp, Integer.toString(pintVal), pstrComments);
455    }
456
457    /**
458     * Sets the specified long property.
459     * @param pstrSection the INI section name.
460     * @param pstrProp the property to be set.
461     * @param plngVal the long value to be persisted.
462     */
463    public void setLongProperty(String pstrSection, String pstrProp, 
464                                        long plngVal, String pstrComments)
465    {
466        INISection objSec   = null;
467
468        objSec = (INISection) this.mhmapSections.get(pstrSection);
469        if (objSec == null)
470        {
471            objSec = new INISection(pstrSection);
472            this.mhmapSections.put(pstrSection, objSec);
473        }
474        objSec.setProperty(pstrProp, Long.toString(plngVal), pstrComments);
475    }
476
477    /**
478     * Sets the specified double property.
479     * @param pstrSection the INI section name.
480     * @param pstrProp the property to be set.
481     * @param pdblVal the double value to be persisted.
482     */
483    public void setDoubleProperty(String pstrSection, String pstrProp, 
484                                                double pdblVal, String pstrComments)
485    {
486        INISection objSec   = null;
487
488        objSec = (INISection) this.mhmapSections.get(pstrSection);
489        if (objSec == null)
490        {
491            objSec = new INISection(pstrSection);
492            this.mhmapSections.put(pstrSection, objSec);
493        }
494        objSec.setProperty(pstrProp, Double.toString(pdblVal), pstrComments);
495    }
496
497    /**
498     * Sets the specified java.util.Date property.
499     * @param pstrSection the INI section name.
500     * @param pstrProp the property to be set.
501     * @param pdtVal the date value to be persisted.
502     */
503    public void setDateProperty(String pstrSection, String pstrProp, 
504                                        Date pdtVal, String pstrComments)
505    {
506        INISection objSec   = null;
507
508        objSec = (INISection) this.mhmapSections.get(pstrSection);
509        if (objSec == null)
510        {
511            objSec = new INISection(pstrSection);
512            this.mhmapSections.put(pstrSection, objSec);
513        }
514        objSec.setProperty(pstrProp, utilDateToStr(pdtVal, this.mstrDateFmt), 
515                                pstrComments);
516    }
517
518    /**
519     * Sets the specified java.sql.Timestamp property.
520     * @param pstrSection the INI section name.
521     * @param pstrProp the property to be set.
522     * @param ptsVal the timestamp value to be persisted.
523     */
524    public void setTimestampProperty(String pstrSection, String pstrProp, 
525                                                        Timestamp ptsVal, String pstrComments)
526    {
527        INISection objSec   = null;
528
529        objSec = (INISection) this.mhmapSections.get(pstrSection);
530        if (objSec == null)
531        {
532            objSec = new INISection(pstrSection);
533            this.mhmapSections.put(pstrSection, objSec);
534        }
535        objSec.setProperty(pstrProp, timeToStr(ptsVal, this.mstrTimeStampFmt), 
536                                pstrComments);
537    }
538
539    /**
540     * Sets the format to be used to interpreat date values.
541     * @param pstrDtFmt the format string
542     * @throws IllegalArgumentException if the if the given pattern is invalid
543     */
544    public void setDateFormat(String pstrDtFmt) throws IllegalArgumentException
545    {
546        if (!checkDateTimeFormat(pstrDtFmt))
547            throw new IllegalArgumentException("The specified date pattern is invalid!");
548        this.mstrDateFmt = pstrDtFmt;
549    }
550
551    /**
552     * Sets the format to be used to interpreat timestamp values.
553     * @param pstrTSFmt the format string
554     * @throws IllegalArgumentException if the if the given pattern is invalid
555     */
556    public void setTimeStampFormat(String pstrTSFmt)
557    {
558        if (!checkDateTimeFormat(pstrTSFmt))
559            throw new IllegalArgumentException("The specified timestamp pattern is invalid!");
560        this.mstrTimeStampFmt = pstrTSFmt;
561    }
562
563/*------------------------------------------------------------------------------
564 * Public methods
565------------------------------------------------------------------------------*/
566    public int getTotalSections()
567    {
568        return this.mhmapSections.size();
569    }
570
571    /**
572     * Returns a string array containing names of all sections in INI file.
573     * @return the string array of section names
574     */
575    public String[] getAllSectionNames()
576    {
577        int        iCntr  = 0;
578        Iterator<String>   iter   = null;
579        String[]   arrRet = null;
580
581        try
582        {
583            if (this.mhmapSections.size() > 0)
584            {
585                arrRet = new String[this.mhmapSections.size()];
586                for (iter = this.mhmapSections.keySet().iterator();;iter.hasNext())
587                {
588                    arrRet[iCntr] = (String) iter.next();
589                    iCntr++;
590                }
591            }
592        }
593        catch (NoSuchElementException NSEExIgnore)
594        {
595        }
596        finally
597        {
598            if (iter != null) iter = null;
599        }
600        return arrRet;
601    }
602
603    /**
604     * Returns a string array containing names of all the properties under specified section.
605     * @param pstrSection the name of the section for which names of properties is to be retrieved.
606     * @return the string array of property names.
607     */
608    public String[] getPropertyNames(String pstrSection)
609    {
610        String[]   arrRet = null;
611        INISection objSec = null;
612
613        objSec = (INISection) this.mhmapSections.get(pstrSection);
614        if (objSec != null)
615        {
616            arrRet = objSec.getPropNames();
617            objSec = null;
618        }
619        return arrRet;
620    }
621
622    /**
623     * Returns a map containing all the properties under specified section.
624     * @param pstrSection the name of the section for which properties are to be retrieved.
625     * @return the map of properties.
626     */
627    public Map<String, INIProperty> getProperties(String pstrSection)
628    {
629        Map<String, INIProperty>        hmRet = null;
630        INISection objSec = null;
631
632        objSec = (INISection) this.mhmapSections.get(pstrSection);
633        if (objSec != null)
634        {
635            hmRet = objSec.getProperties();
636            objSec = null;
637        }
638        return hmRet;
639    }
640
641    /**
642     * Removed specified property from the specified section. If the specified
643     * section or the property does not exist, does nothing.
644     * @param pstrSection the section name.
645     * @param pstrProp the name of the property to be removed.
646     */
647    public void removeProperty(String pstrSection, String pstrProp)
648    {
649        INISection objSec = null;
650
651        objSec = (INISection) this.mhmapSections.get(pstrSection);
652        if (objSec != null)
653        {
654            objSec.removeProperty(pstrProp);
655                objSec = null;
656        }
657    }
658
659    /**
660     * Removes the specified section if one exists, otherwise does nothing.
661     * @param pstrSection the name of the section to be removed.
662     */
663    public void removeSection(String pstrSection)
664    {
665        if (this.mhmapSections.containsKey(pstrSection))
666            this.mhmapSections.remove(pstrSection);
667    }
668
669    /**
670     * Flush changes back to the disk file. If the disk file does not exists then
671     * creates the new one. 
672     * @ 
673     */
674    public boolean save() 
675    {
676        boolean    blnRet    = false;
677        File       objFile   = null;
678        String     strName   = null;
679        String     strTemp   = null;
680        Iterator<String>   itrSec    = null;
681        INISection objSec    = null;
682        FileWriter objWriter = null;
683
684        try
685        {
686            if (this.mhmapSections.size() == 0) return false;
687            objFile = new CSFile(this.mstrFile);
688            if (objFile.exists()) objFile.delete();
689            objWriter = new FileWriter(objFile);
690            itrSec = this.mhmapSections.keySet().iterator();
691            while (itrSec.hasNext())
692            {
693                strName = (String) itrSec.next();
694                objSec = (INISection) this.mhmapSections.get(strName);
695                strTemp = objSec.toString();
696                objWriter.write(strTemp);
697                objWriter.write("\r\n");
698                objSec = null;
699            }
700            blnRet = true;
701        }
702        catch (IOException IOExIgnore)
703        {
704        }
705        finally
706        {
707            if (objWriter != null)
708            {
709                closeWriter(objWriter);
710                objWriter = null;
711            }
712            if (objFile != null) objFile = null;
713            if (itrSec != null) itrSec = null;
714        }
715        return blnRet;
716    }
717
718    public boolean save(OutputStream stream) 
719    {
720        boolean    blnRet    = false;
721        String     strName   = null;
722        String     strTemp   = null;
723        Iterator<String>   itrSec    = null;
724        INISection objSec    = null;
725        OutputStreamWriter objWriter = null;
726
727        try
728        {
729            if (this.mhmapSections.size() == 0) return false;
730            objWriter = new OutputStreamWriter(stream, "UTF-8");
731            itrSec = this.mhmapSections.keySet().iterator();
732            while (itrSec.hasNext())
733            {
734                strName = (String) itrSec.next();
735                objSec = (INISection) this.mhmapSections.get(strName);
736                strTemp = objSec.toString();
737                objWriter.write(strTemp);
738                objWriter.write("\r\n");
739                objSec = null;
740            }
741            blnRet = true;
742        }
743        catch (IOException IOExIgnore)
744        {
745        }
746        finally
747        {
748            if (objWriter != null)
749            {
750                closeWriter(objWriter);
751                objWriter = null;
752            }
753            if (itrSec != null) itrSec = null;
754        }
755        return blnRet;
756    }
757
758    
759/*------------------------------------------------------------------------------
760 * Helper functions
761 *----------------------------------------------------------------------------*/
762    /**
763     * Procedure to read environment variables.
764     * Thanx to http://www.rgagnon.com/howto.html for this implementation.
765     */
766    private Properties getEnvVars()
767    {
768        Process p = null;
769        Properties envVars = new Properties();
770
771        try
772        {
773            Runtime r = Runtime.getRuntime();
774            String OS = System.getProperty("os.name").toLowerCase();
775
776            if (OS.indexOf("windows 9") > -1)
777            {
778                p = r.exec("command.com /c set");
779            }
780            else if ((OS.indexOf("nt") > -1) ||
781                     (OS.indexOf("windows 2000") > -1) ||
782                     (OS.indexOf("windows xp") > -1))
783            {
784                p = r.exec("cmd.exe /c set");
785            }
786            else
787            {
788                // our last hope, we assume Unix (thanks to H. Ware for the fix)
789                p = r.exec("env");
790            }
791            BufferedReader br = new BufferedReader(new InputStreamReader(p.getInputStream()));
792            String line;
793            while((line = br.readLine()) != null)
794            {
795                int idx = line.indexOf('=');
796                String key = line.substring(0, idx);
797                String value = line.substring(idx + 1);
798                envVars.setProperty(key, value);
799            }
800        }
801        catch (Exception ExIgnore)
802        {
803        }
804        return envVars;
805    }
806
807    /**
808     * Helper function to check the date time formats.
809     * @param pstrDtFmt the date time format string to be checked.
810     * @return true for valid date/time format, false otherwise.
811     */
812    private boolean checkDateTimeFormat(String pstrDtFmt)
813    {
814        boolean    blnRet = false;
815        DateFormat objFmt = null;
816
817        try
818        {
819            objFmt = new SimpleDateFormat(pstrDtFmt);
820            blnRet = true;
821        }
822        catch (NullPointerException NPExIgnore)
823        {
824        }
825        catch (IllegalArgumentException IAExIgnore)
826        {
827        }
828        finally
829        {
830            if (objFmt != null) objFmt = null;
831        }
832        return blnRet;
833    }
834
835    /**
836     * Reads the INI file and load its contentens into a section collection after 
837     * parsing the file line by line. 
838     */
839    private void loadStream(InputStream stream)
840    {
841        int            iPos       = -1;
842        String         strLine    = null;
843        String         strSection = null;
844        String         strRemarks = null;
845        BufferedReader objBRdr    = null;
846        InputStreamReader     objFRdr    = null;
847        INISection     objSec     = null;
848
849        try
850        {
851            objFRdr = new InputStreamReader(stream);
852            if (objFRdr != null)
853            {
854                objBRdr = new BufferedReader(objFRdr);
855                if (objBRdr != null)
856                {
857                    while (objBRdr.ready())
858                    {
859                        iPos = -1;
860                        strLine  = null;
861                        strLine = objBRdr.readLine().trim();
862                        if (strLine == null)
863                        {
864                        }
865                        else if (strLine.length() == 0)
866                        {
867                        }
868                        else if (strLine.substring(0, 1).equals(";"))
869                        {
870                            if (strRemarks == null)
871                                strRemarks = strLine.substring(1);
872                            else if (strRemarks.length() == 0)
873                                strRemarks = strLine.substring(1);
874                            else
875                                strRemarks = strRemarks + "\r\n" + strLine.substring(1);
876                        }
877                        else if (strLine.startsWith("[") && strLine.endsWith("]"))
878                        {
879                            // Section start reached create new section
880                            if (objSec != null) 
881                                this.mhmapSections.put(strSection.trim(), objSec);
882                            objSec = null;
883                            strSection = strLine.substring(1, strLine.length() - 1);
884                            objSec = new INISection(strSection.trim(), strRemarks);
885                            strRemarks = null;
886                        }
887                        else if ((iPos = strLine.indexOf("=")) > 0 && objSec != null)
888                        {
889                            // read the key value pair 012345=789
890                            objSec.setProperty(strLine.substring(0, iPos).trim(), 
891                                                strLine.substring(iPos + 1).trim(), 
892                                                strRemarks);
893                            strRemarks = null;
894                        }
895                        else 
896                        {
897                            objSec.setProperty(strLine, "", strRemarks);
898                      
899                        }
900                    }
901                    if (objSec != null)
902                        this.mhmapSections.put(strSection.trim(), objSec);
903                    this.mblnLoaded = true;
904                }
905            }
906        }
907        catch (FileNotFoundException FNFExIgnore)
908        {
909            this.mhmapSections.clear();
910        }
911        catch (IOException IOExIgnore)
912        {
913            this.mhmapSections.clear();
914        }
915        catch (NullPointerException NPExIgnore)
916        {
917            this.mhmapSections.clear();
918        }
919        finally
920        {
921            if (objBRdr != null)
922            {
923                closeReader(objBRdr);
924                objBRdr = null;
925            }
926            if (objFRdr != null)
927            {
928                closeReader(objFRdr);
929                objFRdr = null;
930            }
931            if (objSec != null) objSec = null;
932        }
933    }
934
935    /**
936     * Reads the INI file and load its contentens into a section collection after 
937     * parsing the file line by line. 
938     */
939    private void loadFile()
940    {
941        int            iPos       = -1;
942        String         strLine    = null;
943        String         strSection = null;
944        String         strRemarks = null;
945        BufferedReader objBRdr    = null;
946        FileReader     objFRdr    = null;
947        INISection     objSec     = null;
948
949        try
950        {
951            objFRdr = new FileReader(this.mstrFile);
952            if (objFRdr != null)
953            {
954                objBRdr = new BufferedReader(objFRdr);
955                if (objBRdr != null)
956                {
957                    while (objBRdr.ready())
958                    {
959                        iPos = -1;
960                        strLine  = null;
961                        strLine = objBRdr.readLine().trim();
962                        if (strLine == null)
963                        {
964                        }
965                        else if (strLine.length() == 0)
966                        {
967                        }
968                        else if (strLine.substring(0, 1).equals(";"))
969                        {
970                            if (strRemarks == null)
971                                strRemarks = strLine.substring(1);
972                            else if (strRemarks.length() == 0)
973                                strRemarks = strLine.substring(1);
974                            else
975                                strRemarks = strRemarks + "\r\n" + strLine.substring(1);
976                        }
977                        else if (strLine.startsWith("[") && strLine.endsWith("]"))
978                        {
979                            // Section start reached create new section
980                            if (objSec != null) 
981                                this.mhmapSections.put(strSection.trim(), objSec);
982                            objSec = null;
983                            strSection = strLine.substring(1, strLine.length() - 1);
984                            objSec = new INISection(strSection.trim(), strRemarks);
985                            strRemarks = null;
986                        }
987                        else if ((iPos = strLine.indexOf("=")) > 0 && objSec != null)
988                        {
989                            // read the key value pair 012345=789
990                            objSec.setProperty(strLine.substring(0, iPos).trim(), 
991                                                strLine.substring(iPos + 1).trim(), 
992                                                strRemarks);
993                            strRemarks = null;
994                        }
995                        else 
996                        {
997                            objSec.setProperty(strLine, "", strRemarks);
998                      
999                        }
1000                    }
1001                    if (objSec != null)
1002                        this.mhmapSections.put(strSection.trim(), objSec);
1003                    this.mblnLoaded = true;
1004                }
1005            }
1006        }
1007        catch (FileNotFoundException FNFExIgnore)
1008        {
1009            this.mhmapSections.clear();
1010        }
1011        catch (IOException IOExIgnore)
1012        {
1013            this.mhmapSections.clear();
1014        }
1015        catch (NullPointerException NPExIgnore)
1016        {
1017            this.mhmapSections.clear();
1018        }
1019        finally
1020        {
1021            if (objBRdr != null)
1022            {
1023                closeReader(objBRdr);
1024                objBRdr = null;
1025            }
1026            if (objFRdr != null)
1027            {
1028                closeReader(objFRdr);
1029                objFRdr = null;
1030            }
1031            if (objSec != null) objSec = null;
1032        }
1033    }
1034
1035    /**
1036     * Helper function to close a reader object.
1037     * @param pobjRdr the reader to be closed.
1038     */
1039    private void closeReader(Reader pobjRdr)
1040    {
1041        if (pobjRdr == null) return;
1042        try
1043        {
1044            pobjRdr.close();
1045        }
1046        catch (IOException IOExIgnore)
1047        {
1048        }
1049    }
1050
1051    /**
1052     * Helper function to close a writer object.
1053     * @param pobjWriter the writer to be closed.
1054     */
1055    private void closeWriter(Writer pobjWriter)
1056    {
1057        if (pobjWriter == null) return;
1058
1059        try
1060        {
1061            pobjWriter.close();
1062        }
1063        catch (IOException IOExIgnore)
1064        {
1065        }
1066    }
1067    
1068    /**
1069     * Helper method to check the existance of a file.
1070     * @param the full path and name of the file to be checked.
1071     * @return true if file exists, false otherwise.
1072     */
1073    private boolean checkFile(String pstrFile)
1074    {
1075        boolean blnRet  = false;
1076        File    objFile = null;
1077
1078        try
1079        {
1080            objFile = new CSFile(pstrFile);
1081            blnRet = (objFile.exists() && objFile.isFile());
1082        }
1083        catch (Exception e)
1084        {
1085            blnRet = false;
1086        }
1087        finally
1088        {
1089            if (objFile != null) objFile = null;
1090        }
1091        return blnRet;
1092    }
1093
1094    /**
1095     * Converts a java.util.date into String 
1096     * @param pd Date that need to be converted to String 
1097     * @param pstrFmt The date format pattern.
1098     * @return String
1099     */
1100    private String utilDateToStr(Date pdt, String pstrFmt)
1101    {
1102        String strRet = null;
1103        SimpleDateFormat dtFmt = null;
1104
1105        try
1106        {
1107            dtFmt = new SimpleDateFormat(pstrFmt);
1108            strRet = dtFmt.format(pdt);
1109        }
1110        catch (Exception e)
1111        {
1112            strRet = null;
1113        }
1114        finally
1115        {
1116            if (dtFmt != null) dtFmt = null;
1117        }
1118        return strRet;
1119    }
1120
1121    /**
1122     * Converts the given sql timestamp object to a string representation. The format
1123     * to be used is to be obtained from the configuration file.
1124     *  
1125     * @param pobjTS the sql timestamp object to be converted.
1126     * @param pblnGMT If true formats the string using GMT  timezone 
1127     * otherwise using local timezone. 
1128     * @return the formatted string representation of the timestamp.
1129     */
1130    private String timeToStr(Timestamp pobjTS, String pstrFmt)
1131    {
1132        String strRet = null;
1133        SimpleDateFormat dtFmt = null;
1134
1135        try
1136        {
1137            dtFmt = new SimpleDateFormat(pstrFmt);
1138            strRet = dtFmt.format(pobjTS);
1139        }
1140        catch (IllegalArgumentException  iae)
1141        {
1142            strRet = "";
1143        }
1144        catch (NullPointerException npe)
1145        {
1146            strRet = "";
1147        }
1148        finally
1149        {
1150            if (dtFmt != null) dtFmt = null;
1151        }
1152        return strRet;
1153    }
1154
1155    /**
1156     * This function deletes the remark characters ';' from source string
1157     * @param pstrSrc the source  string
1158     * @return the converted string
1159     */
1160    private String delRemChars(String pstrSrc)
1161    {
1162        int    intPos = 0;
1163
1164        if (pstrSrc == null) return null;
1165        while ((intPos = pstrSrc.indexOf(";")) >= 0)
1166        {
1167            if (intPos == 0)
1168                pstrSrc = pstrSrc.substring(intPos + 1);
1169            else if (intPos > 0)
1170                pstrSrc = pstrSrc.substring(0, intPos) + pstrSrc.substring(intPos + 1);
1171        }
1172        return pstrSrc;
1173    }
1174
1175    /**
1176     * This function adds a remark character ';' in source string.
1177     * @param pstrSrc source string
1178     * @return converted string.
1179     */
1180    private String addRemChars(String pstrSrc)
1181    {
1182        int intLen  = 2;
1183        int intPos  = 0;
1184        int intPrev = 0;
1185
1186        String strLeft  = null;
1187        String strRight = null;
1188
1189        if (pstrSrc == null) return null;
1190        while (intPos >= 0)
1191        {
1192            intLen = 2;
1193            intPos = pstrSrc.indexOf("\r\n", intPrev);
1194            if (intPos < 0)
1195            {
1196                intLen = 1;
1197                intPos = pstrSrc.indexOf("\n", intPrev);
1198                if (intPos < 0) intPos = pstrSrc.indexOf("\r", intPrev);
1199            }
1200            if (intPos == 0)
1201            {
1202                pstrSrc = ";\r\n" + pstrSrc.substring(intPos + intLen);
1203                intPrev = intPos + intLen + 1;
1204            }
1205            else if (intPos > 0)
1206            {
1207                strLeft = pstrSrc.substring(0, intPos);
1208                strRight = pstrSrc.substring(intPos + intLen);
1209                if (strRight == null)
1210                    pstrSrc = strLeft;
1211                else if (strRight.length() == 0)
1212                    pstrSrc = strLeft;
1213                else
1214                    pstrSrc = strLeft + "\r\n;" + strRight;
1215                intPrev = intPos + intLen + 1;
1216            }
1217        }
1218        if (!pstrSrc.substring(0, 1).equals(";"))
1219            pstrSrc = ";" + pstrSrc;
1220        pstrSrc = pstrSrc + "\r\n";
1221        return pstrSrc;
1222    }
1223/*------------------------------------------------------------------------------
1224 * Main entry point to test the functionality.
1225 *----------------------------------------------------------------------------*/
1226    /**
1227     * The main entry point for testing.
1228     * @param pstrArgs the command line arguments array if any.
1229     * @ 
1230     */
1231    public static void main(String[] pstrArgs) 
1232    {
1233        IniFile objINI = null;
1234        String  strFile = null;
1235
1236        if (pstrArgs.length == 0) return;
1237
1238        strFile = pstrArgs[0];
1239        // Following call will load the strFile if one exists.
1240        objINI = new IniFile(strFile);
1241
1242//        objINI.addSection("QADatabase", "QA database connection details\nUsed for QA Testing");
1243//        objINI.setStringProperty("QADatabase", "SID", "ORCL", null);
1244//        objINI.setStringProperty("QADatabase", "UserId", "System", null);
1245//        objINI.setStringProperty("QADatabase", "Password", "Manager", null);
1246//        objINI.setStringProperty("QADatabase", "HostName", "DBServer", null);
1247//        objINI.setIntegerProperty("QADatabase", "Port", 1521, null);
1248//        objINI.setStringProperty("QADatabase", "OracleHome", "%ORACLE_HOME%", null);
1249//        
1250        // objINI.setSectionComments("Folders", "Directories where generated files are stored");
1251        objINI.setStringProperty("Folders", "folder1", "G:\\Temp", null);
1252        objINI.setStringProperty("Folders", "folder2", "G:\\Temp\\Backup", null);
1253
1254        // Save changes back to strFile.
1255        objINI.save();
1256        objINI = null;
1257    }
1258
1259/*------------------------------------------------------------------------------
1260 * Private class representing the INI Section.
1261 *----------------------------------------------------------------------------*/
1262    /**
1263     * Class to represent the individual ini file section.
1264     * @author Prasad P. Khandekar
1265     * @version 1.0
1266     * @since 1.0
1267     */
1268    private class INISection
1269    {
1270        /** Variable to hold any comments associated with this section */
1271        private String mstrComment;
1272
1273        /** Variable to hold the section name. */
1274        private String mstrName;
1275        
1276        /** Variable to hold the properties falling under this section. */
1277        private LinkedHashMap<String, INIProperty> mhmapProps;
1278
1279        /**
1280         * Construct a new section object identified by the name specified in 
1281         * parameter.
1282         * @param pstrSection The new sections name.
1283         */
1284        public INISection(String pstrSection)
1285        {
1286            this.mstrName =  pstrSection;
1287            this.mhmapProps = new LinkedHashMap<String, INIProperty>();
1288        }
1289
1290        /**
1291         * Construct a new section object identified by the name specified in 
1292         * parameter and associated comments.
1293         * @param pstrSection The new sections name.
1294         * @param pstrComments the comments associated with this section.
1295         */
1296        public INISection(String pstrSection, String pstrComments)
1297        {
1298            this.mstrName =  pstrSection;
1299            this.mstrComment = delRemChars(pstrComments);
1300            this.mhmapProps = new LinkedHashMap<String, INIProperty>();
1301        }
1302        
1303        /**
1304         * Sets the comments associated with this section.
1305         * @param pstrComments the comments
1306         */
1307        public void setSecComments(String pstrComments)
1308        {
1309            this.mstrComment = delRemChars(pstrComments);
1310        }
1311
1312        /**
1313         * Removes specified property value from this section. 
1314         * @param pstrProp The name of the property to be removed.
1315         */
1316        public void removeProperty(String pstrProp)
1317        {
1318            if (this.mhmapProps.containsKey(pstrProp))
1319                this.mhmapProps.remove(pstrProp);
1320        }
1321
1322        /**
1323         * Creates or modifies the specified property value.
1324         * @param pstrProp The name of the property to be created or modified. 
1325         * @param pstrValue The new value for the property.
1326         * @param pstrComments the associated comments
1327         */
1328        public void setProperty(String pstrProp, String pstrValue, String pstrComments)
1329        {
1330            this.mhmapProps.put(pstrProp, new INIProperty(pstrProp, pstrValue, pstrComments));
1331        }
1332
1333        /**
1334         * Returns a map of all properties.
1335         * @return a map of all properties
1336         */
1337        public Map<String, INIProperty> getProperties()
1338        {
1339            return Collections.unmodifiableMap(this.mhmapProps);
1340        }
1341
1342        /**
1343         * Returns a string array containing names of all the properties under 
1344         * this section. 
1345         * @return the string array of property names.
1346         */
1347        public String[] getPropNames()
1348        {
1349            int      iCntr  = 0;
1350            String[] arrRet = null;
1351            Iterator<String> iter   = null;
1352
1353            try
1354            {
1355                if (this.mhmapProps.size() > 0)
1356                {
1357                    arrRet = new String[this.mhmapProps.size()]; 
1358                    for (iter = this.mhmapProps.keySet().iterator();iter.hasNext();)
1359                    {
1360                        arrRet[iCntr] = (String) iter.next();
1361                        iCntr++;
1362                    }
1363                }
1364            }
1365            catch (NoSuchElementException NSEExIgnore)
1366            {
1367                arrRet = null;
1368            }
1369            return arrRet;
1370        }
1371
1372        /**
1373         * Returns underlying value of the specified property. 
1374         * @param pstrProp the property whose underlying value is to be etrieved.
1375         * @return the property value.
1376         */
1377        public INIProperty getProperty(String pstrProp)
1378        {
1379            INIProperty objRet = null;
1380
1381            if (this.mhmapProps.containsKey(pstrProp))
1382                objRet = (INIProperty) this.mhmapProps.get(pstrProp);
1383            return objRet;
1384        }
1385
1386        /* (non-Javadoc)
1387         * @see java.lang.Object#toString()
1388         */
1389        @Override
1390                public String toString()
1391        {
1392            Set<String>          colKeys = null;
1393            String       strRet  = "";
1394            Iterator<String>     iter    = null;
1395            INIProperty  objProp = null;
1396            StringBuffer objBuf  = new StringBuffer();
1397
1398            if (this.mstrComment != null)
1399                objBuf.append(addRemChars(this.mstrComment));
1400            objBuf.append("[" + this.mstrName + "]\r\n");
1401            colKeys = this.mhmapProps.keySet();
1402            if (colKeys != null)
1403            {
1404                iter = colKeys.iterator();
1405                if (iter != null)
1406                {
1407                    while (iter.hasNext())
1408                    {
1409                        objProp = (INIProperty) this.mhmapProps.get(iter.next());
1410                        objBuf.append(objProp.toString());
1411                        objBuf.append("\r\n");
1412                        objProp = null;
1413                    }
1414                }
1415            }
1416            strRet = objBuf.toString();
1417
1418            objBuf  = null;
1419            iter    = null;
1420            colKeys = null;
1421            return strRet;
1422        }
1423    }
1424
1425/*------------------------------------------------------------------------------
1426 * Private class representing the INI Property.
1427 *----------------------------------------------------------------------------*/
1428    /**
1429     * This class represents a key value pair called property in an INI file. 
1430     * @author Prasad P. Khandekar
1431     * @version 1.0
1432     * @since 1.0
1433     */
1434    private class INIProperty
1435    {
1436        /** Variable to hold name of this property */
1437        private String mstrName;
1438        /** Variable to hold value of this property */
1439        private String mstrValue;
1440        /** Variable to hold comments associated with this property */
1441        private String mstrComments;
1442
1443        /**
1444         * Constructor
1445         * @param pstrName the name of this property.
1446         * @param pstrValue the value of this property.
1447         * @param pstrComments the comments associated with this property.
1448         */
1449        public INIProperty(String pstrName, String pstrValue, String pstrComments)
1450        {
1451            this.mstrName = pstrName;
1452            this.mstrValue = pstrValue;
1453            this.mstrComments = delRemChars(pstrComments);
1454        }
1455
1456        /**
1457         * Returns value of this property. If value contains a reference to 
1458         * environment avriable then this reference is replaced by actual value
1459         * before the value is returned.
1460         * @return the value of this property.
1461         */
1462        public String getPropValue()
1463        {
1464            int    intStart = 0;
1465            int    intEnd   = 0;
1466            String strVal   = null;
1467            String strVar   = null;
1468            String strRet   = null;
1469
1470            strRet = this.mstrValue;
1471            intStart = strRet.indexOf("%");
1472            if (intStart >= 0)
1473            {
1474                intEnd = strRet.indexOf("%", intStart + 1);
1475                strVar = strRet.substring(intStart + 1, intEnd);
1476                strVal = mpropEnv.getProperty(strVar);
1477                if (strVal != null)
1478                {
1479                    strRet = strRet.substring(0, intStart) + strVal + 
1480                                strRet.substring(intEnd + 1);
1481                }
1482            }
1483            return strRet;
1484        }
1485
1486        /* (non-Javadoc)
1487         * @see java.lang.Object#toString()
1488         */
1489        @Override
1490                public String toString()
1491        {
1492            String strRet = "";
1493
1494            if (this.mstrComments != null)
1495                strRet = addRemChars(mstrComments);
1496            strRet = strRet + this.mstrName + " = " + this.mstrValue;
1497            return strRet;
1498        }
1499    }
1500}