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