1 /**************************************************************
2  *
3  * Licensed to the Apache Software Foundation (ASF) under one
4  * or more contributor license agreements.  See the NOTICE file
5  * distributed with this work for additional information
6  * regarding copyright ownership.  The ASF licenses this file
7  * to you under the Apache License, Version 2.0 (the
8  * "License"); you may not use this file except in compliance
9  * with the License.  You may obtain a copy of the License at
10  *
11  *   http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing,
14  * software distributed under the License is distributed on an
15  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16  * KIND, either express or implied.  See the License for the
17  * specific language governing permissions and limitations
18  * under the License.
19  *
20  *************************************************************/
21 
22 
23 
24 import java.io.File;
25 import java.io.RandomAccessFile;
26 import java.util.ArrayList;
27 import java.util.Enumeration;
28 
29 
30 /**
31  * Simple implementation of a inifile manager
32  */
33 class GlobalLogWriter
34 {
35     public static void println(String _s)
36     {
37         System.out.println(_s);
38     }
39 }
40 
41 /**
42    Helper class to give a simple API to read/write windows like ini files
43 */
44 
45 /* public */ // is only need, if we need this class outside package convwatch
46 public class IniFile implements Enumeration
47 {
48 
49     /**
50      * internal representation of the ini file content.
51      * Problem, if ini file changed why other write something difference, we don't realise this.
52      */
53     private String m_sFilename;
54     // private File m_aFile;
55     private ArrayList<String> m_aList;
56     boolean m_bListContainUnsavedChanges = false;
57     private int m_aEnumerationPos = 0;
58 
59     /**
60        open a ini file by it's name
61        @param _sFilename string a filename, if the file doesn't exist, a new empty ini file will create.
62        write back to disk only if there are really changes.
63     */
64     public IniFile(String _sFilename)
65         {
66             m_sFilename = _sFilename;
67             // m_aFile = new File(_sFilename);
68             m_aList = loadLines();
69             m_aEnumerationPos = findNextSection(0);
70         }
71 
72     /**
73        open a ini file by it's name
74        @param _aFile a java.io.File object, if the file doesn't exist, a new empty ini file will create.
75        write back to disk only if there are really changes.
76     */
77     public IniFile(File _aFile)
78     {
79         m_sFilename = _aFile.getAbsolutePath();
80         m_aList = loadLines();
81         m_aEnumerationPos = findNextSection(0);
82     }
83 
84     public void insertFirstComment(String[] _aList)
85         {
86             if (m_aList.size() == 0)
87             {
88                 // can only insert if there is nothing else already in the ini file
89                 for (int i = 0; i < _aList.length; i++)
90                 {
91                     m_aList.add(_aList[i]);
92                 }
93             }
94         }
95 
96     private ArrayList<String> loadLines()
97         {
98             ArrayList<String> aLines = new ArrayList<String>();
99             File aFile = new File(m_sFilename);
100             if (!aFile.exists())
101             {
102                 // GlobalLogWriter.println("couldn't find file '" + m_sFilename + "', will be created.");
103                 // DebugHelper.exception(BasicErrorCode.SbERR_FILE_NOT_FOUND, "");
104                 // m_bListContainUnsavedChanges = false;
105                 return aLines;
106             }
107             RandomAccessFile aReader = null;
108             // BufferedReader aReader;
109             try
110             {
111                 aReader = new RandomAccessFile(aFile, "r");
112                 String aLine = "";
113                 while (aLine != null)
114                 {
115                     aLine = aReader.readLine();
116                     if (aLine != null && aLine.length() > 0)
117                     {
118                         aLines.add(aLine);
119                     }
120                 }
121             }
122             catch (java.io.FileNotFoundException fne)
123             {
124                 GlobalLogWriter.println("couldn't open file " + m_sFilename);
125                 GlobalLogWriter.println("Message: " + fne.getMessage());
126                 // DebugHelper.exception(BasicErrorCode.SbERR_FILE_NOT_FOUND, "");
127             }
128             catch (java.io.IOException ie)
129             {
130                 GlobalLogWriter.println("Exception occurs while reading from file " + m_sFilename);
131                 GlobalLogWriter.println("Message: " + ie.getMessage());
132                 // DebugHelper.exception(BasicErrorCode.SbERR_INTERNAL_ERROR, ie.getMessage());
133             }
134             try
135             {
136                 aReader.close();
137             }
138             catch (java.io.IOException ie)
139             {
140                 GlobalLogWriter.println("Couldn't close file " + m_sFilename);
141                 GlobalLogWriter.println("Message: " + ie.getMessage());
142                 // DebugHelper.exception(BasicErrorCode.SbERR_INTERNAL_ERROR, ie.getMessage());
143             }
144             return aLines;
145         }
146 
147     /**
148      * @return true, if the ini file contain some readable data
149      */
150     public boolean is()
151         {
152             return m_aList.size() > 1 ? true : false;
153         }
154 
155     /**
156      * Check if a given Section and Key exists in the ini file
157      * @param _sSectionName
158      * @param _sKey
159      * @return true if the given Section, Key exists, now you can get the value
160      */
161     public boolean hasValue(String _sSectionName, String _sKey)
162         {
163             int n = findKey(_sSectionName, _sKey);
164             if (n > 0)
165             {
166                 return true;
167             }
168             return false;
169         }
170     // -----------------------------------------------------------------------------
171 
172     private boolean isRemark(String _sLine)
173         {
174             if (((_sLine.length() < 2)) ||
175                 (_sLine.startsWith("#")) ||
176                 (_sLine.startsWith(";")))
177             {
178                 return true;
179             }
180             return false;
181         }
182 
183     private String getItem(int i)
184         {
185             return m_aList.get(i);
186         }
187 
188     private String buildSectionName(String _sSectionName)
189         {
190             String sFindSection = "[" + _sSectionName + "]";
191             return sFindSection;
192         }
193 
194     private String sectionToString(String _sSectionName)
195         {
196             String sKeyName = _sSectionName;
197             if (sKeyName.startsWith("[") &&
198                 sKeyName.endsWith("]"))
199             {
200                 sKeyName = sKeyName.substring(1, sKeyName.length() - 1);
201             }
202             return sKeyName;
203         }
204 
205     private String toLowerIfNeed(String _sName)
206         {
207             return _sName.toLowerCase();
208         }
209 
210     // return the number where this section starts
211     private int findSection(String _sSection)
212         {
213             String sFindSection = toLowerIfNeed(buildSectionName(_sSection));
214             // ----------- find _sSection ---------------
215             int i;
216             for (i = 0; i < m_aList.size(); i++)
217             {
218                 String sLine = toLowerIfNeed(getItem(i).trim());
219                 if (isRemark(sLine))
220                 {
221                     continue;
222                 }
223                 if (sFindSection.equals("[]"))
224                 {
225                     // special case, empty Section.
226                     return i - 1;
227                 }
228                 if (sLine.startsWith(sFindSection))
229                 {
230                     return i;
231                 }
232             }
233             return -1;
234         }
235 
236     /**
237      * Checks if a given section exists in the ini file
238      * @param _sSection
239      * @return true if the given _sSection was found
240      */
241     public boolean hasSection(String _sSection)
242         {
243             int i = findSection(_sSection);
244             if (i == -1)
245             {
246                 return false;
247             }
248             return true;
249         }
250 
251     // return the line number, where the key is found.
252     private int findKey(String _sSection, String _sKey)
253         {
254             int i = findSection(_sSection);
255             if (i == -1)
256             {
257                 // Section not found, therefore the value can't exist
258                 return -1;
259             }
260             return findKeyFromKnownSection(i, _sKey);
261         }
262 
263     // i must be the index in the list, where the well known section starts
264     private int findKeyFromKnownSection(int _nSectionIndex, String _sKey)
265         {
266             _sKey = toLowerIfNeed(_sKey);
267             for (int j = _nSectionIndex + 1; j < m_aList.size(); j++)
268             {
269                 String sLine = getItem(j).trim();
270 
271                 if (isRemark(sLine))
272                 {
273                     continue;
274                 }
275                 if (sLine.startsWith("[") /* && sLine.endsWith("]") */)
276                 {
277                     // TODO: due to the fact we would like to insert an empty line before new sections
278                     // TODO: we should check if we are in an empty line and if, go back one line.
279 
280                     // found end.
281                     break;
282                 }
283 
284                 int nEqual = sLine.indexOf("=");
285                 if (nEqual >= 0)
286                 {
287                     String sKey = toLowerIfNeed(sLine.substring(0, nEqual).trim());
288                     if (sKey.equals(_sKey))
289                     {
290                         return j;
291                     }
292                 }
293             }
294             return -1;
295         }
296 
297     // i must be the index in the list, where the well known section starts
298     private int findLastKnownKeyIndex(int _nSectionIndex, String _sKey)
299         {
300             _sKey = toLowerIfNeed(_sKey);
301             int i = _nSectionIndex + 1;
302             for (int j = i; j < m_aList.size(); j++)
303             {
304                 String sLine = getItem(j).trim();
305 
306                 if (isRemark(sLine))
307                 {
308                     continue;
309                 }
310 
311                 if (sLine.startsWith("[") /* && sLine.endsWith("]") */)
312                 {
313                     // found end.
314                     return j;
315                 }
316 
317                 int nEqual = sLine.indexOf("=");
318                 if (nEqual >= 0)
319                 {
320                     String sKey = toLowerIfNeed(sLine.substring(0, nEqual).trim());
321                     if (sKey.equals(_sKey))
322                     {
323                         return j;
324                     }
325                 }
326             }
327             return i;
328         }
329 
330     private String getValue(int _nIndex)
331         {
332             String sLine = getItem(_nIndex).trim();
333             if (isRemark(sLine))
334             {
335                 return "";
336             }
337             int nEqual = sLine.indexOf("=");
338             if (nEqual >= 0)
339             {
340                 String sKey = sLine.substring(0, nEqual).trim();
341                 String sValue = sLine.substring(nEqual + 1).trim();
342                 return sValue;
343             }
344             return "";
345         }
346 
347     /**
348        @param _sSection string
349        @param _sKey string
350        @return the value found in the inifile which is given by the section and key parameter
351     */
352     // private int m_nCurrentPosition;
353     // private String m_sOldKey;
354     public String getValue(String _sSection, String _sKey)
355         {
356             String sValue = "";
357             int m_nCurrentPosition = findKey(_sSection, _sKey);
358             if (m_nCurrentPosition == -1)
359             {
360                 // Section not found, therefore the value can't exist
361                 return "";
362             }
363 
364             // m_sOldKey = _sKey;
365             sValue = getValue(m_nCurrentPosition);
366 
367             return sValue;
368         }
369 
370 //    private String getNextValue()
371 //    {
372 //        if (m_nCurrentPosition >= 0)
373 //        {
374 //            ++m_nCurrentPosition;
375 //            String sValue = getValue(m_nCurrentPosition);
376 //            return sValue;
377 //        }
378 //        return "";
379 //    }
380     /**
381      * Returns the value at Section, Key converted to an integer
382      * Check with hasValue(Section, Key) to check before you get into trouble.
383      * @param _sSection
384      * @param _sKey
385      * @param _nDefault if there is a problem, key not found... this value will return
386      * @return the value as integer if possible to convert, if not return default value.
387      */
388     public int getIntValue(String _sSection, String _sKey, int _nDefault)
389         {
390             String sValue = getValue(_sSection, _sKey);
391             int nValue = _nDefault;
392             if (sValue.length() > 0)
393             {
394                 try
395                 {
396                     nValue = Integer.valueOf(sValue).intValue();
397                 }
398                 catch (java.lang.NumberFormatException e)
399                 {
400                     GlobalLogWriter.println("IniFile.getIntValue(): Caught a number format exception, return the default value.");
401                 }
402             }
403             return nValue;
404         }
405 
406 /**
407  * close a open inifile.
408  * If there are changes, all changes will store back to disk.
409  */
410     public void close()
411         {
412             store();
413         }
414 
415     /**
416        write back the ini file to the disk, only if there exist changes
417        * @deprecated use close() instead!
418        */
419 
420     // TODO: make private
421     private void store()
422         {
423             if (m_bListContainUnsavedChanges == false)
424             {
425                 // nothing has changed, so no need to store
426                 return;
427             }
428 
429             File aFile = new File(m_sFilename);
430             if (aFile.exists())
431             {
432                 // System.out.println("couldn't find file " + m_sFilename);
433                 // TODO: little bit unsafe here, first rename, after write is complete, delete the old.
434                 aFile.delete();
435                 if (aFile.exists())
436                 {
437                     GlobalLogWriter.println("Couldn't delete the file " + m_sFilename);
438                     return;
439                     // DebugHelper.exception(BasicErrorCode.SbERR_INTERNAL_ERROR, "Couldn't delete the file " + m_sFilename);
440                 }
441             }
442             // if (! aFile.canWrite())
443             // {
444             //    System.out.println("Couldn't write to file " + m_sFilename);
445             //    DebugHelper.exception(BasicErrorCode.SbERR_INTERNAL_ERROR, "");
446             // }
447             try
448             {
449                 RandomAccessFile aWriter = new RandomAccessFile(aFile, "rw");
450                 for (int i = 0; i < m_aList.size(); i++)
451                 {
452                     String sLine = getItem(i);
453                     if (sLine.startsWith("["))
454                     {
455                         // write an extra empty line before next section.
456                         aWriter.writeByte((int) '\n');
457                     }
458                     aWriter.writeBytes(sLine);
459                     aWriter.writeByte((int) '\n');
460                 }
461                 aWriter.close();
462             }
463             catch (java.io.FileNotFoundException fne)
464             {
465                 GlobalLogWriter.println("couldn't open file for writing " + m_sFilename);
466                 GlobalLogWriter.println("Message: " + fne.getMessage());
467                 // DebugHelper.exception(BasicErrorCode.SbERR_FILE_NOT_FOUND, "");
468             }
469             catch (java.io.IOException ie)
470             {
471                 GlobalLogWriter.println("Exception occurs while writing to file " + m_sFilename);
472                 GlobalLogWriter.println("Message: " + ie.getMessage());
473                 // DebugHelper.exception(BasicErrorCode.SbERR_INTERNAL_ERROR, ie.getMessage());
474             }
475         }
476 
477     public void insertValue(String _sSection, String _sKey, int _nValue)
478         {
479             insertValue(_sSection, _sKey, String.valueOf(_nValue));
480         }
481 
482     public void insertValue(String _sSection, String _sKey, long _nValue)
483         {
484             insertValue(_sSection, _sKey, String.valueOf(_nValue));
485         }
486 
487     /**
488        insert a value
489        there are 3 cases
490        1. section doesn't exist, goto end and insert a new section, insert a new key value pair
491        2. section exist but key not, search section, search key, if key is -1 get last known key position and insert new key value pair there
492        3. section exist and key exist, remove the old key and insert the key value pair at the same position
493      * @param _sSection
494      * @param _sKey
495      * @param _sValue
496      */
497     public void insertValue(String _sSection, String _sKey, String _sValue)
498         {
499             int i = findSection(_sSection);
500             if (i == -1)
501             {
502                 // case 1: section doesn't exist
503                 String sFindSection = buildSectionName(_sSection);
504 
505                 // TODO: before create a new Section, insert a empty line
506                 m_aList.add(sFindSection);
507                 if (_sKey.length() > 0)
508                 {
509                     String sKeyValuePair = _sKey + "=" + _sValue;
510                     m_aList.add(sKeyValuePair);
511                 }
512                 m_bListContainUnsavedChanges = true;
513                 return;
514             }
515             int j = findKeyFromKnownSection(i, _sKey);
516             if (j == -1)
517             {
518                 // case 2: section exist, but not the key
519                 j = findLastKnownKeyIndex(i, _sKey);
520                 if (_sKey.length() > 0)
521                 {
522                     String sKeyValuePair = _sKey + "=" + _sValue;
523                     m_aList.add(j, sKeyValuePair);
524                     m_bListContainUnsavedChanges = true;
525                 }
526                 return;
527             }
528             else
529             {
530                 // case 3: section exist, and also the key
531                 String sKeyValuePair = _sKey + "=" + _sValue;
532                 m_aList.set(j, sKeyValuePair);
533                 m_bListContainUnsavedChanges = true;
534             }
535         }
536     // -----------------------------------------------------------------------------
537     // String replaceEvaluatedValue(String _sSection, String _sValue)
538     //     {
539     //         String sValue = _sValue;
540     //         int nIndex = 0;
541     //         while (( nIndex = sValue.indexOf("$(", nIndex)) >= 0)
542     //         {
543     //             int nNextIndex = sValue.indexOf(")", nIndex);
544     //             if (nNextIndex >= 0)
545     //             {
546     //                 String sKey = sValue.substring(nIndex + 2, nNextIndex);
547     //                 String sNewValue = getValue(_sSection, sKey);
548     //                 if (sNewValue != null && sNewValue.length() > 0)
549     //                 {
550     //                     String sRegexpKey = "\\$\\(" + sKey + "\\)";
551     //                     sValue = sValue.replaceAll(sRegexpKey, sNewValue);
552     //                 }
553     //                 nIndex = nNextIndex;
554     //             }
555     //             else
556     //             {
557     //                 nIndex += 2;
558     //             }
559     //         }
560     //         return sValue;
561     //     }
562     // -----------------------------------------------------------------------------
563 
564     // public String getLocalEvaluatedValue(String _sSection, String _sKey)
565     //     {
566     //         String sValue = getValue(_sSection, _sKey);
567     //         sValue = replaceEvaluatedValue(_sSection, sValue);
568     //         return sValue;
569     //     }
570 
571     // -----------------------------------------------------------------------------
572 
573     // this is a special behaviour.
574     // public String getGlobalLocalEvaluatedValue(String _sSection, String _sKey)
575     //     {
576     //         String sGlobalValue = getKey("global", _sKey);
577     //         String sLocalValue = getKey(_sSection, _sKey);
578     //         if (sLocalValue.length() == 0)
579     //         {
580     //             sGlobalValue = replaceEvaluatedKey(_sSection, sGlobalValue);
581     //             sGlobalValue = replaceEvaluatedKey("global", sGlobalValue);
582     //             return sGlobalValue;
583     //         }
584     //         sLocalValue = replaceEvaluatedKey(_sSection, sLocalValue);
585     //         sLocalValue = replaceEvaluatedKey("global", sLocalValue);
586     //
587     //         return sLocalValue;
588     //     }
589     public void removeSection(String _sSectionToRemove)
590         {
591             // first, search for the name
592             int i = findSection(_sSectionToRemove);
593             if (i == -1)
594             {
595                 // Section to remove not found, do nothing.
596                 return;
597             }
598             // second, find the next section
599             int j = findNextSection(i + 1);
600             if (j == -1)
601             {
602                 // if we are at the end, use size() as second section
603                 j = m_aList.size();
604             }
605             // remove all between first and second section
606             for (int k = i; k < j; k++)
607             {
608                 m_aList.remove(i);
609             }
610             // mark the list as changed
611             m_bListContainUnsavedChanges = true;
612         }
613 
614     /**
615      * some tests for this class
616      */
617 //    public static void main(String[] args)
618 //        {
619 //            String sTempFile = System.getProperty("java.io.tmpdir");
620 //            sTempFile += "inifile";
621 //
622 //
623 //            IniFile aIniFile = new IniFile(sTempFile);
624 //            String sValue = aIniFile.getValue("Section", "Key");
625 //            // insert a new value to a already exist section
626 //            aIniFile.insertValue("Section", "Key2", "a new value in a existing section");
627 //            // replace a value
628 //            aIniFile.insertValue("Section", "Key", "replaced value");
629 //            // create a new value
630 //            aIniFile.insertValue("New Section", "Key", "a new key value pair");
631 //            aIniFile.insertValue("New Section", "Key2", "a new second key value pair");
632 //
633 //            String sValue2 = aIniFile.getValue("Section2", "Key");
634 //
635 //            aIniFile.removeSection("Section");
636 //            aIniFile.removeSection("New Section");
637 //
638 //            aIniFile.close();
639 //        }
640 
641     /**
642      * Enumeration Interface
643      * @return true, if there are more Key values
644      */
645     public boolean hasMoreElements()
646         {
647             if (m_aEnumerationPos >= 0 &&
648                 m_aEnumerationPos < m_aList.size())
649             {
650                 return true;
651             }
652             return false;
653         }
654 
655     /**
656      * Find the next line, which starts with '['
657      * @param i start position
658      * @return the line where '[' found or -1
659      */
660     private int findNextSection(int i)
661         {
662             if (i >= 0)
663             {
664                 while (i < m_aList.size())
665                 {
666                     String sLine =  m_aList.get(i);
667                     if (sLine.startsWith("["))
668                     {
669                         return i;
670                     }
671                     i++;
672                 }
673             }
674             return -1;
675         }
676 
677     /**
678      * Enumeration Interface
679      * @return a key without the enveloped '[' ']'
680      */
681     public Object nextElement()
682         {
683             int nLineWithSection = findNextSection(m_aEnumerationPos);
684             if (nLineWithSection != -1)
685             {
686                 String sSection =  m_aList.get(nLineWithSection);
687                 m_aEnumerationPos = findNextSection(nLineWithSection + 1);
688                 sSection = sectionToString(sSection);
689                 return sSection;
690             }
691             else
692             {
693                 m_aEnumerationPos = m_aList.size();
694             }
695             return null;
696         }
697 
698     /**
699      * Helper to count the occurence of Sections
700      * @return returns the count of '^['.*']$' Elements
701      */
702     public int getElementCount()
703         {
704             int nCount = 0;
705             int nPosition = 0;
706             while ((nPosition = findNextSection(nPosition)) != -1)
707             {
708                 nCount++;
709                 nPosition++;
710             }
711             return nCount;
712         }
713 }
714 
715