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.util.ArrayList;
13 import java.util.Collection;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.HashMap;
17 import java.util.HashSet;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.TreeMap;
23 import java.util.Map.Entry;
24
25 import cz.cuni.amis.utils.exception.PogamutException;
26 import cz.cuni.amis.utils.exception.PogamutIOException;
27
28 public class IniFile {
29
30 public static abstract class SectionEntry {
31
32 private int sectionEntryIndex;
33
34 public SectionEntry(int sectionEntryIndex) {
35 this.sectionEntryIndex = sectionEntryIndex;
36 }
37
38 public int getSectionEntryIndex() {
39 return sectionEntryIndex;
40 }
41
42 protected void setSectionEntryIndex(int sectionEntryIndex) {
43 this.sectionEntryIndex = sectionEntryIndex;
44 }
45
46 public abstract String getIniFileLine();
47
48 public abstract String getKey();
49
50 }
51
52 public static class SectionEntryComment extends SectionEntry {
53
54 private String comment;
55 private String text;
56 private String key = null;
57
58 public SectionEntryComment(int sectionEntryIndex, String text, String comment) {
59 super(sectionEntryIndex);
60 NullCheck.check(text, "text");
61 if (!text.startsWith(";")) text = ";" + text;
62 this.text = text;
63
64 int separ = text.indexOf("=");
65 if (separ < 0) return;
66 key = text.substring(0, separ);
67 if (key == null || key.isEmpty()) return;
68 while (key.startsWith(";")) key = key.substring(1);
69 }
70
71 public String getComment() {
72 return comment;
73 }
74
75 public void setComment(String comment) {
76 this.comment = comment;
77 }
78
79 public String getText() {
80 return text;
81 }
82
83 public void setText(String text) {
84 this.text = text;
85 }
86
87 @Override
88 public String getIniFileLine() {
89 if (comment != null) {
90 return comment + "\n" + text;
91 }
92 return text;
93 }
94
95 @Override
96 public String getKey() {
97 return key;
98 }
99
100 }
101
102 public static class SectionEntryKeyValue extends SectionEntry {
103
104 private String comment;
105 private String key;
106 private String value;
107
108 public SectionEntryKeyValue(int sectionEntryIndex, String key, String value, String comment) {
109 super(sectionEntryIndex);
110 this.key = key;
111 this.value = value;
112 this.comment = comment;
113 if (this.comment != null) {
114 if (!this.comment.startsWith(";")) this.comment = ";" + this.comment;
115 }
116 NullCheck.check(this.key, "key");
117 NullCheck.check(this.value, "value");
118 }
119
120 @Override
121 public String getKey() {
122 return key;
123 }
124
125 public String getValue() {
126 return value;
127 }
128
129 public void setValue(String value) {
130 this.value = value;
131 }
132
133 public String getComment() {
134 return comment;
135 }
136
137 public void setComment(String comment) {
138 this.comment = comment;
139 }
140
141 @Override
142 public String getIniFileLine() {
143 if (comment != null) {
144 return comment + "\n" + key + "=" + value;
145 }
146 return key + "=" + value;
147 }
148
149 }
150
151 public static final Comparator<SectionEntry> SECTION_ENTRY_INDEX_COMPARATOR = new Comparator<SectionEntry>() {
152
153 @Override
154 public int compare(SectionEntry o1, SectionEntry o2) {
155 return o1.getSectionEntryIndex() - o2.getSectionEntryIndex();
156 }
157 };
158
159 public static final Comparator<SectionEntry> SECTION_ENTRY_KEY_COMPARATOR = new Comparator<SectionEntry>() {
160
161 @Override
162 public int compare(SectionEntry o1, SectionEntry o2) {
163 int val = o1.getKey().compareTo(o2.getKey());
164 if (val != 0) return val;
165 if (o1 instanceof SectionEntryKeyValue) {
166 if (o2 instanceof SectionEntryComment) return -1;
167 return 0;
168 } else
169 if (o1 instanceof SectionEntryComment) {
170 if (o2 instanceof SectionEntryKeyValue) return 1;
171 return 0;
172 } else {
173 return 0;
174 }
175 }
176 };
177
178
179
180
181
182 public static class Section {
183
184 private static int nextSectionIndex = 0;
185 private int nextSectionEntryIndex = 0;
186 private int sectionIndex;
187 private String name;
188
189 private List<SectionEntryComment> comments = new ArrayList<SectionEntryComment>();
190
191 private Map<String, SectionEntryKeyValue> props = new HashMap<String, SectionEntryKeyValue>();
192
193
194
195
196
197
198
199
200 public Section(String name) {
201 this.name = name;
202 NullCheck.check(this.name, "name");
203 this.sectionIndex = nextSectionIndex++;
204 }
205
206
207
208
209
210 public Section(Section section) {
211 this.name = section.getName();
212 this.sectionIndex = nextSectionIndex++;
213 this.add(section);
214 }
215
216
217
218
219
220 public String getName() {
221 return name;
222 }
223
224
225
226
227
228
229
230 public Section put(String key, String value) {
231 return put(key, value, null);
232 }
233
234
235
236
237
238
239
240
241 public Section put(String key, String value, String comment) {
242 NullCheck.check(key, "key");
243 SectionEntryKeyValue entry = props.get(key);
244 if (entry != null) {
245 entry.setValue(value);
246 } else {
247 entry = new SectionEntryKeyValue(nextSectionEntryIndex++, key, value, comment);
248 props.put(key, entry);
249 }
250 return this;
251 }
252
253
254
255
256
257
258 public String get(String key) {
259 SectionEntryKeyValue entry = props.get(key);
260 if (entry == null) return null;
261 return entry.getValue();
262 }
263
264
265
266
267
268
269 public SectionEntryKeyValue getEntry(String key) {
270 SectionEntryKeyValue entry = props.get(key);
271 if (entry == null) return null;
272 return entry;
273 }
274
275
276
277
278
279
280 public boolean containsKey(String key) {
281 return props.containsKey(key);
282 }
283
284
285
286
287
288 public Set<String> getKeys() {
289 return props.keySet();
290 }
291
292
293
294
295
296 public Set<String> keySet() {
297 return getKeys();
298 }
299
300
301
302
303
304
305
306
307
308 public SectionEntryKeyValue remove(String key) {
309 SectionEntryKeyValue entry = props.remove(key);
310 indexDeleted(entry.getSectionEntryIndex());
311 return entry;
312 }
313
314 private void indexDeleted(int sectionEntryIndex) {
315 for (SectionEntryComment comment : comments) {
316 if (comment.getSectionEntryIndex() > sectionEntryIndex) {
317 comment.setSectionEntryIndex(comment.getSectionEntryIndex() - 1);
318 }
319 }
320 for (SectionEntryKeyValue keyValue : props.values()) {
321 if (keyValue.getSectionEntryIndex() > sectionEntryIndex) {
322 keyValue.setSectionEntryIndex(keyValue.getSectionEntryIndex() - 1);
323 }
324 }
325 }
326
327
328
329
330
331 public Section clear() {
332 props.clear();
333 comments.clear();
334 return this;
335 }
336
337
338
339
340
341 public Section clearComments() {
342 comments.clear();
343 return this;
344 }
345
346
347
348
349
350
351
352 public Section set(String key, String value) {
353 return put(key, value);
354 }
355
356
357
358
359
360
361
362
363 public Section set(String key, String value, String comment) {
364 return put(key, value, comment);
365 }
366
367
368
369
370
371 public void addComment(String comment) {
372 if (!comment.startsWith(";")) comment = ";" + comment;
373 if (!isComment(comment)) throw new RuntimeException("'" + comment + "' is not a comment!");
374 comments.add(new SectionEntryComment(nextSectionEntryIndex++, comment, null));
375 }
376
377 public void addComment(String keyValueCommented, String comment) {
378 if (!keyValueCommented.startsWith(";")) keyValueCommented = ";" + keyValueCommented;
379 if (comment == null) {
380 addComment(keyValueCommented);
381 return;
382 }
383 if (!comment.startsWith(";")) comment = ";" + comment;
384 if (!isComment(keyValueCommented)) throw new RuntimeException("'" + keyValueCommented + "' is not a comment!");
385 if (!hasCommentKey(keyValueCommented)) throw new RuntimeException("'" + keyValueCommented + "' is not commented out key=value!");
386 comments.add(new SectionEntryComment(nextSectionEntryIndex++, keyValueCommented, comment));
387 }
388
389
390
391
392
393
394
395 public Section add(Section section) {
396 for (SectionEntryKeyValue keyValue : props.values()) {
397 put(keyValue.getKey(), keyValue.getValue());
398 }
399 for (SectionEntryComment comment : section.comments) {
400 addComment(comment.getText());
401 }
402 return this;
403 }
404
405
406
407
408
409 public void output(PrintWriter writer) {
410 List<SectionEntry> output = new ArrayList<SectionEntry>(props.values());
411
412 List<SectionEntryComment> commentsWithKeys = new ArrayList<SectionEntryComment>(comments);
413 List<SectionEntryComment> commentsWithoutKeys = new ArrayList<SectionEntryComment>();
414 Iterator<SectionEntryComment> iter = commentsWithKeys.iterator();
415 while (iter.hasNext()) {
416 SectionEntryComment comment = iter.next();
417 if (comment.getKey() == null || comment.getKey().length() <= 0) {
418 commentsWithoutKeys.add(comment);
419 iter.remove();
420 } else {
421 output.add(comment);
422 }
423 }
424
425 Collections.sort(output, SECTION_ENTRY_KEY_COMPARATOR);
426
427 mergeInComments(output, commentsWithoutKeys);
428
429 writer.print("[");
430 writer.print(name);
431 writer.println("]");
432 for (SectionEntry entry : output) {
433 writer.println(entry.getIniFileLine());
434 }
435 }
436
437 private void mergeInComments(List<SectionEntry> output, List<SectionEntryComment> commentsWithKeys) {
438 if (output.size() == 0) {
439 output.addAll(commentsWithKeys);
440 Collections.sort(output, SECTION_ENTRY_INDEX_COMPARATOR);
441 return;
442 }
443 while(commentsWithKeys.size() > 0) {
444 Iterator<SectionEntryComment> iter = commentsWithKeys.iterator();
445 while (iter.hasNext()) {
446 SectionEntryComment comment = iter.next();
447 if (mergeInComment(output, comment)) {
448 iter.remove();
449 }
450 }
451 }
452 }
453
454 private boolean mergeInComment(List<SectionEntry> output, SectionEntryComment comment) {
455 if (output.size() == 0) {
456 output.add(comment);
457 return true;
458 }
459 for (int i = 0; i < output.size(); ++i) {
460 SectionEntry entry = output.get(i);
461 if (entry.getSectionEntryIndex()-1 == comment.getSectionEntryIndex()) {
462 output.add(i, comment);
463 return true;
464 }
465 }
466 if (comment.getSectionEntryIndex()-1 == output.get(output.size()-1).getSectionEntryIndex()) {
467 output.add(comment);
468 return true;
469 }
470 return false;
471 }
472
473 @Override
474 public String toString() {
475 return "IniFile.Section[name=" + name + ", entries=" + props.size() + "]";
476 }
477
478 }
479
480 private Map<String, Section> sections = new TreeMap<String, Section>(new Comparator<String>() {
481 @Override
482 public int compare(String o1, String o2) {
483 if (o1 == null) return -1;
484 if (o2 == null) return 1;
485 return o1.toLowerCase().compareTo(o2.toLowerCase());
486 }
487 });
488
489
490
491
492 public IniFile() {
493 }
494
495
496
497
498
499
500 public IniFile(File source) {
501 if (!source.exists()) {
502 throw new PogamutException("File with defaults does not exist at: " + source.getAbsolutePath() + ".", this);
503 }
504 load(source);
505 }
506
507 public IniFile(IniFile ini) {
508 for (Section section : ini.getSections()) {
509 addSection(new Section(section));
510 }
511 }
512
513
514
515
516
517
518
519
520
521 public void load(File source) {
522 try {
523 load(new FileInputStream(source));
524 } catch (Exception e) {
525 throw new PogamutException("Could not load defaults for GameBots2004.ini from file: " + source.getAbsolutePath() + ", caused by: " + e.getMessage(), e);
526 }
527 }
528
529
530
531
532
533
534
535
536
537 public void load(InputStream source) {
538 BufferedReader reader = null;
539 reader = new BufferedReader(new InputStreamReader(source));
540
541 Section currSection = null;
542 String currComment = null;
543
544 try {
545 while (reader.ready()) {
546 String line = reader.readLine().trim();
547 if (line.length() == 0) continue;
548 if (isComment(line)) {
549 if (currSection == null) {
550 if (currComment == null) currComment = line.trim();
551 else currComment += "\n" + line;
552 } else {
553 if (hasCommentKey(line)) {
554 currSection.addComment(line, currComment);
555 } else {
556 if (currComment == null) currComment = line.trim();
557 else currComment += "\n" + line;
558 }
559 }
560 continue;
561 }
562 if (line.startsWith("[") && line.endsWith("]")) {
563 if (currComment != null && currSection != null) {
564 currSection.addComment(currComment);
565 }
566 if (currSection != null && getSection(currSection.getName()) == null) {
567 addSection(currSection);
568 }
569 String sectionName = line.substring(1, line.length()-1);
570 currSection = getSection(sectionName);
571 if (currSection == null) currSection = new Section(sectionName);
572 } else {
573 int separ = line.indexOf("=");
574 if (separ < 0) {
575
576 continue;
577 }
578 if (currSection == null) {
579 throw new PogamutException("There is an entry '" + line + "' inside ini file that does not belong to any section.", this);
580 }
581 String key = line.substring(0, separ);
582 String value =
583 (separ+1 < line.length() ?
584 line.substring(separ+1, line.length())
585 : "");
586 currSection.put(key, value, currComment);
587 currComment = null;
588 }
589 }
590 } catch (IOException e) {
591 throw new PogamutIOException("Could not completely read file with defaults from stream, caused by: " + e.getMessage(), e, this);
592 } finally {
593 try {
594 reader.close();
595 } catch (IOException e) {
596 }
597 }
598
599 if (currSection != null && getSection(currSection.getName()) == null) {
600 addSection(currSection);
601 }
602 }
603
604
605
606
607
608
609 public IniFile addIniFile(IniFile iniFile) {
610 for (Section section : iniFile.sections.values()) {
611 addSection(section);
612 }
613 return this;
614 }
615
616
617
618
619
620
621
622 public Section addSection(String sectionName) {
623 return addSection(new Section(sectionName));
624 }
625
626
627
628
629
630
631
632
633
634
635
636 public Section addSection(Section section) {
637 Section oldSection = sections.get(section.getName());
638 if (oldSection != null) {
639 oldSection.add(section);
640 return oldSection;
641 } else {
642 sections.put(section.getName(), section);
643 return section;
644 }
645 }
646
647 public Section copySection(Section section) {
648 Section oldSection = sections.get(section.getName());
649 if (oldSection != null) {
650 oldSection.add(new Section(section));
651 return oldSection;
652 } else {
653 sections.put(section.getName(), section);
654 return section;
655 }
656 }
657
658 public boolean hasSection(String name) {
659 return sections.containsKey(name);
660 }
661
662 public Section getSection(String name) {
663 return sections.get(name);
664 }
665
666 public Set<String> getSectionNames() {
667 return sections.keySet();
668 }
669
670 public Collection<Section> getSections() {
671 return sections.values();
672 }
673
674 public String get(String section, String key) {
675 Section sec = sections.get(section);
676 if (sec == null) return null;
677 return sec.get(key);
678 }
679
680
681
682
683
684
685
686
687 public Section set(String section, String key, String value) {
688 Section sec = sections.get(section);
689 if (sec == null) {
690 sec = addSection(section);
691 }
692 sec.set(key, value);
693 return sec;
694 }
695
696
697
698
699
700
701 public IniFile set(IniFile values) {
702 return addIniFile(values);
703 }
704
705
706
707
708
709
710 public Section set(Section section) {
711 return addSection(section);
712 }
713
714
715
716
717
718
719 public static boolean isComment(String line) {
720 return line.trim().startsWith(";");
721 }
722
723
724
725
726
727
728 public static boolean hasKey(String keyValue) {
729 if (isComment(keyValue)) return false;
730 return keyValue.indexOf("=") > 0;
731 }
732
733
734
735
736
737
738 public static boolean hasCommentKey(String comment) {
739 if (!isComment(comment)) return false;
740 while (comment.startsWith(";")) comment = comment.substring(1);
741 int separ = comment.indexOf("=");
742 if (separ <= 0) return false;
743 String key = comment.substring(0, separ);
744 return !key.contains(" ");
745 }
746
747
748
749
750
751
752 public void output(String pathToFileToBeCreated) {
753 NullCheck.check(pathToFileToBeCreated, "pathToFileToBeCreated");
754 output(new File(pathToFileToBeCreated));
755 }
756
757
758
759
760
761
762
763 public void output(File file) {
764 NullCheck.check(file, "file");
765 PrintWriter writer = null;
766 try {
767 writer = new PrintWriter(new FileWriter(file));
768 output(writer);
769 } catch (IOException e) {
770 throw new PogamutIOException("Could not write ini file into '" + file.getAbsolutePath() + "', caused by: " + e.getMessage(), e, this);
771 } finally {
772 writer.close();
773 }
774 }
775
776
777
778
779
780
781 public void output(PrintWriter writer) {
782 NullCheck.check(writer, "writer");
783 boolean first = true;
784 for (Section section : sections.values()) {
785 if (first) first = false;
786 else writer.println();
787 section.output(writer);
788 }
789 }
790
791
792
793
794
795
796 public String output() {
797 StringWriter stringWriter = new StringWriter();
798 output(new PrintWriter(stringWriter));
799 return stringWriter.toString();
800 }
801
802
803
804
805
806 public static void mergeIntoIniFile(IniFile values, File mergeIntoIniFile) {
807 IniFile read = new IniFile(mergeIntoIniFile);
808 if (read == null) {
809 throw new RuntimeException("Failed to read ini file: " + mergeIntoIniFile.getAbsolutePath());
810 }
811 read.set(values);
812 read.output(mergeIntoIniFile);
813 }
814
815
816
817 }