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