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