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