1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.eclipse.jgit.lib;
20
21 import static java.nio.charset.StandardCharsets.UTF_8;
22
23 import java.io.File;
24 import java.nio.file.InvalidPathException;
25 import java.nio.file.Path;
26 import java.text.MessageFormat;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Set;
32 import java.util.concurrent.TimeUnit;
33 import java.util.concurrent.atomic.AtomicReference;
34
35 import org.eclipse.jgit.annotations.NonNull;
36 import org.eclipse.jgit.errors.ConfigInvalidException;
37 import org.eclipse.jgit.events.ConfigChangedEvent;
38 import org.eclipse.jgit.events.ConfigChangedListener;
39 import org.eclipse.jgit.events.ListenerHandle;
40 import org.eclipse.jgit.events.ListenerList;
41 import org.eclipse.jgit.internal.JGitText;
42 import org.eclipse.jgit.transport.RefSpec;
43 import org.eclipse.jgit.util.FS;
44 import org.eclipse.jgit.util.RawParseUtils;
45
46
47
48
49 public class Config {
50
51 private static final String[] EMPTY_STRING_ARRAY = {};
52
53 static final long KiB = 1024;
54 static final long MiB = 1024 * KiB;
55 static final long GiB = 1024 * MiB;
56 private static final int MAX_DEPTH = 10;
57
58 private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
59
60 private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
61
62
63 private final ListenerListhtml#ListenerList">ListenerList listeners = new ListenerList();
64
65
66
67
68
69
70
71 private final AtomicReference<ConfigSnapshot> state;
72
73 private final Config baseConfig;
74
75
76
77
78
79
80
81
82 private static final String MISSING_ENTRY = new String();
83
84
85
86
87 public Config() {
88 this(null);
89 }
90
91
92
93
94
95
96
97
98 public Configig" href="../../../../org/eclipse/jgit/lib/Config.html#Config">Config(Config defaultConfig) {
99 baseConfig = defaultConfig;
100 state = new AtomicReference<>(newState());
101 }
102
103
104
105
106
107
108
109
110 public Config getBaseConfig() {
111 return baseConfig;
112 }
113
114
115
116
117
118
119
120
121
122 @SuppressWarnings({ "ReferenceEquality", "StringEquality" })
123 public static boolean isMissing(String value) {
124 return value == MISSING_ENTRY;
125 }
126
127
128
129
130
131
132
133
134
135 public static void setTypedConfigGetter(TypedConfigGetter getter) {
136 typedGetter = getter == null ? DEFAULT_GETTER : getter;
137 }
138
139
140
141
142
143
144
145
146 static String escapeValue(String x) {
147 if (x.isEmpty()) {
148 return "";
149 }
150
151 boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
152 StringBuilder r = new StringBuilder(x.length());
153 for (int k = 0; k < x.length(); k++) {
154 char c = x.charAt(k);
155
156
157
158
159
160
161
162
163
164
165
166
167 switch (c) {
168 case '\0':
169
170
171
172 throw new IllegalArgumentException(
173 JGitText.get().configValueContainsNullByte);
174
175 case '\n':
176 r.append('\\').append('n');
177 break;
178
179 case '\t':
180 r.append('\\').append('t');
181 break;
182
183 case '\b':
184
185
186
187 r.append('\\').append('b');
188 break;
189
190 case '\\':
191 r.append('\\').append('\\');
192 break;
193
194 case '"':
195 r.append('\\').append('"');
196 break;
197
198 case '#':
199 case ';':
200 needQuote = true;
201 r.append(c);
202 break;
203
204 default:
205 r.append(c);
206 break;
207 }
208 }
209
210 return needQuote ? '"' + r.toString() + '"' : r.toString();
211 }
212
213 static String escapeSubsection(String x) {
214 if (x.isEmpty()) {
215 return "\"\"";
216 }
217
218 StringBuilder r = new StringBuilder(x.length() + 2).append('"');
219 for (int k = 0; k < x.length(); k++) {
220 char c = x.charAt(k);
221
222
223
224 switch (c) {
225 case '\0':
226 throw new IllegalArgumentException(
227 JGitText.get().configSubsectionContainsNullByte);
228
229 case '\n':
230 throw new IllegalArgumentException(
231 JGitText.get().configSubsectionContainsNewline);
232
233 case '\\':
234 case '"':
235 r.append('\\').append(c);
236 break;
237
238 default:
239 r.append(c);
240 break;
241 }
242 }
243
244 return r.append('"').toString();
245 }
246
247
248
249
250
251
252
253
254
255
256
257
258 public int getInt(final String section, final String name,
259 final int defaultValue) {
260 return typedGetter.getInt(this, section, null, name, defaultValue);
261 }
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276 public int getInt(final String section, String subsection,
277 final String name, final int defaultValue) {
278 return typedGetter.getInt(this, section, subsection, name,
279 defaultValue);
280 }
281
282
283
284
285
286
287
288
289
290
291
292
293 public long getLong(String section, String name, long defaultValue) {
294 return typedGetter.getLong(this, section, null, name, defaultValue);
295 }
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310 public long getLong(final String section, String subsection,
311 final String name, final long defaultValue) {
312 return typedGetter.getLong(this, section, subsection, name,
313 defaultValue);
314 }
315
316
317
318
319
320
321
322
323
324
325
326
327
328 public boolean getBoolean(final String section, final String name,
329 final boolean defaultValue) {
330 return typedGetter.getBoolean(this, section, null, name, defaultValue);
331 }
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347 public boolean getBoolean(final String section, String subsection,
348 final String name, final boolean defaultValue) {
349 return typedGetter.getBoolean(this, section, subsection, name,
350 defaultValue);
351 }
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366 public <T extends Enum<?>> T getEnum(final String section,
367 final String subsection, final String name, final T defaultValue) {
368 final T[] all = allValuesOf(defaultValue);
369 return typedGetter.getEnum(this, all, section, subsection, name,
370 defaultValue);
371 }
372
373 @SuppressWarnings("unchecked")
374 private static <T> T[] allValuesOf(T value) {
375 try {
376 return (T[]) value.getClass().getMethod("values").invoke(null);
377 } catch (Exception err) {
378 String typeName = value.getClass().getName();
379 String msg = MessageFormat.format(
380 JGitText.get().enumValuesNotAvailable, typeName);
381 throw new IllegalArgumentException(msg, err);
382 }
383 }
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401 public <T extends Enum<?>> T getEnum(final T[] all, final String section,
402 final String subsection, final String name, final T defaultValue) {
403 return typedGetter.getEnum(this, all, section, subsection, name,
404 defaultValue);
405 }
406
407
408
409
410
411
412
413
414
415
416
417
418 public String getString(final String section, String subsection,
419 final String name) {
420 return getRawString(section, subsection, name);
421 }
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437 public String[] getStringList(final String section, String subsection,
438 final String name) {
439 String[] base;
440 if (baseConfig != null)
441 base = baseConfig.getStringList(section, subsection, name);
442 else
443 base = EMPTY_STRING_ARRAY;
444
445 String[] self = getRawStringList(section, subsection, name);
446 if (self == null)
447 return base;
448 if (base.length == 0)
449 return self;
450 String[] res = new String[base.length + self.length];
451 int n = base.length;
452 System.arraycopy(base, 0, res, 0, n);
453 System.arraycopy(self, 0, res, n, self.length);
454 return res;
455 }
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476 public long getTimeUnit(String section, String subsection, String name,
477 long defaultValue, TimeUnit wantUnit) {
478 return typedGetter.getTimeUnit(this, section, subsection, name,
479 defaultValue, wantUnit);
480 }
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507 public Path getPath(String section, String subsection, String name,
508 @NonNull FS fs, File resolveAgainst, Path defaultValue) {
509 return typedGetter.getPath(this, section, subsection, name, fs,
510 resolveAgainst, defaultValue);
511 }
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527 public List<RefSpec> getRefSpecs(String section, String subsection,
528 String name) {
529 return typedGetter.getRefSpecs(this, section, subsection, name);
530 }
531
532
533
534
535
536
537
538
539
540
541
542
543
544 public Set<String> getSubsections(String section) {
545 return getState().getSubsections(section);
546 }
547
548
549
550
551
552
553
554
555
556 public Set<String> getSections() {
557 return getState().getSections();
558 }
559
560
561
562
563
564
565
566
567 public Set<String> getNames(String section) {
568 return getNames(section, null);
569 }
570
571
572
573
574
575
576
577
578
579
580 public Set<String> getNames(String section, String subsection) {
581 return getState().getNames(section, subsection);
582 }
583
584
585
586
587
588
589
590
591
592
593
594
595 public Set<String> getNames(String section, boolean recursive) {
596 return getState().getNames(section, null, recursive);
597 }
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612 public Set<String> getNames(String section, String subsection,
613 boolean recursive) {
614 return getState().getNames(section, subsection, recursive);
615 }
616
617
618
619
620
621
622
623
624
625
626
627
628
629 @SuppressWarnings("unchecked")
630 public <T> T get(SectionParser<T> parser) {
631 final ConfigSnapshot myState = getState();
632 T obj = (T) myState.cache.get(parser);
633 if (obj == null) {
634 obj = parser.parse(this);
635 myState.cache.put(parser, obj);
636 }
637 return obj;
638 }
639
640
641
642
643
644
645
646
647
648
649
650 public void uncache(SectionParser<?> parser) {
651 state.get().cache.remove(parser);
652 }
653
654
655
656
657
658
659
660
661
662
663
664
665 public ListenerHandle addChangeListener(ConfigChangedListener listener) {
666 return listeners.addConfigChangedListener(listener);
667 }
668
669
670
671
672
673
674
675
676
677
678
679
680
681 protected boolean notifyUponTransientChanges() {
682 return true;
683 }
684
685
686
687
688 protected void fireConfigChangedEvent() {
689 listeners.dispatch(new ConfigChangedEvent());
690 }
691
692 String getRawString(final String section, final String subsection,
693 final String name) {
694 String[] lst = getRawStringList(section, subsection, name);
695 if (lst != null) {
696 return lst[lst.length - 1];
697 } else if (baseConfig != null) {
698 return baseConfig.getRawString(section, subsection, name);
699 } else {
700 return null;
701 }
702 }
703
704 private String[] getRawStringList(String section, String subsection,
705 String name) {
706 return state.get().get(section, subsection, name);
707 }
708
709 private ConfigSnapshot getState() {
710 ConfigSnapshot cur, upd;
711 do {
712 cur = state.get();
713 final ConfigSnapshot base = getBaseState();
714 if (cur.baseState == base)
715 return cur;
716 upd = new ConfigSnapshot(cur.entryList, base);
717 } while (!state.compareAndSet(cur, upd));
718 return upd;
719 }
720
721 private ConfigSnapshot getBaseState() {
722 return baseConfig != null ? baseConfig.getState() : null;
723 }
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743 public void setInt(final String section, final String subsection,
744 final String name, final int value) {
745 setLong(section, subsection, name, value);
746 }
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766 public void setLong(final String section, final String subsection,
767 final String name, final long value) {
768 final String s;
769
770 if (value >= GiB && (value % GiB) == 0)
771 s = String.valueOf(value / GiB) + "g";
772 else if (value >= MiB && (value % MiB) == 0)
773 s = String.valueOf(value / MiB) + "m";
774 else if (value >= KiB && (value % KiB) == 0)
775 s = String.valueOf(value / KiB) + "k";
776 else
777 s = String.valueOf(value);
778
779 setString(section, subsection, name, s);
780 }
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800 public void setBoolean(final String section, final String subsection,
801 final String name, final boolean value) {
802 setString(section, subsection, name, value ? "true" : "false");
803 }
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823 public <T extends Enum<?>> void setEnum(final String section,
824 final String subsection, final String name, final T value) {
825 String n;
826 if (value instanceof ConfigEnum)
827 n = ((ConfigEnum) value).toConfigValue();
828 else
829 n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
830 setString(section, subsection, name, n);
831 }
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851 public void setString(final String section, final String subsection,
852 final String name, final String value) {
853 setStringList(section, subsection, name, Collections
854 .singletonList(value));
855 }
856
857
858
859
860
861
862
863
864
865
866
867 public void unset(final String section, final String subsection,
868 final String name) {
869 setStringList(section, subsection, name, Collections
870 .<String> emptyList());
871 }
872
873
874
875
876
877
878
879
880
881 public void unsetSection(String section, String subsection) {
882 ConfigSnapshot src, res;
883 do {
884 src = state.get();
885 res = unsetSection(src, section, subsection);
886 } while (!state.compareAndSet(src, res));
887 }
888
889 private ConfigSnapshotonfigSnapshot">ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
890 final String section,
891 final String subsection) {
892 final int max = srcState.entryList.size();
893 final ArrayList<ConfigLine> r = new ArrayList<>(max);
894
895 boolean lastWasMatch = false;
896 for (ConfigLine e : srcState.entryList) {
897 if (e.includedFrom == null && e.match(section, subsection)) {
898
899 lastWasMatch = true;
900 continue;
901 }
902
903 if (lastWasMatch && e.section == null && e.subsection == null)
904 continue;
905 r.add(e);
906 }
907
908 return newState(r);
909 }
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929 public void setStringList(final String section, final String subsection,
930 final String name, final List<String> values) {
931 ConfigSnapshot src, res;
932 do {
933 src = state.get();
934 res = replaceStringList(src, section, subsection, name, values);
935 } while (!state.compareAndSet(src, res));
936 if (notifyUponTransientChanges())
937 fireConfigChangedEvent();
938 }
939
940 private ConfigSnapshotSnapshot">ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
941 final String section, final String subsection, final String name,
942 final List<String> values) {
943 final List<ConfigLine> entries = copy(srcState, values);
944 int entryIndex = 0;
945 int valueIndex = 0;
946 int insertPosition = -1;
947
948
949
950 while (entryIndex < entries.size() && valueIndex < values.size()) {
951 final ConfigLine e = entries.get(entryIndex);
952 if (e.includedFrom == null && e.match(section, subsection, name)) {
953 entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
954 insertPosition = entryIndex + 1;
955 }
956 entryIndex++;
957 }
958
959
960
961 if (valueIndex == values.size() && entryIndex < entries.size()) {
962 while (entryIndex < entries.size()) {
963 final ConfigLine e = entries.get(entryIndex++);
964 if (e.includedFrom == null
965 && e.match(section, subsection, name))
966 entries.remove(--entryIndex);
967 }
968 }
969
970
971
972 if (valueIndex < values.size() && entryIndex == entries.size()) {
973 if (insertPosition < 0) {
974
975
976
977
978 insertPosition = findSectionEnd(entries, section, subsection,
979 true);
980 }
981 if (insertPosition < 0) {
982
983
984
985 final ConfigLineLine.html#ConfigLine">ConfigLine e = new ConfigLine();
986 e.section = section;
987 e.subsection = subsection;
988 entries.add(e);
989 insertPosition = entries.size();
990 }
991 while (valueIndex < values.size()) {
992 final ConfigLineLine.html#ConfigLine">ConfigLine e = new ConfigLine();
993 e.section = section;
994 e.subsection = subsection;
995 e.name = name;
996 e.value = values.get(valueIndex++);
997 entries.add(insertPosition++, e);
998 }
999 }
1000
1001 return newState(entries);
1002 }
1003
1004 private static List<ConfigLine> copy(final ConfigSnapshot src,
1005 final List<String> values) {
1006
1007
1008
1009 final int max = src.entryList.size() + values.size() + 1;
1010 final ArrayList<ConfigLine> r = new ArrayList<>(max);
1011 r.addAll(src.entryList);
1012 return r;
1013 }
1014
1015 private static int findSectionEnd(final List<ConfigLine> entries,
1016 final String section, final String subsection,
1017 boolean skipIncludedLines) {
1018 for (int i = 0; i < entries.size(); i++) {
1019 ConfigLine e = entries.get(i);
1020 if (e.includedFrom != null && skipIncludedLines) {
1021 continue;
1022 }
1023
1024 if (e.match(section, subsection, null)) {
1025 i++;
1026 while (i < entries.size()) {
1027 e = entries.get(i);
1028 if (e.match(section, subsection, e.name))
1029 i++;
1030 else
1031 break;
1032 }
1033 return i;
1034 }
1035 }
1036 return -1;
1037 }
1038
1039
1040
1041
1042
1043
1044 public String toText() {
1045 final StringBuilder out = new StringBuilder();
1046 for (ConfigLine e : state.get().entryList) {
1047 if (e.includedFrom != null)
1048 continue;
1049 if (e.prefix != null)
1050 out.append(e.prefix);
1051 if (e.section != null && e.name == null) {
1052 out.append('[');
1053 out.append(e.section);
1054 if (e.subsection != null) {
1055 out.append(' ');
1056 String escaped = escapeValue(e.subsection);
1057
1058 boolean quoted = escaped.startsWith("\"")
1059 && escaped.endsWith("\"");
1060 if (!quoted)
1061 out.append('"');
1062 out.append(escaped);
1063 if (!quoted)
1064 out.append('"');
1065 }
1066 out.append(']');
1067 } else if (e.section != null && e.name != null) {
1068 if (e.prefix == null || "".equals(e.prefix))
1069 out.append('\t');
1070 out.append(e.name);
1071 if (!isMissing(e.value)) {
1072 out.append(" =");
1073 if (e.value != null) {
1074 out.append(' ');
1075 out.append(escapeValue(e.value));
1076 }
1077 }
1078 if (e.suffix != null)
1079 out.append(' ');
1080 }
1081 if (e.suffix != null)
1082 out.append(e.suffix);
1083 out.append('\n');
1084 }
1085 return out.toString();
1086 }
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097 public void fromText(String text) throws ConfigInvalidException {
1098 state.set(newState(fromTextRecurse(text, 1, null)));
1099 }
1100
1101 private List<ConfigLine> fromTextRecurse(String text, int depth,
1102 String includedFrom) throws ConfigInvalidException {
1103 if (depth > MAX_DEPTH) {
1104 throw new ConfigInvalidException(
1105 JGitText.get().tooManyIncludeRecursions);
1106 }
1107 final List<ConfigLine> newEntries = new ArrayList<>();
1108 final StringReader in = new StringReader(text);
1109 ConfigLine last = null;
1110 ConfigLine e = new ConfigLine();
1111 e.includedFrom = includedFrom;
1112 for (;;) {
1113 int input = in.read();
1114 if (-1 == input) {
1115 if (e.section != null)
1116 newEntries.add(e);
1117 break;
1118 }
1119
1120 final char c = (char) input;
1121 if ('\n' == c) {
1122
1123 newEntries.add(e);
1124 if (e.section != null)
1125 last = e;
1126 e = new ConfigLine();
1127 e.includedFrom = includedFrom;
1128 } else if (e.suffix != null) {
1129
1130 e.suffix += c;
1131
1132 } else if (';' == c || '#' == c) {
1133
1134 e.suffix = String.valueOf(c);
1135
1136 } else if (e.section == null && Character.isWhitespace(c)) {
1137
1138 if (e.prefix == null)
1139 e.prefix = "";
1140 e.prefix += c;
1141
1142 } else if ('[' == c) {
1143
1144 e.section = readSectionName(in);
1145 input = in.read();
1146 if ('"' == input) {
1147 e.subsection = readSubsectionName(in);
1148 input = in.read();
1149 }
1150 if (']' != input)
1151 throw new ConfigInvalidException(JGitText.get().badGroupHeader);
1152 e.suffix = "";
1153
1154 } else if (last != null) {
1155
1156 e.section = last.section;
1157 e.subsection = last.subsection;
1158 in.reset();
1159 e.name = readKeyName(in);
1160 if (e.name.endsWith("\n")) {
1161 e.name = e.name.substring(0, e.name.length() - 1);
1162 e.value = MISSING_ENTRY;
1163 } else
1164 e.value = readValue(in);
1165
1166 if (e.section.equalsIgnoreCase("include")) {
1167 addIncludedConfig(newEntries, e, depth);
1168 }
1169 } else
1170 throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
1171 }
1172
1173 return newEntries;
1174 }
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187 protected byte[] readIncludedConfig(String relPath)
1188 throws ConfigInvalidException {
1189 return null;
1190 }
1191
1192 private void addIncludedConfig(final List<ConfigLine> newEntries,
1193 ConfigLine line, int depth) throws ConfigInvalidException {
1194 if (!line.name.equalsIgnoreCase("path") ||
1195 line.value == null || line.value.equals(MISSING_ENTRY)) {
1196 throw new ConfigInvalidException(MessageFormat.format(
1197 JGitText.get().invalidLineInConfigFileWithParam, line));
1198 }
1199 byte[] bytes = readIncludedConfig(line.value);
1200 if (bytes == null) {
1201 return;
1202 }
1203
1204 String decoded;
1205 if (isUtf8(bytes)) {
1206 decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
1207 } else {
1208 decoded = RawParseUtils.decode(bytes);
1209 }
1210 try {
1211 newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
1212 } catch (ConfigInvalidException e) {
1213 throw new ConfigInvalidException(MessageFormat
1214 .format(JGitText.get().cannotReadFile, line.value), e);
1215 }
1216 }
1217
1218 private ConfigSnapshot newState() {
1219 return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
1220 getBaseState());
1221 }
1222
1223 private ConfigSnapshot newState(List<ConfigLine> entries) {
1224 return new ConfigSnapshot(Collections.unmodifiableList(entries),
1225 getBaseState());
1226 }
1227
1228
1229
1230
1231 protected void clear() {
1232 state.set(newState());
1233 }
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243 protected boolean isUtf8(final byte[] bytes) {
1244 return bytes.length >= 3 && bytes[0] == (byte) 0xEF
1245 && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
1246 }
1247
1248 private static String readSectionName(StringReader in)
1249 throws ConfigInvalidException {
1250 final StringBuilder name = new StringBuilder();
1251 for (;;) {
1252 int c = in.read();
1253 if (c < 0)
1254 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1255
1256 if (']' == c) {
1257 in.reset();
1258 break;
1259 }
1260
1261 if (' ' == c || '\t' == c) {
1262 for (;;) {
1263 c = in.read();
1264 if (c < 0)
1265 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1266
1267 if ('"' == c) {
1268 in.reset();
1269 break;
1270 }
1271
1272 if (' ' == c || '\t' == c)
1273 continue;
1274 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1275 }
1276 break;
1277 }
1278
1279 if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
1280 name.append((char) c);
1281 else
1282 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1283 }
1284 return name.toString();
1285 }
1286
1287 private static String readKeyName(StringReader in)
1288 throws ConfigInvalidException {
1289 final StringBuilder name = new StringBuilder();
1290 for (;;) {
1291 int c = in.read();
1292 if (c < 0)
1293 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1294
1295 if ('=' == c)
1296 break;
1297
1298 if (' ' == c || '\t' == c) {
1299 for (;;) {
1300 c = in.read();
1301 if (c < 0)
1302 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1303
1304 if ('=' == c)
1305 break;
1306
1307 if (';' == c || '#' == c || '\n' == c) {
1308 in.reset();
1309 break;
1310 }
1311
1312 if (' ' == c || '\t' == c)
1313 continue;
1314 throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
1315 }
1316 break;
1317 }
1318
1319 if (Character.isLetterOrDigit((char) c) || c == '-') {
1320
1321
1322
1323 name.append((char) c);
1324 } else if ('\n' == c) {
1325 in.reset();
1326 name.append((char) c);
1327 break;
1328 } else
1329 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
1330 }
1331 return name.toString();
1332 }
1333
1334 private static String readSubsectionName(StringReader in)
1335 throws ConfigInvalidException {
1336 StringBuilder r = new StringBuilder();
1337 for (;;) {
1338 int c = in.read();
1339 if (c < 0) {
1340 break;
1341 }
1342
1343 if ('\n' == c) {
1344 throw new ConfigInvalidException(
1345 JGitText.get().newlineInQuotesNotAllowed);
1346 }
1347 if ('\\' == c) {
1348 c = in.read();
1349 switch (c) {
1350 case -1:
1351 throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1352
1353 case '\\':
1354 case '"':
1355 r.append((char) c);
1356 continue;
1357
1358 default:
1359
1360
1361 r.append((char) c);
1362 continue;
1363 }
1364 }
1365 if ('"' == c) {
1366 break;
1367 }
1368
1369 r.append((char) c);
1370 }
1371 return r.toString();
1372 }
1373
1374 private static String readValue(StringReader in)
1375 throws ConfigInvalidException {
1376 StringBuilder value = new StringBuilder();
1377 StringBuilder trailingSpaces = null;
1378 boolean quote = false;
1379 boolean inLeadingSpace = true;
1380
1381 for (;;) {
1382 int c = in.read();
1383 if (c < 0) {
1384 break;
1385 }
1386 if ('\n' == c) {
1387 if (quote) {
1388 throw new ConfigInvalidException(
1389 JGitText.get().newlineInQuotesNotAllowed);
1390 }
1391 in.reset();
1392 break;
1393 }
1394
1395 if (!quote && (';' == c || '#' == c)) {
1396 if (trailingSpaces != null) {
1397 trailingSpaces.setLength(0);
1398 }
1399 in.reset();
1400 break;
1401 }
1402
1403 char cc = (char) c;
1404 if (Character.isWhitespace(cc)) {
1405 if (inLeadingSpace) {
1406 continue;
1407 }
1408 if (trailingSpaces == null) {
1409 trailingSpaces = new StringBuilder();
1410 }
1411 trailingSpaces.append(cc);
1412 continue;
1413 }
1414 inLeadingSpace = false;
1415 if (trailingSpaces != null) {
1416 value.append(trailingSpaces);
1417 trailingSpaces.setLength(0);
1418 }
1419
1420 if ('\\' == c) {
1421 c = in.read();
1422 switch (c) {
1423 case -1:
1424 throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1425 case '\n':
1426 continue;
1427 case 't':
1428 value.append('\t');
1429 continue;
1430 case 'b':
1431 value.append('\b');
1432 continue;
1433 case 'n':
1434 value.append('\n');
1435 continue;
1436 case '\\':
1437 value.append('\\');
1438 continue;
1439 case '"':
1440 value.append('"');
1441 continue;
1442 case '\r': {
1443 int next = in.read();
1444 if (next == '\n') {
1445 continue;
1446 } else if (next >= 0) {
1447 in.reset();
1448 }
1449 break;
1450 }
1451 default:
1452 break;
1453 }
1454 throw new ConfigInvalidException(
1455 MessageFormat.format(JGitText.get().badEscape,
1456 Character.isAlphabetic(c)
1457 ? Character.valueOf(((char) c))
1458 : toUnicodeLiteral(c)));
1459 }
1460
1461 if ('"' == c) {
1462 quote = !quote;
1463 continue;
1464 }
1465
1466 value.append(cc);
1467 }
1468 return value.length() > 0 ? value.toString() : null;
1469 }
1470
1471 private static String toUnicodeLiteral(int c) {
1472 return String.format("\\u%04x",
1473 Integer.valueOf(c));
1474 }
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490 public static interface SectionParser<T> {
1491
1492
1493
1494
1495
1496
1497
1498 T parse(Config cfg);
1499 }
1500
1501 private static class StringReader {
1502 private final char[] buf;
1503
1504 private int pos;
1505
1506 StringReader(String in) {
1507 buf = in.toCharArray();
1508 }
1509
1510 int read() {
1511 if (pos >= buf.length) {
1512 return -1;
1513 }
1514 return buf[pos++];
1515 }
1516
1517 void reset() {
1518 pos--;
1519 }
1520 }
1521
1522
1523
1524
1525
1526
1527 public static interface ConfigEnum {
1528
1529
1530
1531
1532
1533 String toConfigValue();
1534
1535
1536
1537
1538
1539
1540
1541
1542 boolean matchConfigValue(String in);
1543 }
1544 }