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