View Javadoc
1   package org.sentrysoftware.jawk.jrt;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2023 Sentry Software
8    * ჻჻჻჻჻჻
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of the
12   * License, or (at your option) any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Lesser Public License for more details.
18   *
19   * You should have received a copy of the GNU General Lesser Public
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
22   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
23   */
24  
25  // There must be NO imports to org.sentrysoftware.jawk.*,
26  // other than org.sentrysoftware.jawk.jrt which occurs by
27  // default. We wish to house all
28  // required runtime classes in jrt.jar,
29  // not have to refer to jawk.jar!
30  
31  import java.io.FileOutputStream;
32  import java.io.FileReader;
33  import java.io.IOException;
34  import java.io.InputStream;
35  import java.io.InputStreamReader;
36  import java.io.PrintStream;
37  import java.util.ArrayList;
38  import java.util.Date;
39  import java.util.Enumeration;
40  import java.util.HashMap;
41  import java.util.HashSet;
42  import java.util.IllegalFormatException;
43  import java.util.List;
44  import java.util.Locale;
45  import java.util.Map;
46  import java.util.Random;
47  import java.util.Set;
48  import java.util.StringTokenizer;
49  import java.util.regex.Matcher;
50  import java.util.regex.Pattern;
51  
52  import org.sentrysoftware.jawk.intermediate.UninitializedObject;
53  import org.sentrysoftware.jawk.util.AwkLogger;
54  import org.slf4j.Logger;
55  
56  /**
57   * The Jawk runtime coordinator.
58   * The JRT services interpreted and compiled Jawk scripts, mainly
59   * for IO and other non-CPU bound tasks. The goal is to house
60   * service functions into a Java-compiled class rather than
61   * to hand-craft service functions in byte-code, or cut-paste
62   * compiled JVM code into the compiled AWK script. Also,
63   * since these functions are non-CPU bound, the need for
64   * inlining is reduced.
65   * <p>
66   * Variable access is achieved through the VariableManager interface.
67   * The constructor requires a VariableManager instance (which, in
68   * this case, is the compiled Jawk class itself).
69   *
70   * <p>
71   * Main services include:
72   * <ul>
73   * <li>File and command output redirection via print(f).
74   * <li>File and command input redirection via getline.
75   * <li>Most built-in AWK functions, such as system(), sprintf(), etc.
76   * <li>Automatic AWK type conversion routines.
77   * <li>IO management for input rule processing.
78   * <li>Random number engine management.
79   * <li>Input field ($0, $1, ...) management.
80   * </ul>
81   *
82   * <p>
83   * All static and non-static service methods should be package-private
84   * to the resultant AWK script class rather than public. However,
85   * the resultant script class is not in the <code>org.sentrysoftware.jawk.jrt</code> package
86   * by default, and the user may reassign the resultant script class
87   * to another package. Therefore, all accessed methods are public.
88   *
89   * @see VariableManager
90   * @author Danny Daglas
91   */
92  public class JRT {
93  
94  	private static final Logger LOG = AwkLogger.getLogger(JRT.class);
95  
96  	private static final boolean IS_WINDOWS = (System.getProperty("os.name").indexOf("Windows") >= 0);
97  
98  	private VariableManager vm;
99  
100 	private Map<String, Process> output_processes = new HashMap<String, Process>();
101 	private Map<String, PrintStream> output_streams = new HashMap<String, PrintStream>();
102 
103 	// Partitioning reader for stdin.
104 	private PartitioningReader partitioningReader = null;
105 	// Current input line ($0).
106 	private String inputLine = null;
107 	// Current input fields ($0, $1, $2, ...).
108 	private List<String> input_fields = new ArrayList<String>(100);
109 	private AssocArray arglist_aa = null;
110 	private int arglist_idx;
111 	private boolean has_filenames = false;
112 	private static final UninitializedObject BLANK = new UninitializedObject();
113 
114 	private static final Integer ONE = Integer.valueOf(1);
115 	private static final Integer ZERO = Integer.valueOf(0);
116 	private static final Integer MINUS_ONE = Integer.valueOf(-1);
117 	private String jrt_input_string;
118 
119 	private Map<String, PartitioningReader> file_readers = new HashMap<String, PartitioningReader>();
120 	private Map<String, PartitioningReader> command_readers = new HashMap<String, PartitioningReader>();
121 	private Map<String, Process> command_processes = new HashMap<String, Process>();
122 	private Map<String, PrintStream> outputFiles = new HashMap<String, PrintStream>();
123 
124 	/**
125 	 * Create a JRT with a VariableManager
126 	 *
127 	 * @param vm The VariableManager to use with this JRT.
128 	 */
129 	public JRT(VariableManager vm) {
130 		this.vm = vm;
131 	}
132 
133 	/**
134 	 * Assign all -v variables.
135 	 *
136 	 * @param initial_var_map A map containing all initial variable
137 	 *   names and their values.
138 	 */
139 	public final void assignInitialVariables(Map<String, Object> initial_var_map) {
140 		assert initial_var_map != null;
141 		for (Map.Entry<String, Object> var : initial_var_map.entrySet()) {
142 			vm.assignVariable(var.getKey(), var.getValue());
143 		}
144 	}
145 
146 	/**
147 	 * Called by AVM/compiled modules to assign local
148 	 * environment variables to an associative array
149 	 * (in this case, to ENVIRON).
150 	 *
151 	 * @param aa The associative array to populate with
152 	 *   environment variables. The module asserts that
153 	 *   the associative array is empty prior to population.
154 	 */
155 	public static void assignEnvironmentVariables(AssocArray aa) {
156 		assert aa.keySet().isEmpty();
157 		Map<String, String> env = System.getenv();
158 		for (Map.Entry<String, String> var : env.entrySet()) {
159 			aa.put(var.getKey(), var.getValue());
160 		}
161 	}
162 
163 	/**
164 	 * Convert Strings, Integers, and Doubles to Strings
165 	 * based on the CONVFMT variable contents.
166 	 *
167 	 * @param o Object to convert.
168 	 * @param convfmt The contents of the CONVFMT variable.
169 	 * @return A String representation of o.
170 	 * @param locale a {@link java.util.Locale} object
171 	 */
172 	public static String toAwkString(Object o, String convfmt, Locale locale) {
173 
174 		if (o instanceof Number) {
175 			// It is a number, some processing is required here
176 			double d = ((Number) o).doubleValue();
177 			if (d == (long) d) {
178 				// If an integer, represent it as an integer (no floating point and decimals)
179 				return Long.toString((long) d);
180 			} else {
181 				// It's not a integer, represent it with the specified format
182 				try {
183 					String s = String.format(locale, convfmt, d);
184 					// Surprisingly, while %.6g is the official representation of numbers in AWK
185 					// which should include trailing zeroes, AWK seems to trim them. So, we will
186 					// do the same: trim the trailing zeroes
187 					if ((s.indexOf('.') > -1 || s.indexOf(',') > -1) && (s.indexOf('e') + s.indexOf('E') == -2)) {
188 						while (s.endsWith("0")) {
189 							s = s.substring(0, s.length() - 1);
190 						}
191 						if (s.endsWith(".") || s.endsWith(",")) {
192 							s = s.substring(0, s.length() - 1);
193 						}
194 					}
195 					return s;
196 				} catch (java.util.UnknownFormatConversionException ufce) {
197 					// Impossible case
198 					return "";
199 				}
200 			}
201 		} else {
202 			// It's not a number, easy
203 			return o.toString();
204 		}
205 	}
206 
207 	// not static to use CONVFMT (& possibly OFMT later)
208 	/**
209 	 * Convert a String, Integer, or Double to String
210 	 * based on the OFMT variable contents. Jawk will
211 	 * subsequently use this String for output via print().
212 	 *
213 	 * @param o Object to convert.
214 	 * @param ofmt The contents of the OFMT variable.
215 	 * @return A String representation of o.
216 	 * @param locale a {@link java.util.Locale} object
217 	 */
218 	public static String toAwkStringForOutput(Object o, String ofmt, Locale locale) {
219 
220 		// Even if specified Object o is not officially a number, we try to convert
221 		// it to a Double. Because if it's a literal representation of a number,
222 		// we will need to display it as a number ("12.00" --> 12)
223 		if (!(o instanceof Number)) {
224 			try {
225 				o = Double.parseDouble(o.toString());
226 			} catch (NumberFormatException e) {
227 				// Do nothing here
228 			}
229 		}
230 
231 		return toAwkString(o, ofmt, locale);
232 	}
233 
234 	/**
235 	 * Convert a String, Integer, or Double to Double.
236 	 *
237 	 * @param o Object to convert.
238 	 *
239 	 * @return the "double" value of o, or 0 if invalid
240 	 */
241 	public static double toDouble(final Object o) {
242 
243 		if (o == null) {
244 			return 0;
245 		}
246 
247 		if (o instanceof Number) {
248 			return ((Number) o).doubleValue();
249 		}
250 
251 		if (o instanceof Character) {
252 			return (double)((Character)o).charValue();
253 		}
254 
255 		// Try to convert the string to a number.
256 		String s = o.toString();
257 		int length = s.length();
258 
259 		// Optimization: We don't need to handle strings that are longer than 26 chars
260 		// because a Double cannot be longer than 26 chars when converted to String.
261 		if (length > 26) {
262 			length = 26;
263 		}
264 
265 		// Loop:
266 		// If convervsion fails, try with one character less.
267 		// 25fix will convert to 25 (any numeric prefix will work)
268 		while (length > 0) {
269 			try {
270 				return Double.parseDouble(s.substring(0, length));
271 			} catch (NumberFormatException nfe) {
272 				length--;
273 			}
274 		}
275 
276 		// Failed (not even with one char)
277 		return 0;
278 	}
279 
280 	/**
281 	 * Convert a String, Long, or Double to Long.
282 	 *
283 	 * @param o Object to convert.
284 	 *
285 	 * @return the "long" value of o, or 0 if invalid
286 	 */
287 	public static long toLong(final Object o) {
288 
289 		if (o == null) {
290 			return 0;
291 		}
292 
293 		if (o instanceof Number) {
294 			return ((Number)o).longValue();
295 		}
296 
297 		if (o instanceof Character) {
298 			return (long)((Character)o).charValue();
299 		}
300 
301 		// Try to convert the string to a number.
302 		String s = o.toString();
303 		int length = s.length();
304 
305 		// Optimization: We don't need to handle strings that are longer than 20 chars
306 		// because a Long cannot be longer than 20 chars when converted to String.
307 		if (length > 20) {
308 			length = 20;
309 		}
310 
311 		// Loop:
312 		// If convervsion fails, try with one character less.
313 		// 25fix will convert to 25 (any numeric prefix will work)
314 		while (length > 0) {
315 			try {
316 				return Long.parseLong(s.substring(0, length));
317 			} catch (NumberFormatException nfe) {
318 				length--;
319 			}
320 
321 		}
322 		// Failed (not even with one char)
323 		return 0;
324 	}
325 
326 	/**
327 	 * Compares two objects. Whether to employ less-than, equals, or
328 	 * greater-than checks depends on the mode chosen by the callee.
329 	 * It handles Awk variable rules and type conversion semantics.
330 	 *
331 	 * @param o1 The 1st object.
332 	 * @param o2 the 2nd object.
333 	 * @param mode <ul>
334 	 *   <li>&lt; 0 - Return true if o1 &lt; o2.
335 	 *   <li>0 - Return true if o1 == o2.
336 	 *   <li>&gt; 0 - Return true if o1 &gt; o2.
337 	 *   </ul>
338 	 * @return a boolean
339 	 */
340 	public static boolean compare2(Object o1, Object o2, int mode) {
341 
342 		// Pre-compute String representations of o1 and o2
343 		String o1String = o1.toString();
344 		String o2String = o2.toString();
345 
346 		// Special case of Uninitialized objects
347 		if (o1 instanceof UninitializedObject) {
348 			if (o2 instanceof UninitializedObject ||
349 					"".equals(o2String) ||
350 					"0".equals(o2String)) {
351 				return mode == 0;
352 			} else {
353 				return mode < 0;
354 			}
355 		}
356 		if (o2 instanceof UninitializedObject) {
357 			if ("".equals(o1String) ||
358 					"0".equals(o1String)) {
359 				return mode == 0;
360 			} else {
361 				return mode > 0;
362 			}
363 		}
364 
365 		if (!(o1 instanceof Number) && !o1String.isEmpty()) {
366 			char o1FirstChar = o1String.charAt(0);
367 			if (o1FirstChar >= '0' && o1FirstChar <= '9') {
368 				try {
369 					o1 = Double.parseDouble(o1String);
370 				} catch (NumberFormatException nfe) { /* Fail silently */ }
371 			}
372 		}
373 		if (!(o2 instanceof Number) && !o2String.isEmpty()) {
374 			char o2FirstChar = o2String.charAt(0);
375 			if (o2FirstChar >= '0' && o2FirstChar <= '9') {
376 				try {
377 					o2 = Double.parseDouble(o2String);
378 				} catch (NumberFormatException nfe) { /* Fail silently */ }
379 			}
380 		}
381 
382 		if ((o1 instanceof Number) && (o2 instanceof Number)) {
383 			if (mode < 0) {
384 				return (((Number) o1).doubleValue() < ((Number) o2).doubleValue());
385 			} else if (mode == 0) {
386 				return (((Number) o1).doubleValue() == ((Number) o2).doubleValue());
387 			} else {
388 				return (((Number) o1).doubleValue() > ((Number) o2).doubleValue());
389 			}
390 		} else {
391 
392 			// string equality usually occurs more often than natural ordering comparison
393 			if (mode == 0) {
394 				return o1String.equals(o2String);
395 			} else if (mode < 0) {
396 				return o1String.compareTo(o2String) < 0;
397 			} else {
398 				return o1String.compareTo(o2String) > 0;
399 			}
400 		}
401 	}
402 
403 	/**
404 	 * Return an object which is numerically equivalent to
405 	 * one plus a given object. For Integers and Doubles,
406 	 * this is similar to o+1. For Strings, attempts are
407 	 * made to convert it to a double first. If the
408 	 * String does not represent a valid Double, 1 is returned.
409 	 *
410 	 * @param o The object to increase.
411 	 * @return o+1 if o is an Integer or Double object, or
412 	 *   if o is a String object and represents a double.
413 	 *   Otherwise, 1 is returned. If the return value
414 	 *   is an integer, an Integer object is returned.
415 	 *   Otherwise, a Double object is returned.
416 	 */
417 	public static Object inc(Object o) {
418 		assert (o != null);
419 		double ans;
420 		if (o instanceof Number) {
421 			ans = ((Number) o).doubleValue() + 1;
422 		} else {
423 			try {
424 				ans = Double.parseDouble(o.toString()) + 1;
425 			} catch (NumberFormatException nfe) {
426 				ans = 1;
427 			}
428 		}
429 		if (ans == (long) ans) {
430 			return (long) ans;
431 		} else {
432 			return ans;
433 		}
434 	}
435 
436 	/**
437 	 * Return an object which is numerically equivalent to
438 	 * one minus a given object. For Integers and Doubles,
439 	 * this is similar to o-1. For Strings, attempts are
440 	 * made to convert it to a double first. If the
441 	 * String does not represent a valid Double, -1 is returned.
442 	 *
443 	 * @param o The object to increase.
444 	 * @return o-1 if o is an Integer or Double object, or
445 	 *   if o is a String object and represents a double.
446 	 *   Otherwise, -1 is returned. If the return value
447 	 *   is an integer, an Integer object is returned.
448 	 *   Otherwise, a Double object is returned.
449 	 */
450 	public static Object dec(Object o) {
451 		double ans;
452 		if (o instanceof Number) {
453 			ans = ((Number) o).doubleValue() - 1;
454 		} else {
455 			try {
456 				ans = Double.parseDouble(o.toString()) - 1;
457 			} catch (NumberFormatException nfe) {
458 				ans = 1;
459 			}
460 		}
461 		if (ans == (long) ans) {
462 			return (long) ans;
463 		} else {
464 			return ans;
465 		}
466 	}
467 
468 	// non-static to reference "inputLine"
469 	/**
470 	 * Converts an Integer, Double, String, Pattern,
471 	 * or ConditionPair to a boolean.
472 	 *
473 	 * @param o The object to convert to a boolean.
474 	 * @return For the following class types for o:
475 	 * 	<ul>
476 	 * 	<li><strong>Integer</strong> - o.intValue() != 0
477 	 * 	<li><strong>Long</strong> - o.longValue() != 0
478 	 * 	<li><strong>Double</strong> - o.doubleValue() != 0
479 	 * 	<li><strong>String</strong> - o.length() &gt; 0
480 	 * 	<li><strong>UninitializedObject</strong> - false
481 	 * 	<li><strong>Pattern</strong> - $0 ~ o
482 	 * 	</ul>
483 	 * 	If o is none of these types, an error is thrown.
484 	 */
485 	public final boolean toBoolean(Object o) {
486 		boolean val;
487 		if (o instanceof Integer) {
488 			val = ((Integer)o).intValue() != 0;
489 		} else if (o instanceof Long) {
490 			val = ((Long)o).longValue() != 0;
491 		} else if (o instanceof Double) {
492 			val = ((Double)o).doubleValue() != 0;
493 		} else if (o instanceof String) {
494 			val = (o.toString().length() > 0);
495 		} else if (o instanceof UninitializedObject) {
496 			val = false;
497 		} else if (o instanceof Pattern) {
498 			// match against $0
499 			// ...
500 			Pattern pattern = (Pattern) o;
501 			String s = inputLine == null ? "" : inputLine;
502 			Matcher matcher = pattern.matcher(s);
503 			val = matcher.find();
504 		} else {
505 			throw new Error("Unknown operand_stack type: " + o.getClass() + " for value " + o);
506 		}
507 		return val;
508 	}
509 
510 	/**
511 	 * Splits the string into parts separated by one or more spaces;
512 	 * blank first and last fields are eliminated.
513 	 * This conforms to the 2-argument version of AWK's split function.
514 	 *
515 	 * @param array The array to populate.
516 	 * @param string The string to split.
517 	 * @param convfmt Contents of the CONVFMT variable.
518 	 * @return The number of parts resulting from this split operation.
519 	 * @param locale a {@link java.util.Locale} object
520 	 */
521 	public static int split(Object array, Object string, String convfmt, Locale locale) {
522 		return splitWorker(new StringTokenizer(toAwkString(string, convfmt, locale)), (AssocArray) array);
523 	}
524 	/**
525 	 * Splits the string into parts separated the regular expression fs.
526 	 * This conforms to the 3-argument version of AWK's split function.
527 	 * <p>
528 	 * If fs is blank, it behaves similar to the 2-arg version of
529 	 * AWK's split function.
530 	 *
531 	 * @param fs Field separator regular expression.
532 	 * @param array The array to populate.
533 	 * @param string The string to split.
534 	 * @param convfmt Contents of the CONVFMT variable.
535 	 * @return The number of parts resulting from this split operation.
536 	 * @param locale a {@link java.util.Locale} object
537 	 */
538 	public static int split(Object fs, Object array, Object string, String convfmt, Locale locale) {
539 		String fs_string = toAwkString(fs, convfmt, locale);
540 		if (fs_string.equals(" ")) {
541 			return splitWorker(new StringTokenizer(toAwkString(string, convfmt, locale)), (AssocArray) array);
542 		} else if (fs_string.equals("")) {
543 			return splitWorker(new CharacterTokenizer(toAwkString(string, convfmt, locale)), (AssocArray) array);
544 		} else if (fs_string.length() == 1) {
545 			return splitWorker(new SingleCharacterTokenizer(toAwkString(string, convfmt, locale), fs_string.charAt(0)), (AssocArray) array);
546 		} else {
547 			return splitWorker(new RegexTokenizer(toAwkString(string, convfmt, locale), fs_string), (AssocArray) array);
548 		}
549 	}
550 
551 	private static int splitWorker(Enumeration<Object> e, AssocArray aa) {
552 		int cnt = 0;
553 		aa.clear();
554 		while (e.hasMoreElements()) {
555 			aa.put(++cnt, e.nextElement());
556 		}
557 		return cnt;
558 	}
559 
560 	/**
561 	 * <p>Getter for the field <code>partitioningReader</code>.</p>
562 	 *
563 	 * @return a {@link org.sentrysoftware.jawk.jrt.PartitioningReader} object
564 	 */
565 	public PartitioningReader getPartitioningReader() {
566 		return partitioningReader;
567 	}
568 
569 	/**
570 	 * <p>Getter for the field <code>inputLine</code>.</p>
571 	 *
572 	 * @return a {@link java.lang.String} object
573 	 */
574 	public String getInputLine() {
575 		return inputLine;
576 	}
577 
578 	/**
579 	 * <p>Setter for the field <code>inputLine</code>.</p>
580 	 *
581 	 * @param inputLine a {@link java.lang.String} object
582 	 */
583 	public void setInputLine(String inputLine) {
584 		this.inputLine = inputLine;
585 	}
586 
587 	/**
588 	 * Attempt to consume one line of input, either from stdin
589 	 * or from filenames passed in to ARGC/ARGV via
590 	 * the command-line.
591 	 *
592 	 * @param for_getline true if call is for getline, false otherwise.
593 	 * @return true if line is consumed, false otherwise.
594 	 * @throws java.io.IOException upon an IO error.
595 	 * @param input a {@link java.io.InputStream} object
596 	 * @param locale a {@link java.util.Locale} object
597 	 */
598 	public boolean jrtConsumeInput(final InputStream input, boolean for_getline, Locale locale) throws IOException {
599 		// first time!
600 		if (arglist_aa == null) {
601 			Object arglist_obj = vm.getARGV(); // vm.getVariable("argv_field", true);
602 			arglist_aa = (AssocArray) arglist_obj;
603 			arglist_idx = 1;
604 
605 			// calculate has_filenames
606 
607 			int argc = (int) toDouble(vm.getARGC()); //(vm.getVariable("argc_field", true));
608 			// 1 .. argc doesn't make sense
609 			// 1 .. argc-1 does since arguments of:
610 			// a b c
611 			// result in:
612 			// ARGC=4
613 			// ARGV[0]="java Awk"
614 			// ARGV[1]="a"
615 			// ARGV[2]="b"
616 			// ARGV[3]="c"
617 			for (long i = 1; i < argc; i++) {
618 				if (arglist_aa.isIn(i)) {
619 					Object namevalue_or_filename_object = arglist_aa.get(i);
620 					String namevalue_or_filename = toAwkString(namevalue_or_filename_object, vm.getCONVFMT().toString(), locale);
621 					if (namevalue_or_filename.indexOf('=') == -1) {
622 						// filename!
623 						has_filenames = true;
624 						break;
625 					}
626 				}
627 			}
628 		}
629 
630 		// initial: pr == null
631 		// subsequent: pr != null, but eof
632 
633 		while (true) {
634 			try {
635 				if (partitioningReader == null) {
636 					int argc = (int) toDouble(vm.getARGC()); // (vm.getVariable("argc_field", true));
637 					Object o = BLANK;
638 					while(arglist_idx <= argc) {
639 						o = arglist_aa.get(arglist_idx);
640 						++arglist_idx;
641 						if (!(o instanceof UninitializedObject || o.toString().isEmpty())) {
642 							break;
643 						}
644 					}
645 					if (!(o instanceof UninitializedObject || o.toString().isEmpty())) {
646 						String name_value_or_filename = toAwkString(o, vm.getCONVFMT().toString(), locale);
647 						if (name_value_or_filename.indexOf('=') == -1) {
648 							partitioningReader = new PartitioningReader(new FileReader(name_value_or_filename), vm.getRS().toString(), true);
649 							vm.setFILENAME(name_value_or_filename);
650 							vm.resetFNR();
651 						} else {
652 							setFilelistVariable(name_value_or_filename);
653 							if (!has_filenames) {
654 								// stdin with a variable!
655 								partitioningReader = new PartitioningReader(new InputStreamReader(input), vm.getRS().toString());
656 								vm.setFILENAME("");
657 							} else {
658 								continue;
659 							}
660 						}
661 					} else if (!has_filenames) {
662 						partitioningReader = new PartitioningReader(new InputStreamReader(input), vm.getRS().toString());
663 						vm.setFILENAME("");
664 					} else {
665 						return false;
666 					}
667 				} else if (inputLine == null) {
668 					if (has_filenames) {
669 						int argc = (int) toDouble(vm.getARGC());
670 						Object o = BLANK;
671 						while(arglist_idx <= argc) {
672 							o = arglist_aa.get(arglist_idx);
673 							++arglist_idx;
674 							if (!(o instanceof UninitializedObject || o.toString().isEmpty())) {
675 								break;
676 							}
677 						}
678 						if (!(o instanceof UninitializedObject || o.toString().isEmpty())) {
679 							String name_value_or_filename = toAwkString(o, vm.getCONVFMT().toString(), locale);
680 							if (name_value_or_filename.indexOf('=') == -1) {
681 								// true = from filename list
682 								partitioningReader = new PartitioningReader(new FileReader(name_value_or_filename), vm.getRS().toString(), true);
683 								vm.setFILENAME(name_value_or_filename);
684 								vm.resetFNR();
685 							} else {
686 								setFilelistVariable(name_value_or_filename);
687 								vm.incNR();
688 								continue;
689 							}
690 						} else {
691 							return false;
692 						}
693 					} else {
694 						return false;
695 					}
696 				}
697 
698 
699 				// when active_input == false, usually means
700 				// to instantiate "pr" (PartitioningReader for $0, etc)
701 				// for Jawk extensions
702 				//if (!active_input)
703 				//	return false;
704 
705 				inputLine = partitioningReader.readRecord();
706 				if (inputLine == null) {
707 					continue;
708 				} else {
709 					if (for_getline) {
710 						// TRUE
711 						// leave result on the stack
712 						// DO NOTHING! The callee will re-acquire $0
713 					} else {
714 						// FALSE
715 						// leave the stack alone ...
716 						jrtParseFields();
717 					}
718 					vm.incNR();
719 					if (partitioningReader.fromFilenameList()) {
720 						vm.incFNR();
721 					}
722 					return true;
723 				}
724 			} catch (IOException ioe) {
725 				LOG.warn("IO Exception", ioe);
726 				continue;
727 			}
728 		}
729 	}
730 
731 	private void setFilelistVariable(String name_value) {
732 		int eq_idx = name_value.indexOf('=');
733 		// variable name should be non-blank
734 		assert eq_idx >= 0;
735 		if (eq_idx == 0) {
736 			throw new IllegalArgumentException("Must have a non-blank variable name in a name=value variable assignment argument.");
737 		}
738 		String name = name_value.substring(0, eq_idx);
739 		String value = name_value.substring(eq_idx + 1);
740 		Object obj;
741 		try {
742 			obj = Integer.parseInt(value);
743 		} catch (NumberFormatException nfe) {
744 			try {
745 				obj = Double.parseDouble(value);
746 			} catch (NumberFormatException nfe2) {
747 				obj = value;
748 			}
749 		}
750 		vm.assignVariable(name, obj);
751 	}
752 
753 	/**
754 	 * Splits $0 into $1, $2, etc.
755 	 * Called when an update to $0 has occurred.
756 	 */
757 	public void jrtParseFields() {
758 		String fs_string = vm.getFS().toString();
759 		Enumeration<Object> tokenizer;
760 		if (fs_string.equals(" ")) {
761 			tokenizer = new StringTokenizer(inputLine);
762 		} else if (fs_string.length() == 1) {
763 			tokenizer = new SingleCharacterTokenizer(inputLine, fs_string.charAt(0));
764 		} else if (fs_string.equals("")) {
765 			tokenizer = new CharacterTokenizer(inputLine);
766 		} else {
767 			tokenizer = new RegexTokenizer(inputLine, fs_string);
768 		}
769 
770 		assert inputLine != null;
771 		input_fields.clear();
772 		input_fields.add(inputLine); // $0
773 		while (tokenizer.hasMoreElements()) {
774 			input_fields.add((String) tokenizer.nextElement());
775 		}
776 		// recalc NF
777 		recalculateNF();
778 	}
779 
780 	private void recalculateNF() {
781 		vm.setNF(Integer.valueOf(input_fields.size() - 1));
782 	}
783 
784 	private static int toFieldNumber(Object o) {
785 		int fieldnum;
786 		if (o instanceof Number) {
787 			fieldnum = ((Number) o).intValue();
788 		} else {
789 			try {
790 				fieldnum = (int) Double.parseDouble(o.toString());
791 			} catch (NumberFormatException nfe) {
792 				throw new RuntimeException("Field $(" + o.toString() + ") is incorrect.");
793 			}
794 		}
795 		return fieldnum;
796 	}
797 
798 	/**
799 	 * Retrieve the contents of a particular input field.
800 	 *
801 	 * @param fieldnum_obj Object referring to the field number.
802 	 * @return Contents of the field.
803 	 */
804 	public Object jrtGetInputField(Object fieldnum_obj) {
805 		return jrtGetInputField(toFieldNumber(fieldnum_obj));
806 	}
807 
808 	/**
809 	 * <p>jrtGetInputField.</p>
810 	 *
811 	 * @param fieldnum a int
812 	 * @return a {@link java.lang.Object} object
813 	 */
814 	public Object jrtGetInputField(int fieldnum) {
815 		if (fieldnum < input_fields.size()) {
816 			String retval = input_fields.get(fieldnum);
817 			assert retval != null;
818 			return retval;
819 		} else {
820 			return BLANK;
821 		}
822 	}
823 
824 	/**
825 	 * Stores value_obj into an input field.
826 	 *
827 	 * @param value_obj The RHS of the assignment.
828 	 * @param field_num Object referring to the field number.
829 	 * @return A string representation of value_obj.
830 	 */
831 	public String jrtSetInputField(Object value_obj, int field_num) {
832 		assert field_num >= 1;
833 		assert value_obj != null;
834 		String value = value_obj.toString();
835 		// if the value is BLANK
836 		if (value_obj instanceof UninitializedObject) {
837 			if (field_num < input_fields.size()) {
838 				input_fields.set(field_num, "");
839 			}
840 		} else {
841 			// append the list to accommodate the new value
842 			for (int i = input_fields.size() - 1; i < field_num; i++) {
843 				input_fields.add("");
844 			}
845 			input_fields.set(field_num, value);
846 		}
847 		// rebuild $0
848 		rebuildDollarZeroFromFields();
849 		// recalc NF
850 		recalculateNF();
851 		return value;
852 	}
853 
854 	private void rebuildDollarZeroFromFields() {
855 		StringBuilder new_dollar_zero_sb = new StringBuilder();
856 		String ofs = vm.getOFS().toString();
857 		for (int i = 1; i < input_fields.size(); i++) {
858 			if (i > 1) {
859 				new_dollar_zero_sb.append(ofs);
860 			}
861 			new_dollar_zero_sb.append(input_fields.get(i));
862 		}
863 		input_fields.set(0, new_dollar_zero_sb.toString());
864 	}
865 
866 	/**
867 	 * <p>jrtConsumeFileInputForGetline.</p>
868 	 *
869 	 * @param filename a {@link java.lang.String} object
870 	 * @return a {@link java.lang.Integer} object
871 	 */
872 	public Integer jrtConsumeFileInputForGetline(String filename) {
873 		try {
874 			if (jrtConsumeFileInput(filename)) {
875 				return ONE;
876 			} else {
877 				jrt_input_string = "";
878 				return ZERO;
879 			}
880 		} catch (IOException ioe) {
881 			jrt_input_string = "";
882 			return MINUS_ONE;
883 		}
884 	}
885 
886 	/**
887 	 * Retrieve the next line of output from a command, executing
888 	 * the command if necessary and store it to $0.
889 	 *
890 	 * @param cmd_string The command to execute.
891 	 * @return Integer(1) if successful, Integer(0) if no more
892 	 * 	input is available, Integer(-1) upon an IO error.
893 	 */
894 	public Integer jrtConsumeCommandInputForGetline(String cmd_string) {
895 		try {
896 			if (jrtConsumeCommandInput(cmd_string)) {
897 				return ONE;
898 			} else {
899 				jrt_input_string = "";
900 				return ZERO;
901 			}
902 		} catch (IOException ioe) {
903 			jrt_input_string = "";
904 			return MINUS_ONE;
905 		}
906 	}
907 
908 	/**
909 	 * Retrieve $0.
910 	 *
911 	 * @return The contents of the $0 input field.
912 	 */
913 	public String jrtGetInputString() {
914 		return jrt_input_string;
915 	}
916 
917 	/**
918 	 * <p>Getter for the field <code>outputFiles</code>.</p>
919 	 *
920 	 * @return a {@link java.util.Map} object
921 	 */
922 	public Map<String, PrintStream> getOutputFiles() {
923 		return outputFiles;
924 	}
925 
926 	/**
927 	 * Retrieve the PrintStream which writes to a particular file,
928 	 * creating the PrintStream if necessary.
929 	 *
930 	 * @param filename The file which to write the contents of the PrintStream.
931 	 * @param append true to append to the file, false to overwrite the file.
932 	 * @return a {@link java.io.PrintStream} object
933 	 */
934 	public final PrintStream jrtGetPrintStream(String filename, boolean append) {
935 		PrintStream ps = outputFiles.get(filename);
936 		if (ps == null) {
937 			try {
938 				outputFiles.put(filename, ps = new PrintStream(new FileOutputStream(filename, append), true));	// true = autoflush
939 			} catch (IOException ioe) {
940 				throw new AwkRuntimeException("Cannot open " + filename + " for writing: " + ioe);
941 			}
942 		}
943 		assert ps != null;
944 		return ps;
945 	}
946 
947 	/**
948 	 * <p>jrtConsumeFileInput.</p>
949 	 *
950 	 * @param filename a {@link java.lang.String} object
951 	 * @return a boolean
952 	 * @throws java.io.IOException if any.
953 	 */
954 	public boolean jrtConsumeFileInput(String filename) throws IOException {
955 		PartitioningReader pr = file_readers.get(filename);
956 		if (pr == null) {
957 			try {
958 				file_readers.put(filename, pr = new PartitioningReader(new FileReader(filename), vm.getRS().toString()));
959 				vm.setFILENAME(filename);
960 			} catch (IOException ioe) {
961 				LOG.warn("IO Exception", ioe);
962 				file_readers.remove(filename);
963 				throw ioe;
964 			}
965 		}
966 
967 		inputLine = pr.readRecord();
968 		if (inputLine == null) {
969 			return false;
970 		} else {
971 			jrt_input_string = inputLine;
972 			vm.incNR();
973 			return true;
974 		}
975 	}
976 
977 	private static Process spawnProcess(String cmd) throws IOException {
978 
979 		Process p;
980 
981 		if (IS_WINDOWS) {
982 			// spawn the process!
983 			ProcessBuilder pb = new ProcessBuilder(("cmd.exe /c " + cmd).split("[ \t]+"));
984 			p = pb.start();
985 		} else {
986 			// spawn the process!
987 			ProcessBuilder pb = new ProcessBuilder(cmd.split("[ \t]+"));
988 			p = pb.start();
989 		}
990 
991 		return p;
992 	}
993 
994 	/**
995 	 * <p>jrtConsumeCommandInput.</p>
996 	 *
997 	 * @param cmd a {@link java.lang.String} object
998 	 * @return a boolean
999 	 * @throws java.io.IOException if any.
1000 	 */
1001 	public boolean jrtConsumeCommandInput(String cmd) throws IOException {
1002 		PartitioningReader pr = command_readers.get(cmd);
1003 		if (pr == null) {
1004 			try {
1005 				Process p = spawnProcess(cmd);
1006 				// no input to this process!
1007 				p.getOutputStream().close();
1008 				DataPump.dump(cmd, p.getErrorStream(), System.err);
1009 				command_processes.put(cmd, p);
1010 				command_readers.put(cmd, pr = new PartitioningReader(new InputStreamReader(p.getInputStream()), vm.getRS().toString()));
1011 				vm.setFILENAME("");
1012 			} catch (IOException ioe) {
1013 				LOG.warn("IO Exception", ioe);
1014 				command_readers.remove(cmd);
1015 				Process p = command_processes.get(cmd);
1016 				command_processes.remove(cmd);
1017 				if (p != null) {
1018 					p.destroy();
1019 				}
1020 				throw ioe;
1021 			}
1022 		}
1023 
1024 		inputLine = pr.readRecord();
1025 		if (inputLine == null) {
1026 			return false;
1027 		} else {
1028 			jrt_input_string = inputLine;
1029 			vm.incNR();
1030 			return true;
1031 		}
1032 	}
1033 
1034 	/**
1035 	 * Retrieve the PrintStream which shuttles data to stdin for a process,
1036 	 * executing the process if necessary. Threads are created to shuttle the
1037 	 * data to/from the process.
1038 	 *
1039 	 * @param cmd The command to execute.
1040 	 * @return The PrintStream which to write to provide
1041 	 *   input data to the process.
1042 	 */
1043 	public PrintStream jrtSpawnForOutput(String cmd) {
1044 		PrintStream ps = output_streams.get(cmd);
1045 		if (ps == null) {
1046 			Process p;
1047 			try {
1048 				p = spawnProcess(cmd);
1049 				DataPump.dump(cmd, p.getErrorStream(), System.err);
1050 				DataPump.dump(cmd, p.getInputStream(), System.out);
1051 			} catch (IOException ioe) {
1052 				throw new AwkRuntimeException("Can't spawn " + cmd + ": " + ioe);
1053 			}
1054 			output_processes.put(cmd, p);
1055 			output_streams.put(cmd, ps = new PrintStream(p.getOutputStream(), true));	// true = auto-flush
1056 		}
1057 		return ps;
1058 	}
1059 
1060 	/**
1061 	 * Attempt to close an open stream, whether it is
1062 	 * an input file, output file, input process, or output
1063 	 * process.
1064 	 * <p>
1065 	 * The specification did not describe AWK behavior
1066 	 * when attempting to close streams/processes with
1067 	 * the same file/command name. In this case,
1068 	 * <em>all</em> open streams with this name
1069 	 * are closed.
1070 	 *
1071 	 * @param filename The filename/command process to close.
1072 	 * @return Integer(0) upon a successful close, Integer(-1)
1073 	 *   otherwise.
1074 	 */
1075 	public Integer jrtClose(String filename) {
1076 		boolean b1 = jrtCloseFileReader(filename);
1077 		boolean b2 = jrtCloseCommandReader(filename);
1078 		boolean b3 = jrtCloseOutputFile(filename);
1079 		boolean b4 = jrtCloseOutputStream(filename);
1080 		// either close will do
1081 		return (b1 || b2 || b3 || b4) ? ZERO : MINUS_ONE;
1082 	}
1083 
1084 	/**
1085 	 * <p>jrtCloseAll.</p>
1086 	 */
1087 	public void jrtCloseAll() {
1088 		Set<String> set = new HashSet<String>();
1089 		for (String s : file_readers.keySet()) {
1090 			set.add(s);
1091 		}
1092 		for (String s : command_readers.keySet()) {
1093 			set.add(s);
1094 		}
1095 		for (String s : outputFiles.keySet()) {
1096 			set.add(s);
1097 		}
1098 		for (String s : output_streams.keySet()) {
1099 			set.add(s);
1100 		}
1101 		for (String s : set) {
1102 			jrtClose(s);
1103 		}
1104 	}
1105 
1106 	private boolean jrtCloseOutputFile(String filename) {
1107 		PrintStream ps = outputFiles.get(filename);
1108 		if (ps != null) {
1109 			ps.close();
1110 			outputFiles.remove(filename);
1111 		}
1112 		return ps != null;
1113 	}
1114 
1115 	private boolean jrtCloseOutputStream(String cmd) {
1116 		Process p = output_processes.get(cmd);
1117 		PrintStream ps = output_streams.get(cmd);
1118 		if (ps == null) {
1119 			return false;
1120 		}
1121 		assert p != null;
1122 		output_processes.remove(cmd);
1123 		output_streams.remove(cmd);
1124 		ps.close();
1125 		// if windows, let the process kill itself eventually
1126 		if (!IS_WINDOWS) {
1127 			try {
1128 				// causes a hard exit ?!
1129 				p.waitFor();
1130 				p.exitValue();
1131 			} catch (InterruptedException ie) {
1132 				throw new AwkRuntimeException("Caught exception while waiting for process exit: " + ie);
1133 			}
1134 		}
1135 		return true;
1136 	}
1137 
1138 	private boolean jrtCloseFileReader(String filename) {
1139 		PartitioningReader pr = file_readers.get(filename);
1140 		if (pr == null) {
1141 			return false;
1142 		}
1143 		file_readers.remove(filename);
1144 		try {
1145 			pr.close();
1146 			return true;
1147 		} catch (IOException ioe) {
1148 			return false;
1149 		}
1150 	}
1151 
1152 	private boolean jrtCloseCommandReader(String cmd) {
1153 		Process p = command_processes.get(cmd);
1154 		PartitioningReader pr = command_readers.get(cmd);
1155 		if (pr == null) {
1156 			return false;
1157 		}
1158 		assert p != null;
1159 		command_readers.remove(cmd);
1160 		command_processes.remove(cmd);
1161 		try {
1162 			pr.close();
1163 			// if windows, let the process kill itself eventually
1164 			if (!IS_WINDOWS) {
1165 				try {
1166 					// causes a hard die ?!
1167 					p.waitFor();
1168 					p.exitValue();
1169 				} catch (InterruptedException ie) {
1170 					throw new AwkRuntimeException("Caught exception while waiting for process exit: " + ie);
1171 				}
1172 			}
1173 			return true;
1174 		} catch (IOException ioe) {
1175 			return false;
1176 		}
1177 	}
1178 
1179 	/**
1180 	 * Executes the command specified by cmd and waits
1181 	 * for termination, returning an Integer object
1182 	 * containing the return code.
1183 	 * stdin to this process is closed while
1184 	 * threads are created to shuttle stdout and
1185 	 * stderr of the command to stdout/stderr
1186 	 * of the calling process.
1187 	 *
1188 	 * @param cmd The command to execute.
1189 	 * @return Integer(return_code) of the created
1190 	 *   process. Integer(-1) is returned on an IO error.
1191 	 */
1192 	public static Integer jrtSystem(String cmd) {
1193 		try {
1194 			Process p = spawnProcess(cmd);
1195 			// no input to this process!
1196 			p.getOutputStream().close();
1197 			DataPump.dump(cmd, p.getErrorStream(), System.err);
1198 			DataPump.dump(cmd, p.getInputStream(), System.out);
1199 			try {
1200 				int retcode = p.waitFor();
1201 				return Integer.valueOf(retcode);
1202 			} catch (InterruptedException ie) {
1203 				return Integer.valueOf(p.exitValue());
1204 			}
1205 		} catch (IOException ioe) {
1206 			LOG.warn("IO Exception", ioe);
1207 			return MINUS_ONE;
1208 		}
1209 	}
1210 
1211 	/**
1212 	 * <p>sprintfFunctionNoCatch.</p>
1213 	 *
1214 	 * @param locale a {@link java.util.Locale} object
1215 	 * @param fmt_arg a {@link java.lang.String} object
1216 	 * @param arr an array of {@link java.lang.Object} objects
1217 	 * @return a {@link java.lang.String} object
1218 	 * @throws java.util.IllegalFormatException if any.
1219 	 */
1220 	public static String sprintfNoCatch(Locale locale, String fmt_arg, Object... arr)
1221 			throws IllegalFormatException
1222 	{
1223 		return String.format(locale, fmt_arg, arr);
1224 	}
1225 
1226 	/**
1227 	 * <p>printfFunctionNoCatch.</p>
1228 	 *
1229 	 * @param locale a {@link java.util.Locale} object
1230 	 * @param fmt_arg a {@link java.lang.String} object
1231 	 * @param arr an array of {@link java.lang.Object} objects
1232 	 */
1233 	public static void printfNoCatch(Locale locale, String fmt_arg, Object... arr) {
1234 		System.out.print(sprintfNoCatch(locale, fmt_arg, arr));
1235 	}
1236 
1237 	/**
1238 	 * <p>printfFunctionNoCatch.</p>
1239 	 *
1240 	 * @param ps a {@link java.io.PrintStream} object
1241 	 * @param locale a {@link java.util.Locale} object
1242 	 * @param fmt_arg a {@link java.lang.String} object
1243 	 * @param arr an array of {@link java.lang.Object} objects
1244 	 */
1245 	public static void printfNoCatch(PrintStream ps, Locale locale, String fmt_arg, Object... arr) {
1246 		ps.print(sprintfNoCatch(locale, fmt_arg, arr));
1247 	}
1248 
1249 	/**
1250 	 * <p>replaceFirst.</p>
1251 	 *
1252 	 * @param orig_value_obj a {@link java.lang.Object} object
1253 	 * @param repl_obj a {@link java.lang.Object} object
1254 	 * @param ere_obj a {@link java.lang.Object} object
1255 	 * @param sb a {@link java.lang.StringBuffer} object
1256 	 * @param convfmt a {@link java.lang.String} object
1257 	 * @param locale a {@link java.util.Locale} object
1258 	 * @return a {@link java.lang.Integer} object
1259 	 */
1260 	public static Integer replaceFirst(Object orig_value_obj, Object repl_obj, Object ere_obj, StringBuffer sb, String convfmt, Locale locale) {
1261 		String orig_value = toAwkString(orig_value_obj, convfmt, locale);
1262 		String repl = toAwkString(repl_obj, convfmt, locale);
1263 		String ere = toAwkString(ere_obj, convfmt, locale);
1264 		// remove special meaning for backslash and dollar signs
1265 		repl = Matcher.quoteReplacement(repl);
1266 		sb.setLength(0);
1267 		sb.append(orig_value.replaceFirst(ere, repl));
1268 		if (sb.toString().equals(orig_value)) {
1269 			return ZERO;
1270 		} else {
1271 			return ONE;
1272 		}
1273 	}
1274 
1275 	/**
1276 	 * <p>replaceAll.</p>
1277 	 *
1278 	 * @param orig_value_obj a {@link java.lang.Object} object
1279 	 * @param repl_obj a {@link java.lang.Object} object
1280 	 * @param ere_obj a {@link java.lang.Object} object
1281 	 * @param sb a {@link java.lang.StringBuffer} object
1282 	 * @param convfmt a {@link java.lang.String} object
1283 	 * @param locale a {@link java.util.Locale} object
1284 	 * @return a {@link java.lang.Integer} object
1285 	 */
1286 	public static Integer replaceAll(Object orig_value_obj, Object repl_obj, Object ere_obj, StringBuffer sb, String convfmt, Locale locale) {
1287 		String orig_value = toAwkString(orig_value_obj, convfmt, locale);
1288 		String repl = toAwkString(repl_obj, convfmt, locale);
1289 		String ere = toAwkString(ere_obj, convfmt, locale);
1290 		// remove special meaning for backslash and dollar signs
1291 		repl = Matcher.quoteReplacement(repl);
1292 		sb.setLength(0);
1293 
1294 		Pattern p = Pattern.compile(ere);
1295 		Matcher m = p.matcher(orig_value);
1296 		int cnt = 0;
1297 		while (m.find()) {
1298 			++cnt;
1299 			m.appendReplacement(sb, repl);
1300 		}
1301 		m.appendTail(sb);
1302 		return Integer.valueOf(cnt);
1303 	}
1304 
1305 	/**
1306 	 * <p>substr.</p>
1307 	 *
1308 	 * @param startpos_obj a {@link java.lang.Object} object
1309 	 * @param str a {@link java.lang.String} object
1310 	 * @return a {@link java.lang.String} object
1311 	 */
1312 	public static String substr(Object startpos_obj, String str) {
1313 		int startpos = (int) toDouble(startpos_obj);
1314 		if (startpos <= 0) {
1315 			throw new AwkRuntimeException("2nd arg to substr must be a positive integer");
1316 		}
1317 		if (startpos > str.length()) {
1318 			return "";
1319 		} else {
1320 			return str.substring(startpos - 1);
1321 		}
1322 	}
1323 
1324 	/**
1325 	 * <p>substr.</p>
1326 	 *
1327 	 * @param size_obj a {@link java.lang.Object} object
1328 	 * @param startpos_obj a {@link java.lang.Object} object
1329 	 * @param str a {@link java.lang.String} object
1330 	 * @return a {@link java.lang.String} object
1331 	 */
1332 	public static String substr(Object size_obj, Object startpos_obj, String str) {
1333 		int startpos = (int) toDouble(startpos_obj);
1334 		if (startpos <= 0) {
1335 			throw new AwkRuntimeException("2nd arg to substr must be a positive integer");
1336 		}
1337 		if (startpos > str.length()) {
1338 			return "";
1339 		}
1340 		int size = (int) toDouble(size_obj);
1341 		if (size < 0) {
1342 			throw new AwkRuntimeException("3nd arg to substr must be a non-negative integer");
1343 		}
1344 		if (startpos + size > str.length()) {
1345 			return str.substring(startpos - 1);
1346 		} else {
1347 			return str.substring(startpos - 1, startpos + size - 1);
1348 		}
1349 	}
1350 
1351 	/**
1352 	 * <p>timeSeed.</p>
1353 	 *
1354 	 * @return a int
1355 	 */
1356 	public static int timeSeed() {
1357 		long l = (new Date()).getTime();
1358 		long l2 = (l % (1000 * 60 * 60 * 24));
1359 		int seed = (int) l2;
1360 		return seed;
1361 	}
1362 
1363 	/**
1364 	 * <p>newRandom.</p>
1365 	 *
1366 	 * @param seed a int
1367 	 * @return a {@link java.util.Random} object
1368 	 */
1369 	public static Random newRandom(int seed) {
1370 		return new Random(seed);
1371 	}
1372 
1373 	/**
1374 	 * <p>applyRS.</p>
1375 	 *
1376 	 * @param rs_obj a {@link java.lang.Object} object
1377 	 */
1378 	public void applyRS(Object rs_obj) {
1379 //	if (rs_obj.toString().equals(BLANK))
1380 //		rs_obj = DEFAULT_RS_REGEX;
1381 		if (partitioningReader != null) {
1382 			partitioningReader.setRecordSeparator(rs_obj.toString());
1383 		}
1384 	}
1385 }