1
19 package org.crosswire.common.util;
20
21 import java.io.BufferedReader;
22 import java.io.ByteArrayInputStream;
23 import java.io.File;
24 import java.io.FileInputStream;
25 import java.io.FileOutputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.io.InputStreamReader;
29 import java.io.OutputStreamWriter;
30 import java.io.PrintWriter;
31 import java.io.Reader;
32 import java.io.Writer;
33 import java.util.ArrayList;
34 import java.util.Collection;
35 import java.util.Collections;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40
41
83 public final class IniSection implements Iterable {
84
85
88 public IniSection() {
89 this((String) null);
90 }
91
95 public IniSection(String name) {
96 this.name = name;
97 section = new HashMap<String, List<String>>();
98 warnings = new StringBuilder();
99 }
100
101
106 public IniSection(IniSection config) {
107 this.name = config.getName();
108 section = new HashMap<String, List<String>>();
109 for (String key : config.getKeys()) {
110 for (String value : config.getValues(key)) {
111 add(key, value);
112 }
113 }
114 }
115
118 public void clear() {
119 section.clear();
120 warnings.setLength(0);
121 warnings.trimToSize();
122 report = "";
123 }
124
125
130 public void setName(String name) {
131 this.name = name;
132 }
133
134
139 public String getName() {
140 return name;
141 }
142
143
148 public int size() {
149 return section.size();
150 }
151
152
158 public int size(String key) {
159 Collection<String> values = section.get(key);
160 return values == null ? 0 : values.size();
161 }
162
163
168 public boolean isEmpty() {
169 return section.isEmpty();
170 }
171
172 public Iterator iterator() {
173 return section.keySet().iterator();
174 }
175
180 public Collection<String> getKeys() {
181 return Collections.unmodifiableSet(section.keySet());
182 }
183
184
190 public boolean containsKey(String key) {
191 return section.containsKey(key);
192 }
193
194
200 public boolean containsValue(String value) {
201 for (Collection<String> collection : section.values()) {
202 if (collection.contains(value)) {
203 return true;
204 }
205 }
206 return false;
207 }
208
209
216 public boolean containsValue(String key, String value) {
217 Collection<String> values = section.get(key);
218 return values != null && values.contains(value);
219 }
220
221
228 public boolean add(String key, String value) {
229 if (!allowed(key, value)) {
230 return false;
231 }
232
233 Collection<String> values = getOrCreateValues(key);
234 if (values.contains(value)) {
235 warnings.append("Duplicate value: ").append(key).append(" = ").append(value).append('\n');
236 return true;
237 }
238 return values.add(value);
239 }
240
241
250 public Collection<String> getValues(String key) {
251 if (section.containsKey(key)) {
252 return Collections.unmodifiableCollection(section.get(key));
253 }
254 return null;
255 }
256
257
265 public String get(String key, int index) {
266 List<String> values = section.get(key);
267 return values == null ? null : values.get(index);
268 }
269
270
276 public String get(String key) {
277 List<String> values = section.get(key);
278 return values == null ? null : values.get(0);
279 }
280
281 public String get(String key, String defaultValue) {
282 List<String> values = section.get(key);
283 return values == null ? defaultValue : values.get(0);
284 }
285
286
294 public boolean remove(String key, String value) {
295 Collection<String> values = section.get(key);
296 if (values == null) {
297 return false;
298 }
299
300 boolean changed = values.remove(value);
301 if (changed) {
302 if (values.isEmpty()) {
303 section.remove(key);
304 }
305 }
306
307 return changed;
308 }
309
310
316 public boolean remove(String key) {
317 Collection<String> values = section.get(key);
318 if (values == null) {
319 return false;
320 }
321 section.remove(key);
322 return true;
323 }
324
325
332 public boolean replace(String key, String value) {
333 if (!allowed(key, value)) {
334 return false;
335 }
336
337 Collection<String> values = getOrCreateValues(key);
338 values.clear();
339 return values.add(value);
340 }
341
342
349 public void load(InputStream is, String encoding) throws IOException {
350 load(is, encoding, null);
351 }
352
353
361 public void load(InputStream is, String encoding, Filter<String> filter) throws IOException {
362 Reader in = null;
363 try {
364 in = new InputStreamReader(is, encoding);
365 doLoad(in, filter);
366 } finally {
367 if (in != null) {
368 in.close();
369 in = null;
370 }
371 }
372 }
373
374
381 public void load(File file, String encoding) throws IOException {
382 load(file, encoding, null);
383 }
384
385
393 public void load(File file, String encoding, Filter<String> filter) throws IOException {
394 this.configFile = file;
395 this.charset = encoding;
396 InputStream in = null;
397 try {
398 in = new FileInputStream(file);
399 load(in, encoding, filter);
400 } finally {
401 if (in != null) {
402 in.close();
403 in = null;
404 }
405 }
406 }
407
408
416 public void load(byte[] buffer, String encoding) throws IOException {
417 load(buffer, encoding, null);
418 }
419
420
429 public void load(byte[] buffer, String encoding, Filter<String> filter) throws IOException {
430 InputStream in = null;
431 try {
432 in = new ByteArrayInputStream(buffer);
433 load(in, encoding, filter);
434 } finally {
435 if (in != null) {
436 in.close();
437 in = null;
438 }
439 }
440 }
441
442
446 public void save() throws IOException {
447 assert configFile != null;
448 assert charset != null;
449 if (configFile != null && charset != null) {
450 save(configFile, charset);
451 }
452 }
453
454
461 public void save(File file, String encoding) throws IOException {
462 this.configFile = file;
463 this.charset = encoding;
464 Writer out = null;
465 try {
466 out = new OutputStreamWriter(new FileOutputStream(file), encoding);
467 save(out);
468 } finally {
469 if (out != null) {
470 out.close();
471 out = null;
472 }
473 }
474 }
475
476
482 public void save(Writer out) {
483 PrintWriter writer = null;
484 if (out instanceof PrintWriter) {
485 writer = (PrintWriter) out;
486 } else {
487 writer = new PrintWriter(out);
488 }
489
490 writer.print("[");
491 writer.print(name);
492 writer.print("]");
493 writer.println();
494
495 boolean first = true;
496 Iterator<String> keys = section.keySet().iterator();
497 while (keys.hasNext()) {
498 String key = keys.next();
499 Collection<String> values = section.get(key);
500 Iterator<String> iter = values.iterator();
501 String value;
502 while (iter.hasNext()) {
503 if (!first) {
504 writer.println();
505 first = false;
506 }
507 value = iter.next();
508 writer.print(key);
509 writer.print(" = ");
510 writer.print(format(value));
511 writer.println();
512 }
513 }
514
515 writer.flush();
516 }
517
518
523 public String report() {
524 String str = report;
525 report = "";
526 return str;
527 }
528
529
534 private String format(final String value) {
535 return value.replaceAll("\n", " \\\\\n\t");
539 }
540
541 private Collection<String> getOrCreateValues(final String key) {
542 List<String> values = section.get(key);
543 if (values == null) {
544 values = new ArrayList<String>();
545 section.put(key, values);
546 }
547 return values;
548 }
549
550 private void doLoad(Reader in, Filter<String> filter) throws IOException {
551 BufferedReader bin = null;
552 try {
553 if (in instanceof BufferedReader) {
554 bin = (BufferedReader) in;
555 } else {
556 bin = new BufferedReader(in, MAX_BUFF_SIZE);
561 }
562
563 while (true) {
564 String line = advance(bin);
565 if (line == null) {
566 break;
567 }
568
569 if (isSectionLine(line)) {
570 name = line.substring(1, line.length() - 1);
573 continue;
574 }
575
576 int splitPos = getSplitPos(line);
578 if (splitPos < 0) {
579 warnings.append("Skipping: Expected to see '=' in: ").append(line).append('\n');
580 continue;
581 }
582
583 String key = line.substring(0, splitPos).trim();
584 String value = more(bin, line.substring(splitPos + 1).trim());
585 if (filter == null || filter.test(key)) {
586 add(key, value);
587 }
588 }
589 report = warnings.toString();
590 warnings.setLength(0);
591 warnings.trimToSize();
592 } finally {
593 if (bin != null) {
594 bin.close();
595 bin = null;
596 }
597 }
598 }
599
600
607 private String advance(BufferedReader bin) throws IOException {
608 String trimmed = null;
610 for (String line = bin.readLine(); line != null; line = bin.readLine()) {
611 trimmed = line.trim();
613
614 if (!isCommentLine(trimmed)) {
616 return trimmed;
617 }
618 }
619 return null;
620 }
621
622
629 private boolean isCommentLine(final String line) {
630 if (line == null) {
631 return false;
632 }
633 if (line.length() == 0) {
634 return true;
635 }
636 char firstChar = line.charAt(0);
637 return firstChar == ';' || firstChar == '#';
638 }
639
640
646 private boolean isSectionLine(final String line) {
647 return line.charAt(0) == '[' && line.charAt(line.length() - 1) == ']';
648 }
649
650
656 private int getSplitPos(final String line) {
657 return line.indexOf('=');
658 }
659
660
663 private String more(BufferedReader bin, String value) throws IOException {
664 boolean moreCowBell = false;
665 String line = value;
666 StringBuilder buf = new StringBuilder();
667
668 do {
669 moreCowBell = more(line);
670 if (moreCowBell) {
671 line = line.substring(0, line.length() - 1).trim();
672 }
673 buf.append(line);
674 if (moreCowBell) {
675 buf.append('\n');
676 line = advance(bin);
677 int splitPos = getSplitPos(line);
681 if (splitPos >= 0) {
682 warnings.append("Possible trailing continuation on previous line. Found: ").append(line).append('\n');
683 }
684 }
685 } while (moreCowBell && line != null);
686 String cowBell = buf.toString();
687 buf = null;
688 line = null;
689 return cowBell;
690 }
691
692
698 private static boolean more(final String line) {
699 int length = line.length();
700 return length > 0 && line.charAt(length - 1) == '\\';
701 }
702
703 private boolean allowed(String key, String value) {
704 if (key == null || key.length() == 0 || value == null) {
705 if (key == null) {
706 warnings.append("Null keys not allowed: ").append(" = ").append(value).append('\n');
707 } else if (key.length() == 0) {
708 warnings.append("Empty keys not allowed: ").append(" = ").append(value).append('\n');
709 }
710 if (value == null) {
711 warnings.append("Null values are not allowed: ").append(key).append(" = ").append('\n');
712 }
713 return false;
714 }
715 return true;
716 }
717
718
721 private String name;
722
723
726 private Map<String, List<String>> section;
727
728 private File configFile;
729
730 private String charset;
731
732 private StringBuilder warnings;
733
734 private String report;
735
736
739 private static final int MAX_BUFF_SIZE = 2 * 1024;
740 }
741