1 package com.randomnoun.common;
2
3
4
5
6 import java.io.FileReader;
7 import java.io.IOException;
8 import java.io.InputStream;
9 import java.io.InputStreamReader;
10 import java.io.LineNumberReader;
11 import java.io.OutputStream;
12 import java.io.PrintStream;
13 import java.io.PrintWriter;
14 import java.io.Reader;
15 import java.io.Writer;
16 import java.nio.charset.Charset;
17 import java.text.ParseException;
18 import java.util.ArrayList;
19 import java.util.Collection;
20 import java.util.Enumeration;
21 import java.util.InvalidPropertiesFormatException;
22 import java.util.Iterator;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Properties;
27 import java.util.Set;
28 import java.util.StringTokenizer;
29 import java.util.function.BiConsumer;
30 import java.util.function.BiFunction;
31 import java.util.function.Function;
32
33 import org.apache.log4j.Logger;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155 public class PropertyParser {
156
157
158 public static final Logger logger = Logger.getLogger(PropertyParser.class.getName());
159
160
161 static private final boolean verbose = false;
162
163
164 private LineNumberReader lineReader;
165
166
167 private StringTokenizer thisline;
168
169
170 private boolean inEnvironment = false;
171
172
173 private boolean correctEnvironment = true;
174
175
176 private String comment = null;
177
178
179 private String environmentID = "";
180
181
182 private Properties properties = null;
183
184 private Properties propertyComments = null;
185
186 public static class PropertiesWithComments extends Properties {
187
188 private static final long serialVersionUID = -780503047200669250L;
189 Properties p;
190 Properties c;
191
192 public PropertiesWithComments(Properties p, Properties c) {
193 this.p = p;
194 this.c = c;
195 }
196
197 public String getComment(String key) {
198 return c.getProperty(key);
199 }
200
201 public Properties getComments() {
202 return c;
203 }
204
205
206 public Object setProperty(String key, String value) {
207 return p.setProperty(key, value);
208 }
209
210 public void load(Reader reader) throws IOException {
211 p.load(reader);
212 }
213
214 public void load(InputStream inStream) throws IOException {
215 p.load(inStream);
216 }
217
218
219 public void save(OutputStream out, String comments) {
220 p.save(out, comments);
221 }
222
223 public void store(Writer writer, String comments) throws IOException {
224 p.store(writer, comments);
225 }
226
227 public void store(OutputStream out, String comments) throws IOException {
228 p.store(out, comments);
229 }
230
231 public void loadFromXML(InputStream in) throws IOException, InvalidPropertiesFormatException {
232 p.loadFromXML(in);
233 }
234
235 public void storeToXML(OutputStream os, String comment) throws IOException {
236 p.storeToXML(os, comment);
237 }
238
239 public void storeToXML(OutputStream os, String comment, String encoding) throws IOException {
240 p.storeToXML(os, comment, encoding);
241 }
242
243 public void storeToXML(OutputStream os, String comment, Charset charset) throws IOException {
244 p.storeToXML(os, comment, charset);
245 }
246
247 public String getProperty(String key) {
248 return p.getProperty(key);
249 }
250
251 public String getProperty(String key, String defaultValue) {
252 return p.getProperty(key, defaultValue);
253 }
254
255 public Enumeration<?> propertyNames() {
256 return p.propertyNames();
257 }
258
259 public Set<String> stringPropertyNames() {
260 return p.stringPropertyNames();
261 }
262
263 public void list(PrintStream out) {
264 p.list(out);
265 }
266
267 public void list(PrintWriter out) {
268 p.list(out);
269 }
270
271 public int size() {
272 return p.size();
273 }
274
275 public boolean isEmpty() {
276 return p.isEmpty();
277 }
278
279 public Enumeration<Object> keys() {
280 return p.keys();
281 }
282
283 public Enumeration<Object> elements() {
284 return p.elements();
285 }
286
287 public boolean contains(Object value) {
288 return p.contains(value);
289 }
290
291 public boolean containsValue(Object value) {
292 return p.containsValue(value);
293 }
294
295 public boolean containsKey(Object key) {
296 return p.containsKey(key);
297 }
298
299 public Object get(Object key) {
300 return p.get(key);
301 }
302
303 public Object put(Object key, Object value) {
304 return p.put(key, value);
305 }
306
307 public Object remove(Object key) {
308 return p.remove(key);
309 }
310
311 public void putAll(Map<?, ?> t) {
312 p.putAll(t);
313 }
314
315 public void clear() {
316 p.clear();
317 }
318
319 public String toString() {
320 return p.toString();
321 }
322
323 public Set<Object> keySet() {
324 return p.keySet();
325 }
326
327 public Collection<Object> values() {
328 return p.values();
329 }
330
331 public Set<java.util.Map.Entry<Object, Object>> entrySet() {
332 return p.entrySet();
333 }
334
335 public boolean equals(Object o) {
336 return p.equals(o);
337 }
338
339 public int hashCode() {
340 return p.hashCode();
341 }
342
343 public Object getOrDefault(Object key, Object defaultValue) {
344 return p.getOrDefault(key, defaultValue);
345 }
346
347 public void forEach(BiConsumer<? super Object, ? super Object> action) {
348 p.forEach(action);
349 }
350
351 public void replaceAll(BiFunction<? super Object, ? super Object, ?> function) {
352 p.replaceAll(function);
353 }
354
355 public Object putIfAbsent(Object key, Object value) {
356 return p.putIfAbsent(key, value);
357 }
358
359 public boolean remove(Object key, Object value) {
360 return p.remove(key, value);
361 }
362
363 public boolean replace(Object key, Object oldValue, Object newValue) {
364 return p.replace(key, oldValue, newValue);
365 }
366
367 public Object replace(Object key, Object value) {
368 return p.replace(key, value);
369 }
370
371 public Object computeIfAbsent(Object key, Function<? super Object, ?> mappingFunction) {
372 return p.computeIfAbsent(key, mappingFunction);
373 }
374
375 public Object computeIfPresent(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {
376 return p.computeIfPresent(key, remappingFunction);
377 }
378
379 public Object compute(Object key, BiFunction<? super Object, ? super Object, ?> remappingFunction) {
380 return p.compute(key, remappingFunction);
381 }
382
383 public Object merge(Object key, Object value, BiFunction<? super Object, ? super Object, ?> remappingFunction) {
384 return p.merge(key, value, remappingFunction);
385 }
386
387 public Object clone() {
388 return p.clone();
389 }
390
391 }
392
393
394
395
396
397
398
399
400 public PropertyParser(Reader reader, String environmentID) {
401 lineReader = new LineNumberReader(reader);
402 this.environmentID = environmentID;
403 }
404
405
406
407
408
409
410
411 public PropertyParser(Reader reader) {
412 this(reader, System.getProperty("com.randomnoun.common.mode", "localhost-dev-unknown"));
413 }
414
415
416
417
418
419
420
421
422
423 public PropertiesWithComments parse()
424 throws ParseException, IOException {
425 String line = "";
426 String token = "";
427
428
429 properties = new Properties();
430 propertyComments = new Properties();
431 PropertiesWithComments pwc = new PropertiesWithComments(properties, propertyComments);
432
433
434 line = lineReader.readLine();
435
436 while (line != null) {
437 line = line.trim();
438
439
440
441 while (line != null && line.endsWith("\\")) {
442 line = line.substring(0, line.length() - 1);
443
444 try {
445 line = line + lineReader.readLine().trim();
446 } catch (NullPointerException npe) {
447
448 }
449 }
450
451
452 thisline = new StringTokenizer(line, " =\t\n\r", true);
453
454 if (thisline.hasMoreTokens()) {
455 token = parseToken("keyword");
456
457
458 if (correctEnvironment || token.toLowerCase().equals("endenvironment")) {
459 parseLine(token);
460 }
461 }
462
463 line = lineReader.readLine();
464 }
465
466 return pwc;
467 }
468
469
470
471
472
473
474
475
476 @SuppressWarnings("unchecked")
477 private void parseLine(String token)
478 throws ParseException {
479 String lowerCaseToken;
480 String nextToken = null;
481 String value = null;
482
483
484
485 lowerCaseToken = token.toLowerCase();
486
487 if (token.startsWith("##")) {
488 comment = (comment == null ? "" : comment + "\n" ) + parseTokenToEOL("property value");
489 } else if (token.startsWith("#")) {
490 comment = (comment == null ? null : comment + "\n" + parseTokenToEOL("property value"));
491 } else if (lowerCaseToken.equals("startenvironment")) {
492 parseStartEnvironment();
493 } else if (lowerCaseToken.equals("endenvironment")) {
494 parseEndEnvironment();
495 } else if (lowerCaseToken.equals("env")) {
496 parseEnv();
497 } else if (lowerCaseToken.equals("includeresource")) {
498 parseIncludeResource();
499 } else {
500
501 try {
502 nextToken = parseToken("token");
503 } catch (ParseException pe) {
504 newParseException("Unknown keyword '" + token + "', '=' expected");
505 }
506
507 if (nextToken.equals("=")) {
508
509 value = null;
510
511 try {
512 value = parseTokenToEOL("property value");
513 } catch (ParseException pe) {
514 }
515
516 if (value == null) {
517 value = "";
518 }
519
520
521 int lb = token.indexOf('[');
522 int rb = token.indexOf(']');
523
524
525 if (lb != -1 || rb != -1) {
526 if (lb == -1 || rb == -1) {
527 newParseException("Invalid list property key '" + token + "'");
528 }
529 List<Object> list = null;
530 String keyPart = token.substring(0, lb);
531 String listPart = token.substring(lb);
532 try {
533 list = (List<Object>) properties.get(keyPart);
534 } catch (ClassCastException cce) {
535 newParseException("Cannot create list '" + token + "', this property already exists");
536 }
537 if (list == null) {
538 list = new ArrayList<Object>();
539 properties.put(keyPart, list);
540 }
541
542 String index = token.substring(lb+1, rb);
543 if (index.equals("*")) {
544 index = String.valueOf(list.size());
545 listPart = "[" + index + "]" + listPart.substring(listPart.indexOf("]")+1);
546 } else if (index.equals("")) {
547 index = String.valueOf(list.size()-1);
548 listPart = "[" + index + "]" + listPart.substring(listPart.indexOf("]")+1);
549 }
550
551 Struct.setValue(list, listPart, value, true, true, true);
552
553 comment = null;
554 } else {
555
556 properties.setProperty(token, value);
557 if (comment != null) {
558 propertyComments.setProperty(token, comment);
559 comment = null;
560 }
561 }
562 } else {
563 newParseException("Unknown token '" + nextToken + "', '=' expected");
564 }
565 }
566 }
567
568
569
570
571
572
573
574
575
576 private void newParseException(String s)
577 throws ParseException {
578 throw new ParseException("line " + lineReader.getLineNumber() + ": " + s, 0);
579 }
580
581
582
583
584
585
586
587
588
589
590
591 private String parseToken(String what)
592 throws ParseException {
593 String result = null;
594
595
596 while (result == null || result.equals(" ") || result.equals("\r") || result.equals("\n") || result.equals("\t")) {
597 if (!thisline.hasMoreTokens()) {
598 newParseException("Expecting " + what);
599 }
600
601 result = thisline.nextToken(" =\t\n\r");
602 }
603
604
605 result = result.replaceAll("\\\\n", "\n");
606
607 if (verbose) {
608 logger.debug("parsed token: '" + result + "'");
609 }
610
611 return result;
612 }
613
614
615
616
617
618
619
620
621
622
623
624
625
626 private String parseTokenToEOL(String what)
627 throws ParseException {
628 String token;
629
630 if (!thisline.hasMoreTokens()) {
631 newParseException("Expecting " + what);
632 }
633
634 token = thisline.nextToken("\n").trim();
635 token = token.replaceAll("\\\\n", "\n");
636
637 if (verbose) {
638 System.out.println("parsed token: " + token);
639 }
640
641 return token;
642 }
643
644
645
646
647
648
649 private void parseEnv()
650 throws ParseException {
651 String selectedenvironmentID;
652 String token;
653
654 selectedenvironmentID = parseToken("Environment ID");
655
656
657 if (selectedenvironmentID.equals(environmentID)) {
658 token = parseToken("keyword").toLowerCase();
659 parseLine(token);
660 }
661 }
662
663 private void parseIncludeResource() throws ParseException {
664
665
666
667
668
669
670 String resourceName = parseTokenToEOL("resource name");
671 logger.debug("Including property resource '" + resourceName + "'");
672
673
674 InputStream inputStream = PropertyParser.class.getClassLoader().getResourceAsStream(resourceName);
675 if (inputStream==null) {
676 throw new ParseException("Could not find included resource '" + resourceName + "'", 0);
677 }
678 PropertyParser propertyParser = new PropertyParser(new InputStreamReader(inputStream), environmentID);
679 Properties includedProperties = new Properties();
680 try {
681 includedProperties = propertyParser.parse();
682 } catch (Exception e) {
683 throw (ParseException) new ParseException("Could not load included resource '" +
684 resourceName + "'", 0).initCause(e);
685 }
686
687
688
689
690 for (Iterator<Entry<Object, Object>> i = includedProperties.entrySet().iterator(); i.hasNext(); ) {
691 Map.Entry<Object, Object> entry = i.next();
692 if (entry.getValue() instanceof List) {
693 Object existingObj = properties.get(entry.getKey());
694 if (existingObj==null || !(existingObj instanceof List)) {
695 properties.put(entry.getKey(), entry.getValue());
696 } else {
697 @SuppressWarnings("unchecked")
698 List<Object> existingList = (List<Object>) existingObj;
699 @SuppressWarnings("unchecked")
700 List<Object> listValue = (List<Object>) entry.getValue();
701 for (int j=0; j<listValue.size(); j++) {
702 if (listValue.get(j)!=null) {
703 Struct.setListElement(existingList, j, listValue.get(j));
704 }
705 }
706 }
707 } else {
708 properties.put(entry.getKey(), entry.getValue());
709 }
710 }
711
712 }
713
714
715
716
717 private void parseStartEnvironment()
718 throws ParseException {
719 if (inEnvironment) {
720 newParseException("attempted to nest environment areas");
721 }
722 String propertyName = "environmentId";
723 String propertyMatch = null;
724 String propertyValue = null;
725 String envSpec = parseTokenToEOL("Environment specification");
726 if (envSpec.indexOf("=~")!=-1) {
727 propertyName = envSpec.substring(0, envSpec.indexOf("=~")).trim();
728 propertyMatch = envSpec.substring(envSpec.indexOf("=~")+2).trim();
729 propertyMatch = propertyMatch.replaceAll("\\*", ".*");
730 propertyValue = propertyName.equals("environmentId") ? environmentID : properties.getProperty(propertyName);
731 if (propertyValue==null) { propertyValue = ""; }
732 correctEnvironment = propertyValue.matches(propertyMatch);
733 } else if (envSpec.indexOf("=")!=-1) {
734 propertyName = envSpec.substring(0, envSpec.indexOf("=")).trim();
735 propertyMatch = envSpec.substring(envSpec.indexOf("=")+1).trim();
736 propertyValue = propertyName.equals("environmentId") ? environmentID : properties.getProperty(propertyName);
737 if (propertyValue==null) { propertyValue = ""; }
738 correctEnvironment = propertyValue.equals(propertyMatch);
739 } else {
740
741 propertyMatch = envSpec.replaceAll("\\*", ".*");
742 correctEnvironment = environmentID.toLowerCase().matches(propertyMatch.toLowerCase());
743 }
744
745
746
747
748 inEnvironment = true;
749 }
750
751
752
753
754
755
756 private void parseEndEnvironment()
757 throws ParseException {
758 if (!inEnvironment) {
759 newParseException("'endenvironment' without matching 'startenvironment'");
760 }
761
762 inEnvironment = false;
763 correctEnvironment = true;
764 }
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809 public static Map<? extends Object, ? extends Object> restrict(Map<Object, Object> properties, String prefix, boolean removePrefix) {
810 if (properties == null) {
811 return null;
812 }
813
814 if (prefix == null) {
815 return properties;
816 }
817
818 Properties result = new Properties();
819
820
821
822 prefix = prefix + ".";
823
824 Map.Entry<Object, Object> entry;
825
826 for (Iterator<Entry<Object, Object>> i = properties.entrySet().iterator(); i.hasNext();) {
827 entry = i.next();
828
829 String key = (String) entry.getKey();
830
831 if (key.startsWith(prefix)) {
832 if (removePrefix) {
833 key = key.substring(prefix.length());
834 }
835
836 result.put(key, entry.getValue());
837 }
838 }
839
840 return result;
841 }
842
843
844
845
846
847
848
849
850
851
852
853 public static void main(String[] args)
854 throws IOException, ParseException {
855 String filename = "test.properties";
856 PropertyParser propertyParser;
857
858 if (args.length != 1) {
859 System.out.println("Reading from '" + filename + "' by default...");
860 } else {
861 filename = args[0];
862 }
863
864 try {
865 propertyParser = new PropertyParser(new FileReader(filename));
866 propertyParser.parse();
867 System.out.println("Parse OK");
868 } catch (ParseException pe) {
869 System.out.println("Caught ParseException: " + pe);
870 pe.printStackTrace();
871 }
872 }
873 }