View Javadoc

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  
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  			// HAS KEY?
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 	 * Class representing one section of the ini file.
180 	 * @author Jimmy
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 		 * Creates a section of the given name.
195 		 * <p><p>
196 		 * Name can't be null!
197 		 * 
198 		 * @param name
199 		 */
200 		public Section(String name) {
201 			this.name = name;			
202 			NullCheck.check(this.name, "name");
203 			this.sectionIndex = nextSectionIndex++;
204 		}
205 
206 		/**
207 		 * Copy-constructor.
208 		 * @param section
209 		 */
210 		public Section(Section section) {
211 			this.name = section.getName();
212 			this.sectionIndex = nextSectionIndex++;
213 			this.add(section);
214 		}
215 
216 		/**
217 		 * Returns name of the section.
218 		 * @return
219 		 */
220 		public String getName() {
221 			return name;
222 		}
223 		
224 		/**
225 		 * Sets a property key=value into the section.
226 		 * @param key
227 		 * @param value
228 		 * @return this
229 		 */
230 		public Section put(String key, String value) {
231 			return put(key, value, null);
232 		}
233 		
234 		/**
235 		 * Sets a property key=value into the section with comment.
236 		 * @param key
237 		 * @param value
238 		 * @param comment
239 		 * @return this
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 		 * Returns a value of the propety with 'key'.
255 		 * @param key
256 		 * @return
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 		 * Returns full section entry for a 'key'.
266 		 * @param key
267 		 * @return
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 		 * Whether the section contains property of the given key.
277 		 * @param key
278 		 * @return
279 		 */
280 		public boolean containsKey(String key) {
281 			return props.containsKey(key);
282 		}
283 		
284 		/**
285 		 * Returns all keys stored within the map.
286 		 * @return
287 		 */
288 		public Set<String> getKeys() {
289 			return props.keySet();
290 		}
291 		
292 		/**
293 		 * Alias for {@link Section#getKeys()}.
294 		 * @return
295 		 */
296 		public Set<String> keySet() {
297 			return getKeys();
298 		}
299 		
300 		/**
301 		 * Removes a property under the 'key' from this section.
302 		 * 
303 		 * Time complexity O(n) due to section-entry-index consolidation.
304 		 * 
305 		 * @param key
306 		 * @return removed {@link SectionEntryKeyValue}
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 		 * Deletes all properties within this section.
329 		 * @return
330 		 */
331 		public Section clear() {
332 			props.clear();
333 			comments.clear();
334 			return this;
335 		}
336 		
337 		/**
338 		 * Deletes all comments within this section.
339 		 * @return
340 		 */
341 		public Section clearComments() {
342 			comments.clear();
343 			return this;
344 		}
345 
346 		/**
347 		 * Alias for {@link Section#put(String, String)}.
348 		 * @param key
349 		 * @param value
350 		 * @return this
351 		 */
352 		public Section set(String key, String value) {		
353 			return put(key, value);
354 		}
355 		
356 		/**
357 		 * Alias for {@link Section#put(String, Strin, String)}.
358 		 * @param key
359 		 * @param value
360 		 * @param comment
361 		 * @return this
362 		 */
363 		public Section set(String key, String value, String comment) {		
364 			return put(key, value, comment);
365 		}
366 		
367 		/**
368 		 * Adds comment to this section.
369 		 * @param comment
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 		 * Adds all properties from 'section' into this one.
391 		 * @param section
392 		 * @return 
393 		 * @return this
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 		 * Writes this section into the writer.
407 		 * @param writer
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 	 * Constructs Ini file with no defaults.
491 	 */
492 	public IniFile() {
493 	}
494 	
495 	/**
496 	 * Constructs GameBots2004Ini with defaults taken 'source' (file must exists!).
497 	 * 
498 	 * @param source
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 	 * Loads {@link IniFile#source} into {@link IniFile#sections}.
515 	 * <p><p>
516 	 * Note that his method won't clear anything, it will just load all sections/properties from the given file possibly overwriting existing properties
517 	 * in existing sections.
518 	 * 
519 	 * @param source 
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 	 * Loads {@link IniFile#source} into {@link IniFile#sections}.
531 	 * <p><p>
532 	 * Note that his method won't clear anything, it will just load all sections/properties from the given file possibly overwriting existing properties
533 	 * in existing sections.
534 	 * 
535 	 * @param source 
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 						// TODO: [Jimmy] throw an exception? at least log somewhere?
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 	 * Add all sections from one ini file into this one.
606 	 * @param iniFile
607 	 * @return this
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 	 * Adds a new section into this class (won't overwrite existing one).
618 	 * 
619 	 * @param sectionName
620 	 * @return section that is stored in this class
621 	 */
622 	public Section addSection(String sectionName) {
623 		return addSection(new Section(sectionName));
624 	}
625 	
626 	/**
627 	 * Adds section into this ini file. If section of the same name exists, it won't be replaced. Instead all properties
628 	 * from 'section' will be put there.
629 	 * <p><p>
630 	 * If 'section' is a new section (i.e., its {@link Section#getName()} is not already present in stored sections), this instance
631 	 * will be stored here (does not hard-copy the section!). For hard-copy variant, use {@link IniFile#copySection(Section)}.
632 	 * 
633 	 * @param section
634 	 * @return section that is stored in this class
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 	 * Sets property key=value into section 'section'
682 	 * @param section
683 	 * @param key
684 	 * @param value
685 	 * @return section instance that the property was set into
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 	 * Set 'values' into this IniFile, alias for {@link IniFile#addIniFile(IniFile)}.
698 	 * @param values
699 	 * @return this
700 	 */
701 	public IniFile set(IniFile values) {
702 		return addIniFile(values);
703 	}
704 
705 	/**
706 	 * Set key=values from 'section' into this IniFile. Alias for {@link IniFile#addSection(Section)}.
707 	 * @param section
708 	 * @return section that is stored in this class
709 	 */
710 	public Section set(Section section) {
711 		return addSection(section);		
712 	}
713 
714 	/**
715 	 * Whether this line is comment. (Does not check for end lines).
716 	 * @param text
717 	 * @return
718 	 */
719 	public static boolean isComment(String line) {
720 		return line.trim().startsWith(";");
721 	}
722 	
723 	/**
724 	 * Whether this non-comment line is a key=value. (Does not check for end lines).
725 	 * @param keyValue
726 	 * @return
727 	 */
728 	public static  boolean hasKey(String keyValue) {
729 		if (isComment(keyValue)) return false;
730 		return keyValue.indexOf("=") > 0;
731 	}
732 	
733 	/**
734 	 * Whether this comment is commented key=value pair. (Does not check for end lines).
735 	 * @param comment
736 	 * @return
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 	 * Similar to {@link #output(String)} but this ensure not to overwrite any file + it appends current "_date_time" to filename.
749 	 * 
750 	 * @param file
751 	 */
752 	public void backup(String pathToFileToBeCreated) {
753 		File file = new File(pathToFileToBeCreated);
754 		String fullpath = file.getAbsolutePath();
755 		String separator = System.getProperty("file.separator");
756 		String path = (fullpath.lastIndexOf(separator) >= 0 ? fullpath.substring(0, fullpath.lastIndexOf(separator)) : ".");
757 		String filename = (fullpath.lastIndexOf(separator) >= 0 ? fullpath.substring(fullpath.lastIndexOf(separator)+1) : "file.ini");
758 		String name = (filename.lastIndexOf(".") >= 0 ? filename.substring(0, filename.lastIndexOf(".")) : filename);
759 		String extension = (filename.lastIndexOf(".") >= 0 ? filename.substring(filename.lastIndexOf(".") + 1) : "ini");
760 		Date date = new Date(System.currentTimeMillis());
761 		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss");
762 		name += "." + sdf.format(date);
763 		
764 		File targetFile = null;
765 		int i = 0;
766 		
767 		while (true) {
768 			targetFile = new File(path + separator + name + (i > 0 ? "_" + i : "") + "." + extension);
769 			if (!targetFile.exists()) break; 
770 			++i;
771 		}
772 		
773 		output(targetFile);		
774 	}
775 	
776 	/**
777 	 * Outputs GameBots2004.ini stored by this class into 'file'. If 'file' exists, it overwrites it.
778 	 * 
779 	 * @param file
780 	 */
781 	public void output(String pathToFileToBeCreated) {
782 		NullCheck.check(pathToFileToBeCreated, "pathToFileToBeCreated");
783 		output(new File(pathToFileToBeCreated));
784 	}
785 	
786 	
787 	/**
788 	 * Outputs GameBots2004.ini stored by this class into 'file'. If 'file' exists, it overwrites it.
789 	 * 
790 	 * @param file
791 	 */
792 	public void output(File file) {
793 		NullCheck.check(file, "file");
794 		PrintWriter writer = null;
795 		try {
796 			writer = new PrintWriter(new FileWriter(file));
797 			output(writer);
798 		} catch (IOException e) {
799 			throw new PogamutIOException("Could not write ini file into '" + file.getAbsolutePath() + "', caused by: " + e.getMessage(), e, this);
800 		} finally {
801 			writer.close();
802 		}
803 	}
804 	
805 	/**
806 	 * Outputs contents of this {@link IniFile} into the 'writer'.
807 	 * 
808 	 * @param writer
809 	 */
810 	public void output(PrintWriter writer) {
811 		NullCheck.check(writer, "writer");
812 		boolean first = true;
813 		for (Section section : sections.values()) {
814 			if (first) first = false;
815 			else writer.println();
816 			section.output(writer);
817 		}
818 	}
819 	
820 	/**
821 	 * Returns contents of this {@link IniFile} as string.
822 	 * 
823 	 * @return
824 	 */
825 	public String output() {
826 		StringWriter stringWriter = new StringWriter();
827 		output(new PrintWriter(stringWriter));
828 		return stringWriter.toString();
829 	}
830 	
831 	//
832 	// MERGE INTO
833 	//
834 	
835 	public static void mergeIntoIniFile(IniFile values, File mergeIntoIniFile) {
836 		IniFile read = new IniFile(mergeIntoIniFile);
837 		if (read == null) {
838 			throw new RuntimeException("Failed to read ini file: " + mergeIntoIniFile.getAbsolutePath());
839 		}
840 		read.set(values);
841 		read.output(mergeIntoIniFile);
842 	}
843 
844 
845 }