1 package cz.cuni.amis.utils;
2
3 import java.io.BufferedReader;
4 import java.io.File;
5 import java.io.FileInputStream;
6 import java.io.FileWriter;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.PrintWriter;
11 import java.io.StringWriter;
12 import java.text.SimpleDateFormat;
13 import java.util.ArrayList;
14 import java.util.Collection;
15 import java.util.Collections;
16 import java.util.Comparator;
17 import java.util.Date;
18 import java.util.HashMap;
19 import java.util.Iterator;
20 import java.util.List;
21 import java.util.Map;
22 import java.util.Set;
23 import java.util.TreeMap;
24 import java.util.logging.Logger;
25
26 import cz.cuni.amis.utils.exception.PogamutException;
27 import cz.cuni.amis.utils.exception.PogamutIOException;
28
29 public class IniFile {
30
31 public static abstract class SectionEntry {
32
33 private int sectionEntryIndex;
34
35 public SectionEntry(int sectionEntryIndex) {
36 this.sectionEntryIndex = sectionEntryIndex;
37 }
38
39 public int getSectionEntryIndex() {
40 return sectionEntryIndex;
41 }
42
43 protected void setSectionEntryIndex(int sectionEntryIndex) {
44 this.sectionEntryIndex = sectionEntryIndex;
45 }
46
47 public abstract String getIniFileLines();
48
49 public abstract String getKey();
50
51 }
52
53 public static class SectionEntryComment extends SectionEntry {
54
55 private String comment;
56 private String text;
57 private String key = null;
58
59 public SectionEntryComment(int sectionEntryIndex, String text, String comment) {
60 super(sectionEntryIndex);
61 NullCheck.check(text, "text");
62 if (!text.startsWith(";")) text = ";" + text;
63 this.text = text;
64
65 int separ = text.indexOf("=");
66 if (separ < 0) return;
67 key = text.substring(0, separ);
68 if (key == null || key.isEmpty()) return;
69 while (key.startsWith(";")) key = key.substring(1);
70 }
71
72 public String getComment() {
73 return comment;
74 }
75
76 public void setComment(String comment) {
77 this.comment = comment;
78 }
79
80 public String getText() {
81 return text;
82 }
83
84 public void setText(String text) {
85 this.text = text;
86 }
87
88 @Override
89 public String getIniFileLines() {
90 if (comment != null) {
91 return comment + "\n" + text;
92 }
93 return text;
94 }
95
96 @Override
97 public String getKey() {
98 return key;
99 }
100
101 }
102
103 public static class SectionEntryKeyValue extends SectionEntry {
104
105 private String comment;
106 private String key;
107 private List<String> values = new ArrayList<String>();
108
109 public SectionEntryKeyValue(int sectionEntryIndex, String key, String value, String comment) {
110 super(sectionEntryIndex);
111 this.key = key;
112 this.values.add(value);
113 this.comment = comment;
114 if (this.comment != null) {
115 if (!this.comment.startsWith(";")) this.comment = ";" + this.comment;
116 }
117 NullCheck.check(this.key, "key");
118 NullCheck.check(value, "value");
119 }
120
121 public SectionEntryKeyValue(int sectionEntryIndex, String key, List<String> values, String comment) {
122 super(sectionEntryIndex);
123 this.key = key;
124 NullCheck.check(values, "values");
125 this.values.addAll(values);
126 this.comment = comment;
127 if (this.comment != null) {
128 if (!this.comment.startsWith(";")) this.comment = ";" + this.comment;
129 }
130 NullCheck.check(this.key, "key");
131 }
132
133 @Override
134 public String getKey() {
135 return key;
136 }
137
138 public String getValue() {
139 return values.size() > 0 ? values.get(0) : null;
140 }
141
142 public List<String> getValues() {
143 return values;
144 }
145
146 public void addValue(String value) {
147 values.add(value);
148 }
149
150 public void setValue(String value) {
151 values.clear();
152 values.add(value);
153 }
154
155 public String getComment() {
156 return comment;
157 }
158
159 public void setComment(String comment) {
160 this.comment = comment;
161 }
162
163 @Override
164 public String getIniFileLines() {
165 StringBuffer sb = new StringBuffer();
166 if (comment != null) {
167 sb.append(comment);
168 sb.append("\n");
169 }
170 boolean first = false;
171 for (String value : values) {
172 if (first) first = false;
173 else sb.append("\n");
174 sb.append(key);
175 sb.append("=");
176 sb.append(value);
177 }
178 return sb.toString();
179 }
180
181 }
182
183 public static final Comparator<SectionEntry> SECTION_ENTRY_INDEX_COMPARATOR = new Comparator<SectionEntry>() {
184
185 @Override
186 public int compare(SectionEntry o1, SectionEntry o2) {
187 return o1.getSectionEntryIndex() - o2.getSectionEntryIndex();
188 }
189 };
190
191 public static final Comparator<SectionEntry> SECTION_ENTRY_KEY_COMPARATOR = new Comparator<SectionEntry>() {
192
193 @Override
194 public int compare(SectionEntry o1, SectionEntry o2) {
195 int val = o1.getKey().compareTo(o2.getKey());
196 if (val != 0) return val;
197 if (o1 instanceof SectionEntryKeyValue) {
198 if (o2 instanceof SectionEntryComment) return -1;
199 return 0;
200 } else
201 if (o1 instanceof SectionEntryComment) {
202 if (o2 instanceof SectionEntryKeyValue) return 1;
203 return 0;
204 } else {
205 return 0;
206 }
207 }
208 };
209
210
211
212
213
214 public static class Section {
215
216 private static int nextSectionIndex = 0;
217 private int nextSectionEntryIndex = 0;
218 private int sectionIndex;
219 private String name;
220
221 private List<SectionEntryComment> comments = new ArrayList<SectionEntryComment>();
222
223 private Map<String, SectionEntryKeyValue> props = new HashMap<String, SectionEntryKeyValue>();
224
225
226
227
228
229
230
231
232 public Section(String name) {
233 this.name = name;
234 NullCheck.check(this.name, "name");
235 this.sectionIndex = nextSectionIndex++;
236 }
237
238
239
240
241
242 public Section(Section section) {
243 this.name = section.getName();
244 this.sectionIndex = nextSectionIndex++;
245 this.add(section);
246 }
247
248
249
250
251
252 public String getName() {
253 return name;
254 }
255
256
257
258
259
260
261
262 public Section put(String key, String value) {
263 return put(key, value, null);
264 }
265
266
267
268
269
270
271
272
273 public Section put(String key, String value, String comment) {
274 NullCheck.check(key, "key");
275 SectionEntryKeyValue entry = props.get(key);
276 if (entry != null) {
277 entry.setValue(value);
278 } else {
279 entry = new SectionEntryKeyValue(nextSectionEntryIndex++, key, value, comment);
280 props.put(key, entry);
281 }
282 return this;
283 }
284
285
286
287
288
289
290
291 public Section add(String key, String value) {
292 return add(key, value, null);
293 }
294
295
296
297
298
299
300
301
302 public Section add(String key, String value, String comment) {
303 NullCheck.check(key, "key");
304 SectionEntryKeyValue entry = props.get(key);
305 if (entry != null) {
306 entry.addValue(value);
307 } else {
308 entry = new SectionEntryKeyValue(nextSectionEntryIndex++, key, value, comment);
309 props.put(key, entry);
310 }
311 return this;
312 }
313
314
315
316
317
318
319 public String getOne(String key) {
320 SectionEntryKeyValue entry = props.get(key);
321 if (entry == null) return null;
322 return entry.getValue();
323 }
324
325
326
327
328
329
330 public List<String> getAll(String key) {
331 SectionEntryKeyValue entry = props.get(key);
332 if (entry == null) return null;
333 return entry.getValues();
334 }
335
336
337
338
339
340
341 public SectionEntryKeyValue getEntry(String key) {
342 SectionEntryKeyValue entry = props.get(key);
343 if (entry == null) return null;
344 return entry;
345 }
346
347
348
349
350
351
352 public boolean containsKey(String key) {
353 return props.containsKey(key);
354 }
355
356
357
358
359
360 public Set<String> getKeys() {
361 return props.keySet();
362 }
363
364
365
366
367
368 public Set<String> keySet() {
369 return getKeys();
370 }
371
372
373
374
375
376
377
378
379
380 public SectionEntryKeyValue remove(String key) {
381 SectionEntryKeyValue entry = props.remove(key);
382 indexDeleted(entry.getSectionEntryIndex());
383 return entry;
384 }
385
386 private void indexDeleted(int sectionEntryIndex) {
387 for (SectionEntryComment comment : comments) {
388 if (comment.getSectionEntryIndex() > sectionEntryIndex) {
389 comment.setSectionEntryIndex(comment.getSectionEntryIndex() - 1);
390 }
391 }
392 for (SectionEntryKeyValue keyValue : props.values()) {
393 if (keyValue.getSectionEntryIndex() > sectionEntryIndex) {
394 keyValue.setSectionEntryIndex(keyValue.getSectionEntryIndex() - 1);
395 }
396 }
397 }
398
399
400
401
402
403 public Section clear() {
404 props.clear();
405 comments.clear();
406 return this;
407 }
408
409
410
411
412
413 public Section clearComments() {
414 comments.clear();
415 return this;
416 }
417
418
419
420
421
422
423
424 public Section set(String key, String value) {
425 return put(key, value);
426 }
427
428
429
430
431
432
433
434
435 public Section set(String key, String value, String comment) {
436 return put(key, value, comment);
437 }
438
439
440
441
442
443 public void addComment(String comment) {
444 if (!comment.startsWith(";")) comment = ";" + comment;
445 if (!isComment(comment)) throw new RuntimeException("'" + comment + "' is not a comment!");
446 comments.add(new SectionEntryComment(nextSectionEntryIndex++, comment, null));
447 }
448
449 public void addComment(String keyValueCommented, String comment) {
450 if (!keyValueCommented.startsWith(";")) keyValueCommented = ";" + keyValueCommented;
451 if (comment == null) {
452 addComment(keyValueCommented);
453 return;
454 }
455 if (!comment.startsWith(";")) comment = ";" + comment;
456 if (!isComment(keyValueCommented)) throw new RuntimeException("'" + keyValueCommented + "' is not a comment!");
457 if (!hasCommentKey(keyValueCommented)) throw new RuntimeException("'" + keyValueCommented + "' is not commented out key=value!");
458 comments.add(new SectionEntryComment(nextSectionEntryIndex++, keyValueCommented, comment));
459 }
460
461
462
463
464
465
466
467 public Section add(Section section) {
468 for (SectionEntryKeyValue keyValue : props.values()) {
469 put(keyValue.getKey(), keyValue.getValue());
470 }
471 for (SectionEntryComment comment : section.comments) {
472 addComment(comment.getText());
473 }
474 return this;
475 }
476
477
478
479
480
481 public void output(PrintWriter writer) {
482 List<SectionEntry> output = new ArrayList<SectionEntry>(props.values());
483
484 List<SectionEntryComment> commentsWithKeys = new ArrayList<SectionEntryComment>(comments);
485 List<SectionEntryComment> commentsWithoutKeys = new ArrayList<SectionEntryComment>();
486 Iterator<SectionEntryComment> iter = commentsWithKeys.iterator();
487 while (iter.hasNext()) {
488 SectionEntryComment comment = iter.next();
489 if (comment.getKey() == null || comment.getKey().length() <= 0) {
490 commentsWithoutKeys.add(comment);
491 iter.remove();
492 } else {
493 output.add(comment);
494 }
495 }
496
497 Collections.sort(output, SECTION_ENTRY_KEY_COMPARATOR);
498
499 mergeInComments(output, commentsWithoutKeys);
500
501 writer.print("[");
502 writer.print(name);
503 writer.println("]");
504 for (SectionEntry entry : output) {
505 writer.println(entry.getIniFileLines());
506 }
507 }
508
509 private void mergeInComments(List<SectionEntry> output, List<SectionEntryComment> commentsWithKeys) {
510 if (output.size() == 0) {
511 output.addAll(commentsWithKeys);
512 Collections.sort(output, SECTION_ENTRY_INDEX_COMPARATOR);
513 return;
514 }
515 while(commentsWithKeys.size() > 0) {
516 Iterator<SectionEntryComment> iter = commentsWithKeys.iterator();
517 while (iter.hasNext()) {
518 SectionEntryComment comment = iter.next();
519 if (mergeInComment(output, comment)) {
520 iter.remove();
521 }
522 }
523 }
524 }
525
526 private boolean mergeInComment(List<SectionEntry> output, SectionEntryComment comment) {
527 if (output.size() == 0) {
528 output.add(comment);
529 return true;
530 }
531 for (int i = 0; i < output.size(); ++i) {
532 SectionEntry entry = output.get(i);
533 if (entry.getSectionEntryIndex()-1 == comment.getSectionEntryIndex()) {
534 output.add(i, comment);
535 return true;
536 }
537 }
538 if (comment.getSectionEntryIndex()-1 == output.get(output.size()-1).getSectionEntryIndex()) {
539 output.add(comment);
540 return true;
541 }
542 return false;
543 }
544
545 @Override
546 public String toString() {
547 return "IniFile.Section[name=" + name + ", entries=" + props.size() + "]";
548 }
549
550 }
551
552 private Map<String, Section> sections = new TreeMap<String, Section>(new Comparator<String>() {
553 @Override
554 public int compare(String o1, String o2) {
555 if (o1 == null) return -1;
556 if (o2 == null) return 1;
557 return o1.toLowerCase().compareTo(o2.toLowerCase());
558 }
559 });
560
561
562
563
564 public IniFile() {
565 }
566
567
568
569
570
571
572 public IniFile(File source) {
573 if (!source.exists()) {
574 throw new PogamutException("File with defaults does not exist at: " + source.getAbsolutePath() + ".", this);
575 }
576 load(source);
577 }
578
579
580
581
582
583
584 public IniFile(InputStream source) {
585 load(source);
586 }
587
588
589
590
591
592
593 public IniFile(IniFile ini) {
594 for (Section section : ini.getSections()) {
595 addSection(new Section(section));
596 }
597 }
598
599
600
601
602
603
604
605
606
607 public void load(File source) {
608 try {
609 load(new FileInputStream(source));
610 } catch (Exception e) {
611 throw new PogamutException("Could not load defaults for GameBots2004.ini from file: " + source.getAbsolutePath() + ", caused by: " + e.getMessage(), e);
612 }
613 }
614
615
616
617
618
619
620
621
622
623 public void load(InputStream source) {
624 BufferedReader reader = null;
625 reader = new BufferedReader(new InputStreamReader(source));
626
627 Section currSection = null;
628 String currComment = null;
629
630 try {
631 while (reader.ready()) {
632 String line = reader.readLine().trim();
633
634 if (line.length() == 0) {
635 continue;
636 }
637
638 if (isComment(line)) {
639 if (currSection == null) {
640 if (currComment == null) currComment = line.trim();
641 else currComment += "\n" + line;
642 } else {
643 if (hasCommentKey(line)) {
644 currSection.addComment(line, currComment);
645 } else {
646 if (currComment == null) currComment = line.trim();
647 else currComment += "\n" + line;
648 }
649 }
650 continue;
651 }
652
653 if (line.startsWith("[") && line.endsWith("]")) {
654 if (currComment != null && currSection != null) {
655 currSection.addComment(currComment);
656 }
657 if (currSection != null && getSection(currSection.getName()) == null) {
658 addSection(currSection);
659 }
660 String sectionName = line.substring(1, line.length()-1);
661 if (hasSection(sectionName)) {
662
663 currSection = getSection(sectionName);
664 } else {
665 currSection = getSection(sectionName);
666 }
667 if (currSection == null) currSection = new Section(sectionName);
668 continue;
669 }
670
671 int separ = line.indexOf("=");
672 if (separ < 0) {
673
674 continue;
675 }
676 if (currSection == null) {
677 throw new PogamutException("There is an entry '" + line + "' inside ini file that does not belong to any section.", this);
678 }
679 String key = line.substring(0, separ);
680 String value =
681 (separ+1 < line.length() ?
682 line.substring(separ+1, line.length())
683 : "");
684 currSection.add(key, value, currComment);
685 currComment = null;
686 }
687 } catch (IOException e) {
688 throw new PogamutIOException("Could not completely read file with defaults from stream, caused by: " + e.getMessage(), e, this);
689 } finally {
690 try {
691 reader.close();
692 } catch (IOException e) {
693 }
694 }
695
696 if (currSection != null && getSection(currSection.getName()) == null) {
697 addSection(currSection);
698 }
699 }
700
701
702
703
704
705
706 public IniFile addIniFile(IniFile iniFile) {
707 for (Section section : iniFile.sections.values()) {
708 addSection(section);
709 }
710 return this;
711 }
712
713
714
715
716
717
718
719 public Section addSection(String sectionName) {
720 return addSection(new Section(sectionName));
721 }
722
723
724
725
726
727
728
729
730
731
732
733 public Section addSection(Section section) {
734 Section oldSection = sections.get(section.getName());
735 if (oldSection != null) {
736 oldSection.add(section);
737 return oldSection;
738 } else {
739 sections.put(section.getName(), section);
740 return section;
741 }
742 }
743
744 public Section copySection(Section section) {
745 Section oldSection = sections.get(section.getName());
746 if (oldSection != null) {
747 oldSection.add(new Section(section));
748 return oldSection;
749 } else {
750 sections.put(section.getName(), section);
751 return section;
752 }
753 }
754
755 public boolean hasSection(String name) {
756 return sections.containsKey(name);
757 }
758
759 public Section getSection(String name) {
760 return sections.get(name);
761 }
762
763 public Set<String> getSectionNames() {
764 return sections.keySet();
765 }
766
767 public Collection<Section> getSections() {
768 return sections.values();
769 }
770
771 public String getOne(String section, String key) {
772 Section sec = sections.get(section);
773 if (sec == null) return null;
774 return sec.getOne(key);
775 }
776
777 public List<String> getAll(String section, String key) {
778 Section sec = sections.get(section);
779 if (sec == null) return null;
780 return sec.getAll(key);
781 }
782
783
784
785
786
787
788
789
790 public Section set(String section, String key, String value) {
791 Section sec = sections.get(section);
792 if (sec == null) {
793 sec = addSection(section);
794 }
795 sec.set(key, value);
796 return sec;
797 }
798
799
800
801
802
803
804 public IniFile set(IniFile values) {
805 return addIniFile(values);
806 }
807
808
809
810
811
812
813 public Section set(Section section) {
814 return addSection(section);
815 }
816
817
818
819
820
821
822 public static boolean isComment(String line) {
823 return line.trim().startsWith(";");
824 }
825
826
827
828
829
830
831 public static boolean hasKey(String keyValue) {
832 if (isComment(keyValue)) return false;
833 return keyValue.indexOf("=") > 0;
834 }
835
836
837
838
839
840
841 public static boolean hasCommentKey(String comment) {
842 if (!isComment(comment)) return false;
843 while (comment.startsWith(";")) comment = comment.substring(1);
844 int separ = comment.indexOf("=");
845 if (separ <= 0) return false;
846 String key = comment.substring(0, separ);
847 return !key.contains(" ");
848 }
849
850
851
852
853
854
855 public void backup(String pathToFileToBeCreated) {
856 File file = new File(pathToFileToBeCreated);
857 String fullpath = file.getAbsolutePath();
858 String separator = System.getProperty("file.separator");
859 String path = (fullpath.lastIndexOf(separator) >= 0 ? fullpath.substring(0, fullpath.lastIndexOf(separator)) : ".");
860 String filename = (fullpath.lastIndexOf(separator) >= 0 ? fullpath.substring(fullpath.lastIndexOf(separator)+1) : "file.ini");
861 String name = (filename.lastIndexOf(".") >= 0 ? filename.substring(0, filename.lastIndexOf(".")) : filename);
862 String extension = (filename.lastIndexOf(".") >= 0 ? filename.substring(filename.lastIndexOf(".") + 1) : "ini");
863 Date date = new Date(System.currentTimeMillis());
864 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
865 name += "." + sdf.format(date);
866
867 File targetFile = null;
868 int i = 0;
869
870 while (true) {
871 targetFile = new File(path + separator + name + (i > 0 ? "_" + i : "") + "." + extension);
872 if (!targetFile.exists()) break;
873 ++i;
874 }
875
876 output(targetFile);
877 }
878
879
880
881
882
883
884 public void output(String pathToFileToBeCreated) {
885 NullCheck.check(pathToFileToBeCreated, "pathToFileToBeCreated");
886 output(new File(pathToFileToBeCreated));
887 }
888
889
890
891
892
893
894
895 public void output(File file) {
896 NullCheck.check(file, "file");
897 PrintWriter writer = null;
898 try {
899 writer = new PrintWriter(new FileWriter(file));
900 output(writer);
901 } catch (IOException e) {
902 throw new PogamutIOException("Could not write ini file into '" + file.getAbsolutePath() + "', caused by: " + e.getMessage(), e, this);
903 } finally {
904 writer.close();
905 }
906 }
907
908
909
910
911
912
913 public void output(PrintWriter writer) {
914 NullCheck.check(writer, "writer");
915 boolean first = true;
916 for (Section section : sections.values()) {
917 if (first) first = false;
918 else writer.println();
919 section.output(writer);
920 }
921 }
922
923
924
925
926
927
928 public String output() {
929 StringWriter stringWriter = new StringWriter();
930 output(new PrintWriter(stringWriter));
931 return stringWriter.toString();
932 }
933
934
935
936
937
938 public static void mergeIntoIniFile(IniFile values, File mergeIntoIniFile) {
939 IniFile read = new IniFile(mergeIntoIniFile);
940 if (read == null) {
941 throw new RuntimeException("Failed to read ini file: " + mergeIntoIniFile.getAbsolutePath());
942 }
943 read.set(values);
944 read.output(mergeIntoIniFile);
945 }
946
947
948
949
950
951
952
953
954
955
956
957
958
959 public boolean isSubset(IniFile other, String thisName, String otherName, Logger log) {
960 for (Section thisSection : this.getSections()) {
961 log.info("Checking section [" + thisSection.getName() + "]");
962 Section otherSection = other.getSection(thisSection.getName());
963 if (otherSection == null) {
964 if (log != null) log.severe(thisName + " INI contains Section[" + thisSection.getName() + "] that is not present within Read INI (source)!");
965 return false;
966 }
967 for (String key : thisSection.getKeys()) {
968 List<String> thisValues = thisSection.getAll(key);
969 List<String> otherValues = new ArrayList<String>(otherSection.getAll(key));
970 if (log != null) log.info("Checking key: " + key + " (" + thisValues.size() + " values)");
971
972 if (thisValues.size() != otherValues.size()) {
973 if (log != null) log.severe(thisName + " INI, Section[" + thisSection.getName() + "], Key[" + key + "] contains #values == " + thisValues.size() + " != " + otherValues.size() + " == #values within " + otherName + " INI (source)!");
974 }
975
976 for (String testValue : thisValues) {
977 boolean present = false;
978 for (int i = 0; i < otherValues.size(); ++i) {
979 String otherValue = otherValues.get(i);
980 if (SafeEquals.equals(testValue, otherValue)) {
981 present = true;
982 otherValues.remove(i);
983 break;
984 }
985 }
986 if (present) continue;
987
988 if (log != null) log.severe(thisName + " INI, Section[" + thisSection.getName() + "], Key[" + key + "] contains Value[" + testValue + "] that is not present within " + otherName + " section/key!");
989 return false;
990 }
991 }
992 }
993 return true;
994 }
995
996
997
998
999
1000
1001
1002
1003
1004 public boolean isEqual(IniFile other, String thisName, String otherName, Logger log) {
1005 return isSubset(other, thisName, otherName, log) && other.isSubset(this, otherName, thisName, log);
1006 }
1007
1008
1009 }