1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.shiro.config;
20
21 import org.apache.shiro.io.ResourceUtils;
22 import org.apache.shiro.util.CollectionUtils;
23 import org.apache.shiro.util.StringUtils;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 import java.io.*;
28 import java.util.*;
29
30
31
32
33
34
35
36
37
38
39 public class Ini implements Map<String, Ini.Section> {
40
41 private static transient final Logger log = LoggerFactory.getLogger(Ini.class);
42
43 public static final String DEFAULT_SECTION_NAME = "";
44 public static final String DEFAULT_CHARSET_NAME = "UTF-8";
45
46 public static final String COMMENT_POUND = "#";
47 public static final String COMMENT_SEMICOLON = ";";
48 public static final String SECTION_PREFIX = "[";
49 public static final String SECTION_SUFFIX = "]";
50
51 protected static final char ESCAPE_TOKEN = '\\';
52
53 private final Map<String, Section> sections;
54
55
56
57
58 public Ini() {
59 this.sections = new LinkedHashMap<String, Section>();
60 }
61
62
63
64
65
66
67 public Ini(Ini defaults) {
68 this();
69 if (defaults == null) {
70 throw new NullPointerException("Defaults cannot be null.");
71 }
72 for (Section section : defaults.getSections()) {
73 Section copy = new Section(section);
74 this.sections.put(section.getName(), copy);
75 }
76 }
77
78
79
80
81
82
83
84
85 public boolean isEmpty() {
86 Collection<Section> sections = this.sections.values();
87 if (!sections.isEmpty()) {
88 for (Section section : sections) {
89 if (!section.isEmpty()) {
90 return false;
91 }
92 }
93 }
94 return true;
95 }
96
97
98
99
100
101
102
103
104 public Set<String> getSectionNames() {
105 return Collections.unmodifiableSet(sections.keySet());
106 }
107
108
109
110
111
112
113
114
115 public Collection<Section> getSections() {
116 return Collections.unmodifiableCollection(sections.values());
117 }
118
119
120
121
122
123
124
125 public Section getSection(String sectionName) {
126 String name = cleanName(sectionName);
127 return sections.get(name);
128 }
129
130
131
132
133
134
135
136 public Section addSection(String sectionName) {
137 String name = cleanName(sectionName);
138 Section section = getSection(name);
139 if (section == null) {
140 section = new Section(name);
141 this.sections.put(name, section);
142 }
143 return section;
144 }
145
146
147
148
149
150
151
152 public Section removeSection(String sectionName) {
153 String name = cleanName(sectionName);
154 return this.sections.remove(name);
155 }
156
157 private static String cleanName(String sectionName) {
158 String name = StringUtils.clean(sectionName);
159 if (name == null) {
160 log.trace("Specified name was null or empty. Defaulting to the default section (name = \"\")");
161 name = DEFAULT_SECTION_NAME;
162 }
163 return name;
164 }
165
166
167
168
169
170
171
172
173
174
175 public void setSectionProperty(String sectionName, String propertyName, String propertyValue) {
176 String name = cleanName(sectionName);
177 Section section = getSection(name);
178 if (section == null) {
179 section = addSection(name);
180 }
181 section.put(propertyName, propertyValue);
182 }
183
184
185
186
187
188
189
190
191 public String getSectionProperty(String sectionName, String propertyName) {
192 Section section = getSection(sectionName);
193 return section != null ? section.get(propertyName) : null;
194 }
195
196
197
198
199
200
201
202
203
204
205
206 public String getSectionProperty(String sectionName, String propertyName, String defaultValue) {
207 String value = getSectionProperty(sectionName, propertyName);
208 return value != null ? value : defaultValue;
209 }
210
211
212
213
214
215
216
217
218
219
220 public static Ini fromResourcePath(String resourcePath) throws ConfigurationException {
221 if (!StringUtils.hasLength(resourcePath)) {
222 throw new IllegalArgumentException("Resource Path argument cannot be null or empty.");
223 }
224 Ini ini = new Ini();
225 ini.loadFromPath(resourcePath);
226 return ini;
227 }
228
229
230
231
232
233
234
235
236
237 public void loadFromPath(String resourcePath) throws ConfigurationException {
238 InputStream is;
239 try {
240 is = ResourceUtils.getInputStreamForPath(resourcePath);
241 } catch (IOException e) {
242 throw new ConfigurationException(e);
243 }
244 load(is);
245 }
246
247
248
249
250
251
252
253 public void load(String iniConfig) throws ConfigurationException {
254 load(new Scanner(iniConfig));
255 }
256
257
258
259
260
261
262
263
264
265 public void load(InputStream is) throws ConfigurationException {
266 if (is == null) {
267 throw new NullPointerException("InputStream argument cannot be null.");
268 }
269 InputStreamReader isr;
270 try {
271 isr = new InputStreamReader(is, DEFAULT_CHARSET_NAME);
272 } catch (UnsupportedEncodingException e) {
273 throw new ConfigurationException(e);
274 }
275 load(isr);
276 }
277
278
279
280
281
282
283
284 public void load(Reader reader) {
285 Scanner scanner = new Scanner(reader);
286 try {
287 load(scanner);
288 } finally {
289 try {
290 scanner.close();
291 } catch (Exception e) {
292 log.debug("Unable to cleanly close the InputStream scanner. Non-critical - ignoring.", e);
293 }
294 }
295 }
296
297 private void addSection(String name, StringBuilder content) {
298 if (content.length() > 0) {
299 String contentString = content.toString();
300 String cleaned = StringUtils.clean(contentString);
301 if (cleaned != null) {
302 Section section = new Section(name, contentString);
303 if (!section.isEmpty()) {
304 sections.put(name, section);
305 }
306 }
307 }
308 }
309
310
311
312
313
314
315
316 public void load(Scanner scanner) {
317
318 String sectionName = DEFAULT_SECTION_NAME;
319 StringBuilder sectionContent = new StringBuilder();
320
321 while (scanner.hasNextLine()) {
322
323 String rawLine = scanner.nextLine();
324 String line = StringUtils.clean(rawLine);
325
326 if (line == null || line.startsWith(COMMENT_POUND) || line.startsWith(COMMENT_SEMICOLON)) {
327
328 continue;
329 }
330
331 String newSectionName = getSectionName(line);
332 if (newSectionName != null) {
333
334 addSection(sectionName, sectionContent);
335
336
337 sectionContent = new StringBuilder();
338
339 sectionName = newSectionName;
340
341 if (log.isDebugEnabled()) {
342 log.debug("Parsing " + SECTION_PREFIX + sectionName + SECTION_SUFFIX);
343 }
344 } else {
345
346 sectionContent.append(rawLine).append("\n");
347 }
348 }
349
350
351 addSection(sectionName, sectionContent);
352 }
353
354 protected static boolean isSectionHeader(String line) {
355 String s = StringUtils.clean(line);
356 return s != null && s.startsWith(SECTION_PREFIX) && s.endsWith(SECTION_SUFFIX);
357 }
358
359 protected static String getSectionName(String line) {
360 String s = StringUtils.clean(line);
361 if (isSectionHeader(s)) {
362 return cleanName(s.substring(1, s.length() - 1));
363 }
364 return null;
365 }
366
367 public boolean equals(Object obj) {
368 if (obj instanceof Ini) {
369 Ini ini = (Ini) obj;
370 return this.sections.equals(ini.sections);
371 }
372 return false;
373 }
374
375 @Override
376 public int hashCode() {
377 return this.sections.hashCode();
378 }
379
380 public String toString() {
381 if (CollectionUtils.isEmpty(this.sections)) {
382 return "<empty INI>";
383 } else {
384 StringBuilder sb = new StringBuilder("sections=");
385 int i = 0;
386 for (Ini.Section section : this.sections.values()) {
387 if (i > 0) {
388 sb.append(",");
389 }
390 sb.append(section.toString());
391 i++;
392 }
393 return sb.toString();
394 }
395 }
396
397 public int size() {
398 return this.sections.size();
399 }
400
401 public boolean containsKey(Object key) {
402 return this.sections.containsKey(key);
403 }
404
405 public boolean containsValue(Object value) {
406 return this.sections.containsValue(value);
407 }
408
409 public Section get(Object key) {
410 return this.sections.get(key);
411 }
412
413 public Section put(String key, Section value) {
414 return this.sections.put(key, value);
415 }
416
417 public Section remove(Object key) {
418 return this.sections.remove(key);
419 }
420
421 public void putAll(Map<? extends String, ? extends Section> m) {
422 this.sections.putAll(m);
423 }
424
425 public void clear() {
426 this.sections.clear();
427 }
428
429 public Set<String> keySet() {
430 return Collections.unmodifiableSet(this.sections.keySet());
431 }
432
433 public Collection<Section> values() {
434 return Collections.unmodifiableCollection(this.sections.values());
435 }
436
437 public Set<Entry<String, Section>> entrySet() {
438 return Collections.unmodifiableSet(this.sections.entrySet());
439 }
440
441
442
443
444
445 public static class Section implements Map<String, String> {
446 private final String name;
447 private final Map<String, String> props;
448
449 private Section(String name) {
450 if (name == null) {
451 throw new NullPointerException("name");
452 }
453 this.name = name;
454 this.props = new LinkedHashMap<String, String>();
455 }
456
457 private Section(String name, String sectionContent) {
458 if (name == null) {
459 throw new NullPointerException("name");
460 }
461 this.name = name;
462 Map<String,String> props;
463 if (StringUtils.hasText(sectionContent) ) {
464 props = toMapProps(sectionContent);
465 } else {
466 props = new LinkedHashMap<String,String>();
467 }
468 if ( props != null ) {
469 this.props = props;
470 } else {
471 this.props = new LinkedHashMap<String,String>();
472 }
473 }
474
475 private Section(Section defaults) {
476 this(defaults.getName());
477 putAll(defaults.props);
478 }
479
480
481
482 protected static boolean isContinued(String line) {
483 if (!StringUtils.hasText(line)) {
484 return false;
485 }
486 int length = line.length();
487
488
489 int backslashCount = 0;
490 for (int i = length - 1; i > 0; i--) {
491 if (line.charAt(i) == ESCAPE_TOKEN) {
492 backslashCount++;
493 } else {
494 break;
495 }
496 }
497 return backslashCount % 2 != 0;
498 }
499
500 private static boolean isKeyValueSeparatorChar(char c) {
501 return Character.isWhitespace(c) || c == ':' || c == '=';
502 }
503
504 private static boolean isCharEscaped(CharSequence s, int index) {
505 return index > 0 && s.charAt(index - 1) == ESCAPE_TOKEN;
506 }
507
508
509 protected static String[] splitKeyValue(String keyValueLine) {
510 String line = StringUtils.clean(keyValueLine);
511 if (line == null) {
512 return null;
513 }
514 StringBuilder keyBuffer = new StringBuilder();
515 StringBuilder valueBuffer = new StringBuilder();
516
517 boolean buildingKey = true;
518
519 for (int i = 0; i < line.length(); i++) {
520 char c = line.charAt(i);
521
522 if (buildingKey) {
523 if (isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
524 buildingKey = false;
525 } else {
526 keyBuffer.append(c);
527 }
528 } else {
529 if (valueBuffer.length() == 0 && isKeyValueSeparatorChar(c) && !isCharEscaped(line, i)) {
530
531 } else {
532 valueBuffer.append(c);
533 }
534 }
535 }
536
537 String key = StringUtils.clean(keyBuffer.toString());
538 String value = StringUtils.clean(valueBuffer.toString());
539
540 if (key == null || value == null) {
541 String msg = "Line argument must contain a key and a value. Only one string token was found.";
542 throw new IllegalArgumentException(msg);
543 }
544
545 log.trace("Discovered key/value pair: {}={}", key, value);
546
547 return new String[]{key, value};
548 }
549
550 private static Map<String, String> toMapProps(String content) {
551 Map<String, String> props = new LinkedHashMap<String, String>();
552 String line;
553 StringBuilder lineBuffer = new StringBuilder();
554 Scanner scanner = new Scanner(content);
555 while (scanner.hasNextLine()) {
556 line = StringUtils.clean(scanner.nextLine());
557 if (isContinued(line)) {
558
559 line = line.substring(0, line.length() - 1);
560 lineBuffer.append(line);
561 continue;
562 } else {
563 lineBuffer.append(line);
564 }
565 line = lineBuffer.toString();
566 lineBuffer = new StringBuilder();
567 String[] kvPair = splitKeyValue(line);
568 props.put(kvPair[0], kvPair[1]);
569 }
570
571 return props;
572 }
573
574 public String getName() {
575 return this.name;
576 }
577
578 public void clear() {
579 this.props.clear();
580 }
581
582 public boolean containsKey(Object key) {
583 return this.props.containsKey(key);
584 }
585
586 public boolean containsValue(Object value) {
587 return this.props.containsValue(value);
588 }
589
590 public Set<Entry<String, String>> entrySet() {
591 return this.props.entrySet();
592 }
593
594 public String get(Object key) {
595 return this.props.get(key);
596 }
597
598 public boolean isEmpty() {
599 return this.props.isEmpty();
600 }
601
602 public Set<String> keySet() {
603 return this.props.keySet();
604 }
605
606 public String put(String key, String value) {
607 return this.props.put(key, value);
608 }
609
610 public void putAll(Map<? extends String, ? extends String> m) {
611 this.props.putAll(m);
612 }
613
614 public String remove(Object key) {
615 return this.props.remove(key);
616 }
617
618 public int size() {
619 return this.props.size();
620 }
621
622 public Collection<String> values() {
623 return this.props.values();
624 }
625
626 public String toString() {
627 String name = getName();
628 if (DEFAULT_SECTION_NAME.equals(name)) {
629 return "<default>";
630 }
631 return name;
632 }
633
634 @Override
635 public boolean equals(Object obj) {
636 if (obj instanceof Section) {
637 Section other = (Section) obj;
638 return getName().equals(other.getName()) && this.props.equals(other.props);
639 }
640 return false;
641 }
642
643 @Override
644 public int hashCode() {
645 return this.name.hashCode() * 31 + this.props.hashCode();
646 }
647 }
648
649 }