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.Closeable;
26 import java.io.File;
27 import java.io.FileInputStream;
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.io.InputStreamReader;
31 import java.nio.charset.StandardCharsets;
32 import java.util.List;
33 import java.util.Map;
34 import java.util.Objects;
35
36 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59 public class StreamInputSource implements InputSource, Closeable {
60
61 private final InputStream defaultInput;
62 private final VariableManager vm;
63 private final JRT jrt;
64
65
66 private Map<Object, Object> arglistMap;
67 private int arglistIdx;
68 private int arglistMaxKey;
69 private boolean hasFilenames;
70
71
72 private PartitioningReader partitioningReader;
73 private boolean currentReaderIsDefaultInput;
74 private boolean currentFromFilenameList;
75 private String currentRecord;
76 private boolean currentReaderExhausted;
77
78
79 private String currentFileOpenError;
80 private boolean currentPresentedToLoop;
81 private int lastArgumentIndex;
82
83
84
85
86
87
88
89
90
91
92
93 @SuppressFBWarnings(value = "CT_CONSTRUCTOR_THROW", justification = "Fail-fast argument validation; no security-sensitive state to protect from finalizer attacks")
94 public StreamInputSource(InputStream defaultInput, VariableManager vm, JRT jrt) {
95 this.defaultInput = Objects.requireNonNull(defaultInput, "defaultInput");
96 this.vm = Objects.requireNonNull(vm, "vm");
97 this.jrt = Objects.requireNonNull(jrt, "jrt");
98 }
99
100
101
102
103
104
105
106
107
108
109 InputStream getDefaultInput() {
110 return defaultInput;
111 }
112
113
114 @Override
115 public boolean nextRecord() throws IOException {
116 initializeArgList();
117
118 while (true) {
119 if (partitioningReader == null || currentReaderExhausted) {
120 if (!prepareNextReader()) {
121 return false;
122 }
123 currentReaderExhausted = false;
124 }
125
126 String nextRecord = partitioningReader.readRecord();
127 if (nextRecord != null) {
128 currentRecord = nextRecord;
129 currentFromFilenameList = partitioningReader.fromFilenameList();
130 return true;
131 }
132 if (!partitioningReader.fromFilenameList()) {
133 return false;
134 }
135 currentReaderExhausted = true;
136 }
137 }
138
139
140 @Override
141 public String getRecordText() {
142 return currentRecord;
143 }
144
145
146
147
148
149
150
151 @Override
152 public List<String> getFields() {
153 return null;
154 }
155
156
157 @Override
158 public boolean isFromFilenameList() {
159 return currentFromFilenameList;
160 }
161
162
163
164
165
166
167
168 public void setRecordSeparator(String rs) {
169 if (partitioningReader != null) {
170 partitioningReader.setRecordSeparator(rs);
171 }
172 }
173
174
175
176
177
178
179
180 PartitioningReader getPartitioningReader() {
181 return partitioningReader;
182 }
183
184
185
186
187
188
189
190
191 private void initializeArgList() {
192 if (arglistMap != null) {
193 return;
194 }
195 arglistMap = toArgvMap(vm.getARGV());
196 arglistMaxKey = computeMaxArgvKey();
197 arglistIdx = 1;
198 hasFilenames = detectFilenames();
199 }
200
201 private Map<Object, Object> toArgvMap(Object argv) {
202 if (!(argv instanceof Map)) {
203 throw new IllegalArgumentException("ARGV must be a Map.");
204 }
205 @SuppressWarnings("unchecked")
206 Map<Object, Object> argvMap = (Map<Object, Object>) argv;
207 return argvMap;
208 }
209
210
211
212
213
214
215 private int computeMaxArgvKey() {
216 int max = 0;
217 for (Object key : arglistMap.keySet()) {
218 int idx = (int) JRT.toLong(key);
219 if (idx > max) {
220 max = idx;
221 }
222 }
223 return max;
224 }
225
226
227
228
229
230
231
232 private boolean detectFilenames() {
233 int traversalArgCount = getTraversalArgCount();
234 boolean found = false;
235 for (int i = 1; i < traversalArgCount && !found; i++) {
236 Object argValue = getArgvValue(i);
237 if (argValue == MISSING_ARGV_VALUE) {
238 continue;
239 }
240 String arg = jrt.toAwkString(argValue);
241 if (arg.isEmpty() || arg.indexOf('=') > 0) {
242 continue;
243 }
244 found = true;
245 }
246 return found;
247 }
248
249
250
251
252
253
254 private int getArgCount() {
255 double raw = JRT.toDouble(vm.getARGC());
256 if (raw <= 0) {
257 return 0;
258 }
259 if (raw > Integer.MAX_VALUE) {
260 return Integer.MAX_VALUE;
261 }
262 return (int) raw;
263 }
264
265
266
267
268
269
270
271
272 private int getTraversalArgCount() {
273 int argCount = getArgCount();
274 if (argCount <= 0) {
275 return 0;
276 }
277 return Math.min(argCount, arglistMaxKey + 1);
278 }
279
280
281
282
283
284
285
286
287 private String nextArgument() {
288 int traversalArgCount = getTraversalArgCount();
289 while (arglistIdx < traversalArgCount) {
290 int idx = arglistIdx++;
291 Object argValue = getArgvValue(idx);
292 if (argValue == MISSING_ARGV_VALUE) {
293 continue;
294 }
295 String arg = jrt.toAwkString(argValue);
296 if (!arg.isEmpty()) {
297 lastArgumentIndex = idx;
298 return arg;
299 }
300 }
301 return null;
302 }
303
304 private static final Object MISSING_ARGV_VALUE = new Object();
305
306 private Object getArgvValue(int index) {
307 Long longIndex = Long.valueOf(index);
308 if (arglistMap instanceof AssocArray) {
309 return JRT.containsAwkKey(arglistMap, longIndex) ?
310 JRT.getAssocArrayValue(arglistMap, longIndex) : MISSING_ARGV_VALUE;
311 }
312 if (arglistMap.containsKey(longIndex)) {
313 return arglistMap.get(longIndex);
314 }
315 Integer intIndex = Integer.valueOf(index);
316 if (arglistMap.containsKey(intIndex)) {
317 return arglistMap.get(intIndex);
318 }
319 for (Map.Entry<Object, Object> entry : arglistMap.entrySet()) {
320 Object key = entry.getKey();
321 if (!(key instanceof Number)) {
322 continue;
323 }
324 double numericKey = ((Number) key).doubleValue();
325 if (JRT.isActuallyLong(numericKey) && ((long) Math.rint(numericKey)) == index) {
326 return entry.getValue();
327 }
328 }
329 return MISSING_ARGV_VALUE;
330 }
331
332
333
334
335
336
337
338
339
340
341 private boolean prepareNextReader() throws IOException {
342 boolean ready = false;
343 arglistMaxKey = computeMaxArgvKey();
344 hasFilenames = detectFilenames();
345 while (!ready) {
346 String arg = nextArgument();
347 if (arg == null) {
348
349 hasFilenames = detectFilenames();
350 if (partitioningReader == null && !hasFilenames) {
351 partitioningReader = new PartitioningReader(
352 new InputStreamReader(defaultInput, StandardCharsets.UTF_8),
353 jrt.getRSString());
354 currentReaderIsDefaultInput = true;
355 jrt.setFILENAMEViaJrt(jrt.toInputScalar(""));
356
357
358 jrt.setERRNO("");
359 return true;
360 }
361 closeCurrentReaderIfFileStream();
362 return false;
363 }
364 if (arg.indexOf('=') > 0) {
365 setFilelistVariable(arg);
366
367 arglistMaxKey = computeMaxArgvKey();
368 hasFilenames = detectFilenames();
369 if (partitioningReader == null && !hasFilenames) {
370 partitioningReader = new PartitioningReader(
371 new InputStreamReader(defaultInput, StandardCharsets.UTF_8),
372 jrt.getRSString());
373 currentReaderIsDefaultInput = true;
374 jrt.setFILENAMEViaJrt(jrt.toInputScalar(""));
375
376
377 jrt.setERRNO("");
378 return true;
379 }
380 } else {
381 closeCurrentReaderIfFileStream();
382 partitioningReader = openFileListReader(arg);
383 jrt.setFILENAMEViaJrt(jrt.toInputScalar(arg));
384 jrt.setFNR(0L);
385 jrt.setARGIND(Long.valueOf(lastArgumentIndex));
386
387
388 jrt.setERRNO("");
389 ready = true;
390 }
391 }
392 return true;
393 }
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413 public boolean advanceToNextFile() throws IOException {
414 initializeArgList();
415
416
417
418
419 if (!currentPresentedToLoop
420 && partitioningReader != null
421 && !currentReaderExhausted
422 && currentFileOpenError == null) {
423 currentPresentedToLoop = true;
424 return true;
425 }
426
427 currentFileOpenError = null;
428 arglistMaxKey = computeMaxArgvKey();
429 hasFilenames = detectFilenames();
430 while (true) {
431 String arg = nextArgument();
432 if (arg == null) {
433
434 hasFilenames = detectFilenames();
435 if (partitioningReader == null && !hasFilenames) {
436 return presentDefaultInput();
437 }
438 closeCurrentReaderIfFileStream();
439 return false;
440 }
441 if (arg.indexOf('=') > 0) {
442 setFilelistVariable(arg);
443
444 arglistMaxKey = computeMaxArgvKey();
445 hasFilenames = detectFilenames();
446 if (partitioningReader == null && !hasFilenames) {
447 return presentDefaultInput();
448 }
449 } else {
450 closeCurrentReaderIfFileStream();
451 partitioningReader = null;
452 currentReaderExhausted = false;
453 currentPresentedToLoop = true;
454 jrt.setFILENAMEViaJrt(jrt.toInputScalar(arg));
455 beginFileState(lastArgumentIndex);
456 currentFileOpenError = openCurrentFile(arg);
457 if (currentFileOpenError != null) {
458 jrt.setERRNO(currentFileOpenError);
459 }
460 return true;
461 }
462 }
463 }
464
465
466
467
468
469
470
471
472
473
474 public boolean nextRecordInCurrentFile() throws IOException {
475 if (partitioningReader == null || currentReaderExhausted || currentFileOpenError != null) {
476 return false;
477 }
478 String nextRecord = partitioningReader.readRecord();
479 if (nextRecord == null) {
480 currentReaderExhausted = true;
481 return false;
482 }
483 currentRecord = nextRecord;
484 currentFromFilenameList = partitioningReader.fromFilenameList();
485 return true;
486 }
487
488
489
490
491
492
493
494
495 public String getCurrentFileOpenError() {
496 return currentFileOpenError;
497 }
498
499
500
501
502
503
504
505 private boolean presentDefaultInput() {
506 partitioningReader = new PartitioningReader(
507 new InputStreamReader(defaultInput, StandardCharsets.UTF_8),
508 jrt.getRSString());
509 currentReaderIsDefaultInput = true;
510 currentPresentedToLoop = true;
511 jrt.setFILENAMEViaJrt(jrt.toInputScalar(""));
512 beginFileState(0);
513 return true;
514 }
515
516
517
518
519
520
521
522
523
524 private void beginFileState(int argvIndex) {
525 jrt.setFNR(0L);
526 jrt.setERRNO("");
527 jrt.setARGIND(Long.valueOf(argvIndex));
528 jrt.setInputLine("");
529 }
530
531
532
533
534
535
536
537
538 private String openCurrentFile(String arg) {
539
540
541
542
543
544 if ("-".equals(arg) || JRT.isNullDeviceName(arg)) {
545 try {
546 partitioningReader = openFileListReader(arg);
547 return null;
548 } catch (IOException e) {
549 return e.getMessage();
550 }
551 }
552 File file = new File(arg);
553 if (file.isDirectory()) {
554 return "Is a directory";
555 }
556 if (!file.exists()) {
557 return "No such file or directory";
558 }
559 try {
560 partitioningReader = openFileListReader(arg);
561 return null;
562 } catch (IOException e) {
563 return JRT.describeIoReason(e);
564 }
565 }
566
567
568
569
570
571
572
573
574
575
576
577
578 private PartitioningReader openFileListReader(String arg) throws IOException {
579 boolean isDefaultInput = "-".equals(arg);
580
581
582
583 InputStream stream = isDefaultInput ? defaultInput : new FileInputStream(JRT.toPlatformFileName(arg));
584 PartitioningReader reader = new PartitioningReader(
585 new InputStreamReader(stream, StandardCharsets.UTF_8),
586 jrt.getRSString(),
587 true);
588 currentReaderIsDefaultInput = isDefaultInput;
589 return reader;
590 }
591
592
593
594
595
596
597 private void closeCurrentReaderIfFileStream() {
598 if (partitioningReader != null && partitioningReader.fromFilenameList() && !currentReaderIsDefaultInput) {
599 try {
600 partitioningReader.close();
601 } catch (IOException ignored) {
602
603 }
604 }
605 }
606
607
608
609
610
611
612
613
614
615
616 @Override
617 public void close() throws IOException {
618 closeCurrentReaderIfFileStream();
619 }
620
621
622
623
624
625
626
627 private void setFilelistVariable(String nameValue) {
628 int eqIdx = nameValue.indexOf('=');
629 if (eqIdx == 0) {
630 throw new IllegalArgumentException(
631 "Must have a non-blank variable name in a name=value variable assignment argument.");
632 }
633 String name = nameValue.substring(0, eqIdx);
634 if (name.startsWith("awk::")) {
635 name = name.substring("awk::".length());
636 }
637 String value = nameValue.substring(eqIdx + 1);
638 vm.assignVariable(name, jrt.toInputScalar(value));
639 }
640 }