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