1 package io.jawk.jrt;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25 import java.io.FileOutputStream;
26 import java.io.FileInputStream;
27 import java.io.IOException;
28 import java.io.InputStreamReader;
29 import java.io.PrintStream;
30 import java.nio.charset.StandardCharsets;
31 import java.text.DecimalFormatSymbols;
32 import java.util.ArrayList;
33 import java.util.Date;
34 import java.util.Enumeration;
35 import java.util.HashMap;
36 import java.util.IdentityHashMap;
37 import java.util.HashSet;
38 import java.util.IllegalFormatException;
39 import java.util.List;
40 import java.util.Locale;
41 import java.util.Map;
42 import java.util.Objects;
43 import java.util.Set;
44 import java.util.StringTokenizer;
45 import java.util.regex.Matcher;
46 import java.util.regex.Pattern;
47 import io.jawk.Awk;
48 import io.jawk.intermediate.UninitializedObject;
49 import io.jawk.intermediate.UntypedObject;
50 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86 public class JRT {
87
88 private static final boolean IS_WINDOWS = System.getProperty("os.name").indexOf("Windows") >= 0;
89
90
91
92
93
94
95
96
97 private static final int DYNAMIC_PATTERN_CACHE_LIMIT = 256;
98
99 private final VariableManager vm;
100
101 private IoState ioState;
102
103 private AwkSink awkSink;
104
105 private PrintStream error;
106
107 private PrintStream warning = System.err;
108
109 private Object ignorecase = Long.valueOf(0L);
110
111 private boolean ignoreCase;
112
113 private Map<Pattern, Pattern> caseInsensitivePatterns;
114
115 private Map<String, Pattern> dynamicPatterns;
116
117 private Map<String, Pattern> dynamicPatternsIgnoreCase;
118
119 private final StringBuffer replaceResult = new StringBuffer();
120
121 private Object inputLine = null;
122
123 private RecordState recordState;
124
125 private InputSource activeSource;
126 private static final UninitializedObject BLANK = new UninitializedObject();
127
128 private static final Integer ONE = Integer.valueOf(1);
129 private static final Integer ZERO = Integer.valueOf(0);
130 private static final Integer MINUS_ONE = Integer.valueOf(-1);
131 private String jrtInputString;
132
133
134 private long nr;
135 private long fnr;
136 private int rstart;
137 private int rlength;
138 private Object filename;
139 private Object errno;
140 private Object argind;
141 private boolean syntheticFilePresented;
142 private String fs;
143 private String rs;
144 private String ofs;
145 private String ors;
146 private String convfmt;
147 private String ofmt;
148 private String subsep;
149 private final Locale locale;
150 private final char decimalSeparator;
151
152 private static final class FileOutputState {
153
154 private final AwkSink sink;
155
156 private FileOutputState(AwkSink sinkParam) {
157 this.sink = Objects.requireNonNull(sinkParam, "sink");
158 }
159 }
160
161 private static final class CommandInputState {
162
163 private final Process process;
164 private final PartitioningReader reader;
165 private final Thread errorPump;
166
167 private CommandInputState(Process processParam, PartitioningReader readerParam, Thread errorPumpParam) {
168 this.process = Objects.requireNonNull(processParam, "process");
169 this.reader = Objects.requireNonNull(readerParam, "reader");
170 this.errorPump = errorPumpParam;
171 }
172 }
173
174 private static final class ProcessOutputState {
175
176 private final Process process;
177 private final AwkSink sink;
178 private final PrintStream processOutput;
179 private final Thread stdoutPump;
180 private final Thread stderrPump;
181
182 private ProcessOutputState(
183 Process processParam,
184 AwkSink sinkParam,
185 PrintStream processOutputParam,
186 Thread stdoutPumpParam,
187 Thread stderrPumpParam) {
188 this.process = Objects.requireNonNull(processParam, "process");
189 this.sink = Objects.requireNonNull(sinkParam, "sink");
190 this.processOutput = Objects.requireNonNull(processOutputParam, "processOutput");
191 this.stdoutPump = stdoutPumpParam;
192 this.stderrPump = stderrPumpParam;
193 }
194 }
195
196 private static final class IoState {
197
198 private final Map<String, PartitioningReader> fileReaders = new HashMap<String, PartitioningReader>();
199 private final Map<String, CommandInputState> commandInputs = new HashMap<String, CommandInputState>();
200 private final Map<String, FileOutputState> fileOutputs = new HashMap<String, FileOutputState>();
201 private final Map<String, ProcessOutputState> processOutputs = new HashMap<String, ProcessOutputState>();
202 }
203
204
205
206
207
208
209
210
211
212 @SuppressFBWarnings(value = {
213 "EI_EXPOSE_REP2",
214 "CT_CONSTRUCTOR_THROW" }, justification = "JRT must hold the provided runtime collaborators for later use;"
215 + " fail-fast argument validation with no security-sensitive state to protect from finalizer attacks")
216 public JRT(VariableManager vm, Locale locale, AwkSink awkSink, PrintStream error) {
217 this.vm = vm;
218 this.locale = locale == null ? Locale.US : locale;
219 this.decimalSeparator = DecimalFormatSymbols.getInstance(this.locale).getDecimalSeparator();
220 this.awkSink = Objects.requireNonNull(awkSink, "awkSink");
221 this.error = error == null ? System.err : error;
222 this.nr = 0L;
223 this.fnr = 0L;
224 this.rstart = 0;
225 this.rlength = 0;
226 this.filename = "";
227 this.fs = Awk.DEFAULT_FS;
228 this.rs = Awk.DEFAULT_RS;
229 this.ofs = Awk.DEFAULT_OFS;
230 this.ors = Awk.DEFAULT_ORS;
231 this.convfmt = Awk.DEFAULT_CONVFMT;
232 this.ofmt = Awk.DEFAULT_OFMT;
233 this.subsep = Awk.DEFAULT_SUBSEP;
234 }
235
236
237
238
239
240
241
242 public void setAwkSink(AwkSink sink) {
243 awkSink = Objects.requireNonNull(sink, "awkSink");
244 }
245
246
247
248
249
250
251
252 public void setErrorStream(PrintStream errorStream) {
253 this.error = Objects.requireNonNull(errorStream, "errorStream");
254 }
255
256
257
258
259
260
261
262
263
264 public void setWarningStream(PrintStream warningStream) {
265 this.warning = Objects.requireNonNull(warningStream, "warningStream");
266 }
267
268
269
270
271
272
273
274 public void printWarning(String message) {
275 warning.println(message);
276 warning.flush();
277 }
278
279
280
281
282
283
284 public AwkSink getAwkSink() {
285 return awkSink;
286 }
287
288
289
290
291
292
293 public Locale getLocale() {
294 return locale;
295 }
296
297 private IoState getIoState() {
298 if (ioState == null) {
299 ioState = new IoState();
300 }
301 return ioState;
302 }
303
304
305
306
307
308
309
310
311 public static boolean isJrtManagedSpecialVariable(String name) {
312 switch (name) {
313 case "FS":
314 case "RS":
315 case "OFS":
316 case "ORS":
317 case "CONVFMT":
318 case "OFMT":
319 case "SUBSEP":
320 case "FILENAME":
321 case "NF":
322 case "NR":
323 case "FNR":
324 case "ARGC":
325 case "IGNORECASE":
326 case "ERRNO":
327 case "ARGIND":
328 return true;
329 default:
330 return false;
331 }
332 }
333
334
335
336
337
338
339
340
341
342 public static boolean isGawkOnlySpecialVariable(String name) {
343 return "ERRNO".equals(name) || "ARGIND".equals(name);
344 }
345
346
347
348
349
350
351
352 public static Map<String, Object> copySpecialVariables(Map<String, Object> variableMap) {
353 Map<String, Object> specialVariables = new HashMap<String, Object>();
354 if (variableMap == null || variableMap.isEmpty()) {
355 return specialVariables;
356 }
357 for (Map.Entry<String, Object> entry : variableMap.entrySet()) {
358 if (isJrtManagedSpecialVariable(entry.getKey())) {
359 specialVariables.put(entry.getKey(), entry.getValue());
360 }
361 }
362 return specialVariables;
363 }
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380 public void prepareForExecution(String defaultFs, String defaultRs) {
381
382 jrtCloseAll();
383
384
385 ioState = null;
386 inputLine = null;
387 recordState = null;
388 activeSource = null;
389 jrtInputString = null;
390 nr = 0L;
391 fnr = 0L;
392 rstart = 0;
393 rlength = 0;
394 filename = "";
395 errno = "";
396 argind = ZERO;
397 syntheticFilePresented = false;
398
399
400 setFS(defaultFs == null ? Awk.DEFAULT_FS : defaultFs);
401 setRS(defaultRs);
402 setOFS(Awk.DEFAULT_OFS);
403 setORS(Awk.DEFAULT_ORS);
404 setCONVFMT(Awk.DEFAULT_CONVFMT);
405 setOFMT(Awk.DEFAULT_OFMT);
406 setSUBSEP(Awk.DEFAULT_SUBSEP);
407 setFILENAMEViaJrt("");
408 setNR(0);
409 setFNR(0);
410 setRSTART(0);
411 setRLENGTH(0);
412 setIGNORECASE(Long.valueOf(0L));
413 }
414
415
416
417
418
419
420
421 public final void assignInitialVariables(Map<String, Object> initialVarMap) {
422 for (Map.Entry<String, Object> var : initialVarMap.entrySet()) {
423 String name = var.getKey();
424 Object value = var.getValue();
425 if (!applySpecialVariable(name, value)) {
426 vm.assignVariable(name, value);
427 }
428 }
429 }
430
431
432
433
434
435
436
437
438
439 public boolean applySpecialVariable(String name, Object value) {
440 switch (name) {
441 case "FS":
442 setFS(value);
443 return true;
444 case "RS":
445 setRS(value);
446 return true;
447 case "OFS":
448 setOFS(value);
449 return true;
450 case "ORS":
451 setORS(value);
452 return true;
453 case "CONVFMT":
454 setCONVFMT(value);
455 return true;
456 case "OFMT":
457 setOFMT(value);
458 return true;
459 case "SUBSEP":
460 setSUBSEP(value);
461 return true;
462 case "FILENAME":
463 setFILENAMEViaJrt(value);
464 return true;
465 case "NF":
466 setNF(value);
467 return true;
468 case "NR":
469 setNR(value);
470 return true;
471 case "FNR":
472 setFNR(value);
473 return true;
474 case "ARGC":
475 setARGC(value);
476 return true;
477 case "IGNORECASE":
478 setIGNORECASE(value);
479 return true;
480 case "ERRNO":
481 setERRNO(value);
482 return true;
483 case "ARGIND":
484 setARGIND(value);
485 return true;
486 default:
487 return false;
488 }
489 }
490
491
492
493
494
495
496
497
498
499
500 public final void applySpecialVariables(Map<String, Object> variableMap) {
501 if (variableMap == null || variableMap.isEmpty()) {
502 return;
503 }
504 for (Map.Entry<String, Object> var : variableMap.entrySet()) {
505
506
507 applySpecialVariable(var.getKey(), var.getValue());
508 }
509 }
510
511
512
513
514
515
516
517
518
519
520 public static void assignEnvironmentVariables(AssocArray aa) {
521 Map<String, String> env = System.getenv();
522 for (Map.Entry<String, String> var : env.entrySet()) {
523 aa.put(var.getKey(), new StrNum(var.getValue()));
524 }
525 }
526
527
528
529
530
531
532
533
534 public static Map<Object, Object> createAwkMap(boolean sortedArrayKeys) {
535 return AssocArray.create(sortedArrayKeys);
536 }
537
538
539
540
541
542
543
544
545
546
547 public static boolean containsAwkKey(Map<Object, Object> map, Object key) {
548 if (map instanceof AssocArray) {
549 return ((AssocArray) map).isIn(key);
550 }
551 return map.containsKey(key);
552 }
553
554
555
556
557
558
559
560
561
562
563
564
565 public static Object getAssocArrayValue(Map<Object, Object> map, Object key) {
566 if (map instanceof AssocArray) {
567 return map.get(key);
568 }
569 Object value = map.get(key);
570 return value != null ? value : BLANK;
571 }
572
573
574
575
576
577
578
579
580
581
582
583
584 public String getAwkStringEntry(Map<Object, Object> map, Object key) {
585 if (!containsAwkKey(map, key)) {
586 return null;
587 }
588 return toAwkString(getAssocArrayValue(map, key));
589 }
590
591
592
593
594
595
596
597
598 public String toAwkString(Object o) {
599 return AwkSink.formatOutputValue(o, this.convfmt, this.locale);
600 }
601
602
603
604
605
606
607
608 public static double toDouble(final Object o) {
609 if (o == null) {
610 return 0;
611 }
612
613 if (o instanceof Number) {
614 return ((Number) o).doubleValue();
615 }
616
617 if (o instanceof Character) {
618 return (double) ((Character) o).charValue();
619 }
620
621 if (o instanceof StrNum) {
622 StrNum strNum = (StrNum) o;
623 if (strNum.isNumber()) {
624 return strNum.doubleValue();
625 }
626 }
627
628
629 String s = o.toString();
630 int length = s.length();
631
632
633
634 if (length > 26) {
635 length = 26;
636 }
637
638
639
640
641 while (length > 0) {
642 try {
643 return Double.parseDouble(s.substring(0, length));
644 } catch (NumberFormatException nfe) {
645 length--;
646 }
647 }
648
649
650 return 0;
651 }
652
653
654
655
656
657
658
659
660 public static boolean isActuallyLong(double d) {
661 double r = Math.rint(d);
662 return Math.abs(d - r) < Math.ulp(d);
663 }
664
665
666
667
668
669
670
671 public static long toLong(final Object o) {
672 if (o == null) {
673 return 0;
674 }
675
676 if (o instanceof Number) {
677 return ((Number) o).longValue();
678 }
679
680 if (o instanceof Character) {
681 return (long) ((Character) o).charValue();
682 }
683
684
685 String s = o.toString();
686 int length = s.length();
687
688
689
690 if (length > 20) {
691 length = 20;
692 }
693
694
695
696
697 while (length > 0) {
698 try {
699 return Long.parseLong(s.substring(0, length));
700 } catch (NumberFormatException nfe) {
701 length--;
702 }
703 }
704
705 return 0;
706 }
707
708
709
710
711
712
713
714
715
716 public static long parseFieldNumber(Object obj) {
717 long num = toLong(obj);
718 if (num < 0) {
719 throw new AwkRuntimeException(
720 "Field $(" + obj.toString()
721 + ") is incorrect.");
722 }
723 return num;
724 }
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741 public static boolean compare2(Object o1, Object o2, int mode) {
742 return compare2(o1, o2, mode, false);
743 }
744
745
746
747
748
749
750
751
752
753
754
755
756
757 public static boolean compare2(Object o1, Object o2, int mode, boolean ignoreCase) {
758 if (o1 instanceof Number && o2 instanceof Number) {
759 return compareNumbers(((Number) o1).doubleValue(), ((Number) o2).doubleValue(), mode);
760 }
761
762 String o1String = o1 == null ? "" : o1.toString();
763 String o2String = o2 == null ? "" : o2.toString();
764
765 if (o1 instanceof UninitializedObject) {
766 if (isBlankOrZero(o2, o2String)) {
767 return mode == 0;
768 } else {
769 return mode < 0;
770 }
771 }
772 if (o2 instanceof UninitializedObject) {
773 if (isBlankOrZero(o1, o1String)) {
774 return mode == 0;
775 } else {
776 return mode > 0;
777 }
778 }
779
780 if (isNumericComparisonOperand(o1) && isNumericComparisonOperand(o2)) {
781 return compareNumbers(getDoubleForComparison(o1), getDoubleForComparison(o2), mode);
782 }
783
784 if (mode == 0) {
785 return ignoreCase ? o1String.equalsIgnoreCase(o2String) : o1String.equals(o2String);
786 }
787 int comparison = ignoreCase ? o1String.compareToIgnoreCase(o2String) : o1String.compareTo(o2String);
788 return mode < 0 ? comparison < 0 : comparison > 0;
789 }
790
791
792
793
794
795
796
797
798
799
800 public int index(String haystack, String needle) {
801 if (!ignoreCase) {
802 return haystack.indexOf(needle) + 1;
803 }
804 int max = haystack.length() - needle.length();
805 for (int i = 0; i <= max; i++) {
806 if (haystack.regionMatches(true, i, needle, 0, needle.length())) {
807 return i + 1;
808 }
809 }
810 return 0;
811 }
812
813 private static boolean isBlankOrZero(Object value, String stringValue) {
814 if (value instanceof UninitializedObject) {
815 return true;
816 }
817 if (value instanceof Number) {
818 return ((Number) value).doubleValue() == 0.0D;
819 }
820 if (value instanceof StrNum && ((StrNum) value).isNumber()) {
821 return ((StrNum) value).doubleValue() == 0.0D;
822 }
823 return "".equals(stringValue) || "0".equals(stringValue);
824 }
825
826 private static boolean isNumericComparisonOperand(Object value) {
827 return value instanceof Number || value instanceof StrNum && ((StrNum) value).isNumber();
828 }
829
830 private static double getDoubleForComparison(Object value) {
831 if (value instanceof Number) {
832 return ((Number) value).doubleValue();
833 }
834 return ((StrNum) value).doubleValue();
835 }
836
837 private static boolean compareNumbers(double o1Number, double o2Number, int mode) {
838 if (mode < 0) {
839 return o1Number < o2Number;
840 } else if (mode == 0) {
841 return o1Number == o2Number;
842 } else {
843 return o1Number > o2Number;
844 }
845 }
846
847
848
849
850
851
852
853 public static Object toJavaScalar(Object value) {
854 if (value instanceof StrNum) {
855 return value.toString();
856 }
857 if (value instanceof Double || value instanceof Float) {
858 double number = ((Number) value).doubleValue();
859 if (isActuallyLong(number)) {
860 return Long.valueOf((long) Math.rint(number));
861 }
862 }
863 return value;
864 }
865
866
867
868
869
870
871
872
873 public boolean isParseableNumber(String value) {
874 return isParseableNumber(value, decimalSeparator);
875 }
876
877
878
879
880
881
882
883
884
885
886
887
888
889 public static Object untypedToBlank(Object value) {
890 return value instanceof UntypedObject ? BLANK : value;
891 }
892
893 static boolean isParseableNumber(String value, char decimalSeparator) {
894 int index = 0;
895 int length = value.length();
896
897 if (length == 0) {
898 return false;
899 }
900
901 char current = value.charAt(index);
902 if (current == '+' || current == '-') {
903 index++;
904 if (index == length) {
905 return false;
906 }
907 }
908
909 boolean digitFound = false;
910 while (index < length && value.charAt(index) >= '0' && value.charAt(index) <= '9') {
911 index++;
912 digitFound = true;
913 }
914
915 if (index < length && value.charAt(index) == decimalSeparator) {
916 index++;
917 while (index < length && value.charAt(index) >= '0' && value.charAt(index) <= '9') {
918 index++;
919 digitFound = true;
920 }
921 }
922
923 if (!digitFound) {
924 return false;
925 }
926
927 if (index < length && (value.charAt(index) == 'e' || value.charAt(index) == 'E')) {
928 index++;
929 if (index < length && (value.charAt(index) == '+' || value.charAt(index) == '-')) {
930 index++;
931 }
932
933 boolean exponentDigitFound = false;
934 while (index < length && value.charAt(index) >= '0' && value.charAt(index) <= '9') {
935 index++;
936 exponentDigitFound = true;
937 }
938 if (!exponentDigitFound) {
939 return false;
940 }
941 }
942
943 return index == length;
944 }
945
946 static String normalizeNumberForComparison(String value, char decimalSeparator) {
947 return decimalSeparator == '.' ? value : value.replace(decimalSeparator, '.');
948 }
949
950
951
952
953
954
955
956
957
958
959
960
961 public static Object inc(Object o) {
962 return toDouble(o) + 1;
963 }
964
965
966
967
968
969
970
971
972
973
974
975
976 public static Object dec(Object o) {
977 return toDouble(o) - 1;
978 }
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997 public final boolean toBoolean(Object o) {
998 boolean val;
999 if (o instanceof Integer) {
1000 val = ((Integer) o).intValue() != 0;
1001 } else if (o instanceof Long) {
1002 val = ((Long) o).longValue() != 0;
1003 } else if (o instanceof Double) {
1004 val = ((Double) o).doubleValue() != 0;
1005 } else if (o instanceof StrNum) {
1006 StrNum strNum = (StrNum) o;
1007 val = strNum.isNumber() ? strNum.doubleValue() != 0 : strNum.toString().length() > 0;
1008 } else if (o instanceof String) {
1009 val = (o.toString().length() > 0);
1010 } else if (o instanceof UninitializedObject) {
1011 val = false;
1012 } else if (o instanceof Pattern) {
1013
1014 Pattern pattern = caseAwarePattern((Pattern) o);
1015 Object inputField = jrtGetInputField(0);
1016 String s = inputField instanceof UninitializedObject ? "" : inputField.toString();
1017 Matcher matcher = pattern.matcher(s);
1018 val = matcher.find();
1019 } else {
1020 throw new Error("Unknown operand_stack type: " + o.getClass() + " for value " + o);
1021 }
1022 return val;
1023 }
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034 public int split(Object array, Object string) {
1035 return splitWorker(new StringTokenizer(toAwkString(string)), toArrayMap(array));
1036 }
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050 public int split(Object fieldSeparator, Object array, Object string) {
1051 return splitWorker(splitTokenizer(toAwkString(string), fieldSeparator), toArrayMap(array));
1052 }
1053
1054 private static Map<Object, Object> toArrayMap(Object array) {
1055 if (!(array instanceof Map)) {
1056 throw new IllegalArgumentException("split target must be a Map.");
1057 }
1058 @SuppressWarnings("unchecked")
1059 Map<Object, Object> arrayMap = (Map<Object, Object>) array;
1060 return arrayMap;
1061 }
1062
1063 private int splitWorker(Enumeration<Object> e, Map<Object, Object> array) {
1064 int cnt = 0;
1065 array.clear();
1066 while (e.hasMoreElements()) {
1067 Object value = e.nextElement();
1068 array.put(Long.valueOf(++cnt), toInputScalar(value));
1069 }
1070 array.put(0L, Long.valueOf(cnt));
1071 return cnt;
1072 }
1073
1074
1075
1076
1077
1078
1079
1080
1081 public PartitioningReader getPartitioningReader() {
1082 if (activeSource instanceof StreamInputSource) {
1083 return ((StreamInputSource) activeSource).getPartitioningReader();
1084 }
1085 return null;
1086 }
1087
1088
1089
1090
1091
1092
1093
1094
1095 public Object getInputLine() {
1096 if (recordState != null) {
1097 return recordState.getField(0);
1098 }
1099 return inputLine;
1100 }
1101
1102
1103
1104
1105
1106
1107
1108 public Integer getNF() {
1109 if (recordState == null) {
1110 return Integer.valueOf(0);
1111 }
1112 return Integer.valueOf(recordState.getNF());
1113 }
1114
1115
1116
1117
1118
1119
1120 public void setNF(Object nfObject) {
1121 jrtSetNF(nfObject);
1122 }
1123
1124
1125
1126
1127
1128
1129 public Long getNR() {
1130 return Long.valueOf(nr);
1131 }
1132
1133
1134
1135
1136
1137
1138 public void setNR(Object value) {
1139 this.nr = toLong(value);
1140 }
1141
1142
1143
1144
1145
1146
1147 public Long getFNR() {
1148 return Long.valueOf(fnr);
1149 }
1150
1151
1152
1153
1154
1155
1156 public void setFNR(Object value) {
1157 this.fnr = toLong(value);
1158 }
1159
1160
1161
1162
1163
1164
1165 public Object getFSVar() {
1166 return fs;
1167 }
1168
1169
1170
1171
1172
1173
1174 public String getFSString() {
1175 return fs;
1176 }
1177
1178
1179
1180
1181
1182
1183 public void setFS(Object value) {
1184 this.fs = value == null ? "" : value.toString();
1185 }
1186
1187
1188
1189
1190
1191
1192
1193 public void setIGNORECASE(Object value) {
1194 this.ignorecase = value == null ? Long.valueOf(0L) : value;
1195
1196
1197 this.ignoreCase = toBoolean(this.ignorecase);
1198 }
1199
1200
1201
1202
1203
1204
1205 public Object getIGNORECASEVar() {
1206 return ignorecase;
1207 }
1208
1209
1210
1211
1212
1213
1214
1215
1216 public boolean isIgnoreCase() {
1217 return ignoreCase;
1218 }
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228 public int regexpFlags() {
1229 return ignoreCase ? Pattern.CASE_INSENSITIVE : 0;
1230 }
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242 public int replaceFirst(String orig, String repl, String ere) {
1243 return replace(orig, repl, ere, false);
1244 }
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256 public int replaceAll(String orig, String repl, String ere) {
1257 return replace(orig, repl, ere, true);
1258 }
1259
1260 private int replace(String orig, String repl, String ere, boolean global) {
1261 replaceResult.setLength(0);
1262 String preparedReplacement = prepareReplacement(repl, false);
1263 Matcher matcher = dynamicPattern(ere).matcher(orig);
1264 int count = 0;
1265 while (matcher.find()) {
1266 count++;
1267 matcher.appendReplacement(replaceResult, preparedReplacement);
1268 if (!global) {
1269 break;
1270 }
1271 }
1272 matcher.appendTail(replaceResult);
1273 return count;
1274 }
1275
1276
1277
1278
1279
1280
1281
1282 public String getReplaceResult() {
1283 return replaceResult.toString();
1284 }
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295 public boolean matches(String text, Object regexp) {
1296 if (regexp instanceof Pattern) {
1297
1298 return caseAwarePattern((Pattern) regexp).matcher(text).find();
1299 }
1300 return dynamicPattern(toAwkString(regexp)).matcher(text).find();
1301 }
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311 public int matchPosition(String s, String ere) {
1312 Matcher matcher = dynamicPattern(ere).matcher(s);
1313 if (matcher.find()) {
1314 int start = matcher.start() + 1;
1315 setRSTART(start);
1316 setRLENGTH(matcher.end() - matcher.start());
1317 return start;
1318 }
1319 setRSTART(0);
1320 setRLENGTH(-1);
1321 return 0;
1322 }
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335 public Enumeration<Object> splitTokenizer(String input, Object separator) {
1336 if (separator instanceof Pattern) {
1337 return new RegexTokenizer(input, caseAwarePattern((Pattern) separator));
1338 }
1339 String fsString = toAwkString(separator);
1340 if (fsString.equals(" ")) {
1341 return new StringTokenizer(input);
1342 }
1343 if (fsString.isEmpty()) {
1344 return new CharacterTokenizer(input);
1345 }
1346 if (fsString.length() == 1) {
1347 char fsChar = fsString.charAt(0);
1348 if (ignoreCase && Character.isLetter(fsChar)) {
1349
1350
1351 return new RegexTokenizer(input, dynamicPattern(fsString));
1352 }
1353 return new SingleCharacterTokenizer(input, fsChar);
1354 }
1355 return new RegexTokenizer(input, dynamicPattern(fsString));
1356 }
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369 public static String prepareReplacement(String awkRepl, boolean backreferences) {
1370 return prepareReplacement(awkRepl, backreferences ? Integer.MAX_VALUE : -1);
1371 }
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386 public static String prepareReplacement(String awkRepl, int maxGroup) {
1387 boolean backreferences = maxGroup >= 0;
1388 if (awkRepl == null) {
1389 return "";
1390 }
1391
1392 if ((awkRepl.indexOf('\\') == -1) && (awkRepl.indexOf('$') == -1) && (awkRepl.indexOf('&') == -1)) {
1393 return awkRepl;
1394 }
1395
1396 StringBuilder javaRepl = new StringBuilder();
1397 for (int i = 0; i < awkRepl.length(); i++) {
1398 char c = awkRepl.charAt(i);
1399
1400 if (c == '\\' && i == awkRepl.length() - 1) {
1401
1402
1403
1404 javaRepl.append(backreferences ? "\\\\" : "\\");
1405 continue;
1406 }
1407
1408 if (c == '\\') {
1409 i++;
1410 c = awkRepl.charAt(i);
1411 if (c == '&') {
1412 javaRepl.append('&');
1413 continue;
1414 } else if (c == '\\') {
1415 javaRepl.append("\\\\");
1416 continue;
1417 } else if (backreferences && Character.isDigit(c)) {
1418 if (c - '0' <= maxGroup) {
1419 javaRepl.append('$').append(c);
1420 }
1421
1422
1423 continue;
1424 }
1425
1426 javaRepl.append('\\');
1427 }
1428
1429 if (c == '$') {
1430 javaRepl.append("\\$");
1431 } else if (c == '&') {
1432 javaRepl.append("$0");
1433 } else {
1434 javaRepl.append(c);
1435 }
1436 }
1437
1438 return javaRepl.toString();
1439 }
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451 public Pattern caseAwarePattern(Pattern pattern) {
1452 if (!ignoreCase || (pattern.flags() & Pattern.CASE_INSENSITIVE) != 0) {
1453 return pattern;
1454 }
1455 if (caseInsensitivePatterns == null) {
1456 caseInsensitivePatterns = new IdentityHashMap<Pattern, Pattern>();
1457 }
1458 return caseInsensitivePatterns
1459 .computeIfAbsent(
1460 pattern,
1461 base -> Pattern.compile(base.pattern(), base.flags() | Pattern.CASE_INSENSITIVE));
1462 }
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479 public Pattern dynamicPattern(String ere) {
1480 if (dynamicPatterns == null) {
1481 dynamicPatterns = new HashMap<String, Pattern>();
1482 dynamicPatternsIgnoreCase = new HashMap<String, Pattern>();
1483 }
1484 Map<String, Pattern> cache = ignoreCase ? dynamicPatternsIgnoreCase : dynamicPatterns;
1485 Pattern pattern = cache.get(ere);
1486 if (pattern == null) {
1487 if (cache.size() >= DYNAMIC_PATTERN_CACHE_LIMIT) {
1488 cache.clear();
1489 }
1490 pattern = Pattern.compile(ere, regexpFlags());
1491 cache.put(ere, pattern);
1492 }
1493 return pattern;
1494 }
1495
1496
1497
1498
1499
1500
1501 public Object getRSVar() {
1502 return rs;
1503 }
1504
1505
1506
1507
1508
1509
1510 public String getRSString() {
1511 return rs;
1512 }
1513
1514
1515
1516
1517
1518
1519 public void setRS(Object value) {
1520 this.rs = value == null ? "" : value.toString();
1521 applyRS(this.rs);
1522 }
1523
1524
1525
1526
1527
1528
1529 public Object getOFSVar() {
1530 return ofs;
1531 }
1532
1533
1534
1535
1536
1537
1538 public String getOFSString() {
1539 return ofs;
1540 }
1541
1542
1543
1544
1545
1546
1547 public void setOFS(Object value) {
1548 this.ofs = value == null ? "" : value.toString();
1549 }
1550
1551
1552
1553
1554
1555
1556 public Object getORSVar() {
1557 return ors;
1558 }
1559
1560
1561
1562
1563
1564
1565 public String getORSString() {
1566 return ors;
1567 }
1568
1569
1570
1571
1572
1573
1574 public void setORS(Object value) {
1575 this.ors = value == null ? "" : value.toString();
1576 }
1577
1578
1579
1580
1581
1582
1583 public Integer getRSTART() {
1584 return Integer.valueOf(rstart);
1585 }
1586
1587
1588
1589
1590
1591
1592 public void setRSTART(Object value) {
1593 this.rstart = (int) toLong(value);
1594 }
1595
1596
1597
1598
1599
1600
1601 public Integer getRLENGTH() {
1602 return Integer.valueOf(rlength);
1603 }
1604
1605
1606
1607
1608
1609
1610 public void setRLENGTH(Object value) {
1611 this.rlength = (int) toLong(value);
1612 }
1613
1614
1615
1616
1617
1618
1619 public Object getFILENAME() {
1620 return filename == null ? "" : filename;
1621 }
1622
1623
1624
1625
1626
1627
1628 public void setFILENAMEViaJrt(Object name) {
1629 this.filename = normalizeRecordValue(name);
1630 }
1631
1632
1633
1634
1635
1636
1637 public Object getERRNO() {
1638 return errno == null ? "" : errno;
1639 }
1640
1641
1642
1643
1644
1645
1646 public void setERRNO(Object value) {
1647 this.errno = normalizeRecordValue(value);
1648 }
1649
1650
1651
1652
1653
1654
1655 public Object getARGIND() {
1656 return argind == null ? ZERO : argind;
1657 }
1658
1659
1660
1661
1662
1663
1664 public void setARGIND(Object value) {
1665 this.argind = normalizeRecordValue(value);
1666 }
1667
1668
1669
1670
1671
1672
1673 public Object getSUBSEPVar() {
1674 return subsep;
1675 }
1676
1677
1678
1679
1680
1681
1682 public String getSUBSEPString() {
1683 return subsep;
1684 }
1685
1686
1687
1688
1689
1690
1691 public void setSUBSEP(Object value) {
1692 this.subsep = value == null ? "" : value.toString();
1693 }
1694
1695
1696
1697
1698
1699
1700 public Object getCONVFMTVar() {
1701 return convfmt;
1702 }
1703
1704
1705
1706
1707
1708
1709 public String getCONVFMTString() {
1710 return convfmt;
1711 }
1712
1713
1714
1715
1716
1717
1718 public void setCONVFMT(Object value) {
1719 this.convfmt = value == null ? "" : value.toString();
1720 }
1721
1722
1723
1724
1725
1726
1727 public String getOFMTString() {
1728 return ofmt;
1729 }
1730
1731
1732
1733
1734
1735
1736 public void setOFMT(Object value) {
1737 this.ofmt = value == null ? "" : value.toString();
1738 }
1739
1740
1741
1742
1743
1744
1745 public Object getARGCVar() {
1746 return vm.getARGC();
1747 }
1748
1749
1750
1751
1752
1753
1754 public void setARGC(Object value) {
1755 vm.assignVariable("ARGC", value);
1756 }
1757
1758
1759
1760
1761
1762
1763
1764
1765 public void setInputLine(Object inputLineParam) {
1766 Object inputValue = normalizeRecordValue(inputLineParam);
1767 this.inputLine = inputValue;
1768 recordState = new RecordState(inputValue, null);
1769 }
1770
1771
1772
1773
1774
1775
1776
1777 public Object toInputScalar(Object value) {
1778 if (value instanceof String) {
1779 return new StrNum((String) value, decimalSeparator);
1780 }
1781 if (value instanceof StrNum) {
1782 return value;
1783 }
1784 if (value == null || value instanceof UninitializedObject) {
1785 return new StrNum("", decimalSeparator);
1786 }
1787 return new StrNum(value.toString(), decimalSeparator);
1788 }
1789
1790 private static Object normalizeRecordValue(Object value) {
1791 if (value == null || value instanceof UninitializedObject) {
1792 return "";
1793 }
1794 return value;
1795 }
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807 public boolean consumeInput(final InputSource source) throws IOException {
1808 Objects.requireNonNull(source, "source");
1809 activeSource = source;
1810 if (!source.nextRecord()) {
1811 return false;
1812 }
1813
1814 bindConsumedRecord(source);
1815 return true;
1816 }
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835 public boolean consumeCurrentFileInput(final InputSource source) throws IOException {
1836 Objects.requireNonNull(source, "source");
1837 if (!(source instanceof StreamInputSource)) {
1838
1839 return consumeInput(source);
1840 }
1841 StreamInputSource streamSource = (StreamInputSource) source;
1842 throwIfCurrentFileUnopened(streamSource);
1843 activeSource = source;
1844 if (!streamSource.nextRecordInCurrentFile()) {
1845 return false;
1846 }
1847 bindConsumedRecord(source);
1848 return true;
1849 }
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865 public Object consumeCurrentFileInputToTarget(final InputSource source) throws IOException {
1866 Objects.requireNonNull(source, "source");
1867 if (!(source instanceof StreamInputSource)) {
1868
1869 return consumeInputToTarget(source);
1870 }
1871 StreamInputSource streamSource = (StreamInputSource) source;
1872 throwIfCurrentFileUnopened(streamSource);
1873 activeSource = source;
1874 materializeCurrentRecord();
1875 if (!streamSource.nextRecordInCurrentFile()) {
1876 return null;
1877 }
1878
1879 RecordState inputState = new RecordState(source);
1880 this.nr++;
1881 if (countsTowardFNR(source)) {
1882 this.fnr++;
1883 }
1884 return new StrNum(inputState.getRecordText(), decimalSeparator);
1885 }
1886
1887
1888
1889
1890
1891
1892
1893
1894 private void throwIfCurrentFileUnopened(StreamInputSource streamSource) {
1895 String openError = streamSource.getCurrentFileOpenError();
1896 if (openError != null) {
1897 throw new AwkRuntimeException(
1898 "cannot open file `" + toAwkString(getFILENAME()) + "' for reading: " + openError);
1899 }
1900 }
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916 public boolean advanceToNextFile(final InputSource source) throws IOException {
1917 Objects.requireNonNull(source, "source");
1918 if (source instanceof StreamInputSource) {
1919 return ((StreamInputSource) source).advanceToNextFile();
1920 }
1921
1922 if (syntheticFilePresented) {
1923 return false;
1924 }
1925 syntheticFilePresented = true;
1926 return true;
1927 }
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937 public boolean hasPendingInputFileError(InputSource source) {
1938 return source instanceof StreamInputSource
1939 && ((StreamInputSource) source).getCurrentFileOpenError() != null;
1940 }
1941
1942
1943
1944
1945
1946
1947
1948 private void bindConsumedRecord(InputSource source) {
1949 inputLine = null;
1950 recordState = new RecordState(source);
1951
1952 this.nr++;
1953 if (countsTowardFNR(source)) {
1954 this.fnr++;
1955 }
1956 }
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969 private static boolean countsTowardFNR(InputSource source) {
1970 return source instanceof StreamInputSource || source.isFromFilenameList();
1971 }
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984 public Object consumeInputToTarget(final InputSource source) throws IOException {
1985 Objects.requireNonNull(source, "source");
1986 activeSource = source;
1987 materializeCurrentRecord();
1988 if (!source.nextRecord()) {
1989 return null;
1990 }
1991
1992 RecordState inputState = new RecordState(source);
1993 this.nr++;
1994 if (countsTowardFNR(source)) {
1995 this.fnr++;
1996 }
1997 return new StrNum(inputState.getRecordText(), decimalSeparator);
1998 }
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009 public boolean consumeInputForEval(InputSource source) throws IOException {
2010 return consumeInput(source);
2011 }
2012
2013
2014
2015
2016
2017
2018
2019 protected void initializeInputFields(String record, List<String> preFields) {
2020 recordState = new RecordState(toInputScalar(record), preFields);
2021 }
2022
2023
2024
2025
2026
2027 public void jrtParseFields() {
2028 RecordState state = ensureRecordStateForTextMutation();
2029 state.ensureFieldsMaterialized();
2030 }
2031
2032
2033
2034
2035 public boolean hasInputFields() {
2036 return recordState != null;
2037 }
2038
2039
2040
2041
2042
2043
2044
2045
2046 public void jrtSetNF(Object nfObj) {
2047 int nf = (int) toDouble(nfObj);
2048 if (nf < 0) {
2049 nf = 0;
2050 }
2051
2052 RecordState state = ensureRecordStateForFieldMutation();
2053 int currentNF = state.getNF();
2054
2055 if (nf < currentNF) {
2056 for (int i = currentNF; i > nf; i--) {
2057 state.removeField(i - 1);
2058 }
2059 } else if (nf > currentNF) {
2060 for (int i = currentNF + 1; i <= nf; i++) {
2061 state.addField("");
2062 }
2063 }
2064
2065 state.markRecordTextDirty();
2066 }
2067
2068
2069
2070
2071
2072
2073
2074 public Object jrtGetInputField(Object fieldnumObj) {
2075 return jrtGetInputField(parseFieldNumber(fieldnumObj));
2076 }
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086 public Object jrtGetInputField(long fieldnum) {
2087 if (fieldnum < 0 || fieldnum > Integer.MAX_VALUE) {
2088 throw new AwkRuntimeException("Field $(" + Long.valueOf(fieldnum) + ") is incorrect.");
2089 }
2090 if (recordState == null) {
2091 return BLANK;
2092 }
2093 return recordState.getField((int) fieldnum);
2094 }
2095
2096
2097
2098
2099
2100
2101
2102
2103 public String jrtSetInputField(Object valueObj, long fieldNum) {
2104 if (fieldNum > Integer.MAX_VALUE) {
2105 throw new AwkRuntimeException("Field $(" + Long.valueOf(fieldNum) + ") is incorrect.");
2106 }
2107 String value = valueObj == null ? "" : valueObj.toString();
2108 int fieldIndex = (int) fieldNum;
2109 RecordState state = ensureRecordStateForFieldMutation();
2110 if (valueObj instanceof UninitializedObject) {
2111 if (fieldIndex <= state.getNF()) {
2112 state.setField(fieldIndex - 1, "");
2113 }
2114 } else {
2115 while (state.getNF() < fieldIndex) {
2116 state.addField(BLANK);
2117 }
2118 state.setField(fieldIndex - 1, valueObj);
2119 }
2120 state.markRecordTextDirty();
2121 return value;
2122 }
2123
2124 protected void rebuildDollarZeroFromFields() {
2125 if (recordState != null) {
2126 recordState.markRecordTextDirty();
2127 inputLine = recordState.getField(0);
2128 }
2129 }
2130
2131 private void materializeCurrentRecord() {
2132 if (recordState != null) {
2133 recordState.materialize();
2134 }
2135 }
2136
2137 private RecordState ensureRecordStateForTextMutation() {
2138 if (recordState == null) {
2139 recordState = new RecordState(inputLine, null);
2140 }
2141 return recordState;
2142 }
2143
2144 private RecordState ensureRecordStateForFieldMutation() {
2145 RecordState state = ensureRecordStateForTextMutation();
2146 state.ensureFieldsMaterialized();
2147 return state;
2148 }
2149
2150 private List<Object> sanitizeFields(List<String> rawFields) {
2151 List<Object> copy = new ArrayList<Object>(rawFields.size());
2152 for (String field : rawFields) {
2153 String value = field == null ? "" : field;
2154 copy.add(new StrNum(value, decimalSeparator));
2155 }
2156 return copy;
2157 }
2158
2159 private List<Object> splitRecordText(String recordText, String fieldSeparator) {
2160 List<Object> fields = new ArrayList<Object>();
2161 if (recordText == null || recordText.isEmpty()) {
2162 return fields;
2163 }
2164
2165 Enumeration<Object> tokenizer = splitTokenizer(recordText, fieldSeparator);
2166
2167 while (tokenizer.hasMoreElements()) {
2168 fields.add(new StrNum((String) tokenizer.nextElement(), decimalSeparator));
2169 }
2170 return fields;
2171 }
2172
2173 private static String joinFieldsWithLiteralSeparator(List<Object> fields, String separator) {
2174 StringBuilder sb = new StringBuilder();
2175 for (int i = 0; i < fields.size(); i++) {
2176 if (i > 0) {
2177 sb.append(separator);
2178 }
2179 Object field = fields.get(i);
2180 sb.append(field == null ? "" : field.toString());
2181 }
2182 return sb.toString();
2183 }
2184
2185 private String rebuildRecordTextFromFields(List<Object> fields) {
2186 return joinFieldsWithLiteralSeparator(fields, ofs);
2187 }
2188
2189 private final class RecordState {
2190
2191 private final String fieldSeparatorAtRead;
2192 private final InputSource source;
2193 private String recordText;
2194 private Object recordScalar;
2195 private List<Object> fields;
2196 private boolean recordTextAvailable;
2197 private boolean fieldsAvailable;
2198 private boolean recordTextDirty;
2199 private boolean fieldsDirty;
2200 private boolean recordTextLoadedFromSource;
2201 private boolean fieldsLoadedFromSource;
2202
2203 private RecordState(InputSource source) {
2204 this(null, null, source);
2205 }
2206
2207 private RecordState(Object recordValue, List<String> rawFields) {
2208 this(recordValue, rawFields, null);
2209 }
2210
2211 private RecordState(Object recordValue, List<String> rawFields, InputSource source) {
2212 this.fieldSeparatorAtRead = fs;
2213 this.source = source;
2214 if (recordValue != null) {
2215 this.recordScalar = normalizeRecordValue(recordValue);
2216 this.recordText = this.recordScalar.toString();
2217 this.recordTextAvailable = true;
2218 } else if (rawFields == null && source == null) {
2219 this.recordScalar = "";
2220 this.recordText = "";
2221 this.recordTextAvailable = true;
2222 }
2223 if (rawFields != null) {
2224 this.fields = sanitizeFields(rawFields);
2225 this.fieldsAvailable = true;
2226 this.fieldsDirty = false;
2227 } else {
2228 this.fieldsAvailable = false;
2229 this.fieldsDirty = true;
2230 }
2231 this.recordTextDirty = false;
2232 }
2233
2234 private void ensureFieldsMaterialized() {
2235 if (fieldsAvailable && !fieldsDirty) {
2236 return;
2237 }
2238 if (!recordTextDirty) {
2239 loadFieldsFromSource();
2240 if (fieldsAvailable && !fieldsDirty) {
2241 return;
2242 }
2243 }
2244 fields = splitRecordText(getRecordText(), fieldSeparatorAtRead);
2245 fieldsAvailable = true;
2246 fieldsDirty = false;
2247 }
2248
2249 private String getRecordText() {
2250 if (!recordTextAvailable || recordTextDirty) {
2251 if (recordTextDirty) {
2252 recordText = rebuildRecordTextFromFields(fields);
2253 recordScalar = recordText;
2254 } else {
2255 loadRecordTextFromSource();
2256 if (!recordTextAvailable) {
2257 loadFieldsFromSource();
2258 if (!fieldsAvailable) {
2259 throw new IllegalStateException(
2260 "InputSource must provide record text, fields, or both after nextRecord()");
2261 }
2262 recordText = joinFieldsWithLiteralSeparator(fields, fieldSeparatorAtRead);
2263 recordScalar = new StrNum(recordText, decimalSeparator);
2264 }
2265 }
2266 recordTextAvailable = true;
2267 recordTextDirty = false;
2268 }
2269 return recordText;
2270 }
2271
2272 private int getNF() {
2273 ensureFieldsMaterialized();
2274 return fields.size();
2275 }
2276
2277 private Object getField(int fieldIndex) {
2278 if (fieldIndex == 0) {
2279 String value = getRecordText();
2280 if (recordScalar == null) {
2281 recordScalar = value;
2282 }
2283 return recordScalar;
2284 }
2285 ensureFieldsMaterialized();
2286 int zeroBasedIndex = fieldIndex - 1;
2287 if (zeroBasedIndex < 0 || zeroBasedIndex >= fields.size()) {
2288 return BLANK;
2289 }
2290 return fields.get(zeroBasedIndex);
2291 }
2292
2293 private void setField(int zeroBasedIndex, Object value) {
2294 ensureFieldsMaterialized();
2295 fields.set(zeroBasedIndex, normalizeFieldValue(value));
2296 markRecordTextDirty();
2297 }
2298
2299 private void addField(Object value) {
2300 ensureFieldsMaterialized();
2301 fields.add(normalizeFieldValue(value));
2302 markRecordTextDirty();
2303 }
2304
2305 private Object normalizeFieldValue(Object value) {
2306 if (value == null) {
2307 return "";
2308 }
2309 return value;
2310 }
2311
2312 private void removeField(int zeroBasedIndex) {
2313 ensureFieldsMaterialized();
2314 fields.remove(zeroBasedIndex);
2315 markRecordTextDirty();
2316 }
2317
2318 private void markRecordTextDirty() {
2319 recordTextDirty = true;
2320 recordTextAvailable = fieldsAvailable;
2321 recordScalar = null;
2322 }
2323
2324 private void materialize() {
2325 getRecordText();
2326 ensureFieldsMaterialized();
2327 }
2328
2329 private void loadRecordTextFromSource() {
2330 if (source == null || recordTextLoadedFromSource) {
2331 return;
2332 }
2333 recordText = source.getRecordText();
2334 recordTextAvailable = recordText != null;
2335 if (recordTextAvailable) {
2336 recordScalar = new StrNum(recordText, decimalSeparator);
2337 }
2338 recordTextLoadedFromSource = true;
2339 }
2340
2341 private void loadFieldsFromSource() {
2342 if (source == null || fieldsLoadedFromSource) {
2343 return;
2344 }
2345 List<String> rawFields = source.getFields();
2346 fieldsLoadedFromSource = true;
2347 if (rawFields != null) {
2348 fields = sanitizeFields(rawFields);
2349 fieldsAvailable = true;
2350 fieldsDirty = false;
2351 }
2352 }
2353 }
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363 public Integer jrtConsumeFileInputForGetline(String fileNameParam) {
2364 try {
2365 if (jrtConsumeFileInput(fileNameParam)) {
2366 return ONE;
2367 } else {
2368 jrtInputString = "";
2369 return ZERO;
2370 }
2371 } catch (IOException ioe) {
2372 jrtInputString = "";
2373 return MINUS_ONE;
2374 }
2375 }
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385 public Integer jrtConsumeCommandInputForGetline(String cmdString) {
2386 try {
2387 if (jrtConsumeCommandInput(cmdString)) {
2388 return ONE;
2389 } else {
2390 jrtInputString = "";
2391 return ZERO;
2392 }
2393 } catch (IOException ioe) {
2394 jrtInputString = "";
2395 return MINUS_ONE;
2396 }
2397 }
2398
2399
2400
2401
2402
2403
2404 public String jrtGetInputString() {
2405 return jrtInputString;
2406 }
2407
2408
2409
2410
2411
2412
2413
2414
2415 public Map<String, PrintStream> getOutputFiles() {
2416 Map<String, PrintStream> outputFiles = new HashMap<String, PrintStream>();
2417 for (Map.Entry<String, FileOutputState> entry : getIoState().fileOutputs.entrySet()) {
2418 outputFiles.put(entry.getKey(), entry.getValue().sink.getPrintStream());
2419 }
2420 return outputFiles;
2421 }
2422
2423
2424
2425
2426
2427
2428
2429
2430 protected AwkSink getFileAwkSink(String fileNameParam, boolean append) {
2431 return getOrCreateFileOutputState(fileNameParam, append).sink;
2432 }
2433
2434
2435
2436
2437
2438
2439
2440 protected AwkSink getPipeAwkSink(String cmd) {
2441 return getOrCreateProcessOutputState(cmd).sink;
2442 }
2443
2444
2445
2446
2447
2448
2449
2450 public void printDefault(Object[] values) throws IOException {
2451 awkSink.print(ofs, ors, ofmt, values);
2452 }
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462 public void printToFile(String fileNameParam, boolean append, Object[] values) throws IOException {
2463 getFileAwkSink(fileNameParam, append).print(ofs, ors, ofmt, values);
2464 }
2465
2466
2467
2468
2469
2470
2471
2472
2473 public void printToProcess(String cmd, Object[] values) throws IOException {
2474 AwkSink sink = getPipeAwkSink(cmd);
2475 sink.print(ofs, ors, ofmt, values);
2476 sink.flush();
2477 }
2478
2479
2480
2481
2482
2483
2484
2485
2486 public void printfDefault(String format, Object[] values) throws IOException {
2487 awkSink.printf(ofs, ors, ofmt, format, values);
2488 }
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499 public void printfToFile(String fileNameParam, boolean append, String format, Object[] values)
2500 throws IOException {
2501 AwkSink sink = getFileAwkSink(fileNameParam, append);
2502 sink.printf(ofs, ors, ofmt, format, values);
2503 }
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513 public void printfToProcess(String cmd, String format, Object[] values) throws IOException {
2514 AwkSink sink = getPipeAwkSink(cmd);
2515 sink.printf(ofs, ors, ofmt, format, values);
2516 sink.flush();
2517 }
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527 public PrintStream jrtGetPrintStream(String fileNameParam, boolean append) {
2528 return getFileAwkSink(fileNameParam, append).getPrintStream();
2529 }
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540 public boolean jrtConsumeFileInput(String fileNameParam) throws IOException {
2541 Map<String, PartitioningReader> fileReaders = getIoState().fileReaders;
2542 PartitioningReader pr = fileReaders.get(fileNameParam);
2543 if (pr == null) {
2544 try {
2545 pr = new PartitioningReader(
2546 new InputStreamReader(new FileInputStream(fileNameParam), StandardCharsets.UTF_8),
2547 this.rs);
2548 fileReaders.put(fileNameParam, pr);
2549 this.filename = fileNameParam;
2550 } catch (IOException ioe) {
2551 fileReaders.remove(fileNameParam);
2552 throw ioe;
2553 }
2554 }
2555
2556 String recordText = pr.readRecord();
2557 if (recordText == null) {
2558 return false;
2559 } else {
2560 jrtInputString = recordText;
2561 inputLine = toInputScalar(recordText);
2562 recordState = new RecordState(inputLine, null);
2563 this.nr++;
2564 return true;
2565 }
2566 }
2567
2568 private static Process spawnProcess(String cmd) throws IOException {
2569 Process p;
2570
2571 if (IS_WINDOWS) {
2572
2573 ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", cmd);
2574 p = pb.start();
2575 } else {
2576
2577 ProcessBuilder pb = new ProcessBuilder("/bin/sh", "-c", cmd);
2578 p = pb.start();
2579 }
2580
2581 return p;
2582 }
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593 public boolean jrtConsumeCommandInput(String cmd) throws IOException {
2594 CommandInputState commandInput = getOrCreateCommandInputState(cmd);
2595 String recordText = commandInput.reader.readRecord();
2596 if (recordText == null) {
2597 return false;
2598 } else {
2599 jrtInputString = recordText;
2600 inputLine = toInputScalar(recordText);
2601 recordState = new RecordState(inputLine, null);
2602 this.nr++;
2603 return true;
2604 }
2605 }
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616 public PrintStream jrtSpawnForOutput(String cmd) {
2617 return getPipeAwkSink(cmd).getPrintStream();
2618 }
2619
2620 private FileOutputState getOrCreateFileOutputState(String fileNameParam, boolean append) {
2621 IoState state = getIoState();
2622 FileOutputState outputState = state.fileOutputs.get(fileNameParam);
2623 if (outputState == null) {
2624 outputState = createFileOutputState(fileNameParam, append);
2625 state.fileOutputs.put(fileNameParam, outputState);
2626 }
2627 return outputState;
2628 }
2629
2630 private FileOutputState createFileOutputState(String fileNameParam, boolean append) {
2631 try {
2632 PrintStream printStream = new PrintStream(
2633 new FileOutputStream(fileNameParam, append),
2634 true,
2635 StandardCharsets.UTF_8.name());
2636 return new FileOutputState(new OutputStreamAwkSink(printStream, locale));
2637 } catch (IOException ioe) {
2638 throw new AwkRuntimeException("Cannot open " + fileNameParam + " for writing: " + ioe);
2639 }
2640 }
2641
2642 private CommandInputState getOrCreateCommandInputState(String cmd) throws IOException {
2643 IoState state = getIoState();
2644 CommandInputState commandInput = state.commandInputs.get(cmd);
2645 if (commandInput == null) {
2646 commandInput = createCommandInputState(cmd);
2647 state.commandInputs.put(cmd, commandInput);
2648 this.filename = "";
2649 }
2650 return commandInput;
2651 }
2652
2653 private CommandInputState createCommandInputState(String cmd) throws IOException {
2654 Process process = null;
2655 Thread errorPump = null;
2656 try {
2657 process = spawnProcess(cmd);
2658 process.getOutputStream().close();
2659 errorPump = DataPump.dumpAndReturnThread(cmd + " stderr", process.getErrorStream(), error);
2660 PartitioningReader reader = new PartitioningReader(
2661 new InputStreamReader(process.getInputStream(), StandardCharsets.UTF_8),
2662 this.rs);
2663 return new CommandInputState(process, reader, errorPump);
2664 } catch (IOException ioe) {
2665 if (process != null) {
2666 process.destroy();
2667 }
2668 joinDataPump(errorPump);
2669 throw ioe;
2670 }
2671 }
2672
2673 private ProcessOutputState getOrCreateProcessOutputState(String cmd) {
2674 IoState state = getIoState();
2675 ProcessOutputState outputState = state.processOutputs.get(cmd);
2676 if (outputState == null) {
2677 outputState = createProcessOutputState(cmd);
2678 state.processOutputs.put(cmd, outputState);
2679 }
2680 return outputState;
2681 }
2682
2683 private ProcessOutputState createProcessOutputState(String cmd) {
2684 Process process = null;
2685 Thread stderrPump = null;
2686 Thread stdoutPump = null;
2687 PrintStream processOutput = null;
2688 try {
2689 processOutput = awkSink.getPrintStream();
2690 process = spawnProcess(cmd);
2691 stderrPump = DataPump.dumpAndReturnThread(cmd + " stderr", process.getErrorStream(), error);
2692 stdoutPump = DataPump.dumpAndReturnThread(cmd + " stdout", process.getInputStream(), processOutput);
2693 PrintStream processInput = new PrintStream(process.getOutputStream(), true, StandardCharsets.UTF_8.name());
2694 return new ProcessOutputState(
2695 process,
2696 new OutputStreamAwkSink(processInput, locale),
2697 processOutput,
2698 stdoutPump,
2699 stderrPump);
2700 } catch (IOException ioe) {
2701 if (process != null) {
2702 process.destroy();
2703 }
2704 joinDataPump(stdoutPump);
2705 joinDataPump(stderrPump);
2706 throw new AwkRuntimeException("Can't spawn " + cmd + ": " + ioe);
2707 }
2708 }
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725 public Integer jrtClose(String fileNameParam) {
2726 boolean b1 = jrtCloseFileReader(fileNameParam);
2727 boolean b2 = jrtCloseCommandReader(fileNameParam);
2728 boolean b3 = jrtCloseOutputFile(fileNameParam);
2729 boolean b4 = jrtCloseOutputStream(fileNameParam);
2730
2731 return (b1 || b2 || b3 || b4) ? ZERO : MINUS_ONE;
2732 }
2733
2734
2735
2736
2737
2738
2739 public void jrtCloseAll() {
2740 IoState state = ioState;
2741 if (state == null) {
2742 return;
2743 }
2744 Set<String> set = new HashSet<String>();
2745 for (String s : state.fileReaders.keySet()) {
2746 set.add(s);
2747 }
2748 for (String s : state.commandInputs.keySet()) {
2749 set.add(s);
2750 }
2751 for (String s : state.fileOutputs.keySet()) {
2752 set.add(s);
2753 }
2754 for (String s : state.processOutputs.keySet()) {
2755 set.add(s);
2756 }
2757 for (String s : set) {
2758 jrtClose(s);
2759 }
2760 }
2761
2762 private boolean jrtCloseOutputFile(String fileNameParam) {
2763 IoState state = ioState;
2764 if (state == null) {
2765 return false;
2766 }
2767 FileOutputState outputState = state.fileOutputs.remove(fileNameParam);
2768 if (outputState != null) {
2769 outputState.sink.getPrintStream().close();
2770 }
2771 return outputState != null;
2772 }
2773
2774 private boolean jrtCloseOutputStream(String cmd) {
2775 IoState state = ioState;
2776 if (state == null) {
2777 return false;
2778 }
2779 ProcessOutputState outputState = state.processOutputs.remove(cmd);
2780 if (outputState == null) {
2781 return false;
2782 }
2783 outputState.sink.getPrintStream().close();
2784 try {
2785
2786
2787 outputState.process.waitFor();
2788 outputState.process.exitValue();
2789 } catch (InterruptedException ie) {
2790 Thread.currentThread().interrupt();
2791 outputState.process.destroyForcibly();
2792 throw new AwkRuntimeException(
2793 "Caught exception while waiting for process exit: " + ie);
2794 } finally {
2795 joinDataPump(outputState.stdoutPump);
2796 joinDataPump(outputState.stderrPump);
2797 outputState.processOutput.flush();
2798 error.flush();
2799 }
2800 return true;
2801 }
2802
2803 private boolean jrtCloseFileReader(String fileNameParam) {
2804 IoState state = ioState;
2805 if (state == null) {
2806 return false;
2807 }
2808 PartitioningReader pr = state.fileReaders.get(fileNameParam);
2809 if (pr == null) {
2810 return false;
2811 }
2812 state.fileReaders.remove(fileNameParam);
2813 try {
2814 pr.close();
2815 return true;
2816 } catch (IOException ioe) {
2817 return false;
2818 }
2819 }
2820
2821 private boolean jrtCloseCommandReader(String cmd) {
2822 IoState state = ioState;
2823 if (state == null) {
2824 return false;
2825 }
2826 CommandInputState commandInput = state.commandInputs.remove(cmd);
2827 if (commandInput == null) {
2828 return false;
2829 }
2830 try {
2831 commandInput.reader.close();
2832 try {
2833
2834
2835 commandInput.process.waitFor();
2836 commandInput.process.exitValue();
2837 } catch (InterruptedException ie) {
2838 Thread.currentThread().interrupt();
2839 commandInput.process.destroyForcibly();
2840 throw new AwkRuntimeException(
2841 "Caught exception while waiting for process exit: " + ie);
2842 }
2843 return true;
2844 } catch (IOException ioe) {
2845 return false;
2846 } finally {
2847 joinDataPump(commandInput.errorPump);
2848 error.flush();
2849 }
2850 }
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865 public Integer jrtSystem(String cmd) {
2866 try {
2867 PrintStream processOutput = awkSink.getPrintStream();
2868 Process p = spawnProcess(cmd);
2869
2870 p.getOutputStream().close();
2871 Thread errorPump = DataPump.dumpAndReturnThread(cmd + " stderr", p.getErrorStream(), error);
2872 Thread outputPump = DataPump.dumpAndReturnThread(cmd + " stdout", p.getInputStream(), processOutput);
2873 boolean interrupted = false;
2874 int retcode;
2875 while (true) {
2876 try {
2877 retcode = p.waitFor();
2878 break;
2879 } catch (InterruptedException ie) {
2880
2881 interrupted = true;
2882 }
2883 }
2884 joinDataPump(outputPump);
2885 joinDataPump(errorPump);
2886 processOutput.flush();
2887 error.flush();
2888 if (interrupted) {
2889 Thread.currentThread().interrupt();
2890 }
2891 return Integer.valueOf(retcode);
2892 } catch (IOException ioe) {
2893 return MINUS_ONE;
2894 }
2895 }
2896
2897 private static void joinDataPump(Thread pump) {
2898 if (pump == null) {
2899 return;
2900 }
2901 boolean interrupted = false;
2902 while (true) {
2903 try {
2904 pump.join();
2905 break;
2906 } catch (InterruptedException ie) {
2907 interrupted = true;
2908 }
2909 }
2910 if (interrupted) {
2911 Thread.currentThread().interrupt();
2912 }
2913 }
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926 public static String sprintfNoCatch(Locale locale, String fmtArg, Object... arr) throws IllegalFormatException {
2927 return String.format(locale, fmtArg, arr);
2928 }
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939 public static void printfNoCatch(Locale locale, String fmtArg, Object... arr) {
2940 System.out.print(sprintfNoCatch(locale, fmtArg, arr));
2941 }
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953 public static void printfNoCatch(PrintStream ps, Locale locale, String fmtArg, Object... arr) {
2954 ps.print(sprintfNoCatch(locale, fmtArg, arr));
2955 }
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966 public static String substr(Object startposObj, String str) {
2967 int startpos = (int) toDouble(startposObj);
2968 if (startpos <= 0) {
2969 throw new AwkRuntimeException("2nd arg to substr must be a positive integer");
2970 }
2971 if (startpos > str.length()) {
2972 return "";
2973 } else {
2974 return str.substring(startpos - 1);
2975 }
2976 }
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988 public static String substr(Object sizeObj, Object startposObj, String str) {
2989 int startpos = (int) toDouble(startposObj);
2990 if (startpos <= 0) {
2991 throw new AwkRuntimeException("2nd arg to substr must be a positive integer");
2992 }
2993 if (startpos > str.length()) {
2994 return "";
2995 }
2996 int size = (int) toDouble(sizeObj);
2997 if (size < 0) {
2998 throw new AwkRuntimeException("3nd arg to substr must be a non-negative integer");
2999 }
3000 if (startpos + size > str.length()) {
3001 return str.substring(startpos - 1);
3002 } else {
3003 return str.substring(startpos - 1, startpos + size - 1);
3004 }
3005 }
3006
3007
3008
3009
3010
3011
3012
3013
3014 public static int timeSeed() {
3015 long l = new Date().getTime();
3016 long l2 = l % (1000 * 60 * 60 * 24);
3017 int seed = (int) l2;
3018 return seed;
3019 }
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029 public static BSDRandom newRandom(int seed) {
3030 return new BSDRandom(seed);
3031 }
3032
3033
3034
3035
3036
3037
3038
3039
3040 public void applyRS(Object rsObj) {
3041 if (activeSource instanceof StreamInputSource) {
3042 ((StreamInputSource) activeSource).setRecordSeparator(rsObj.toString());
3043 }
3044 }
3045 }