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