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