View Javadoc
1   
2   package org.sentrysoftware.jawk.ext;
3   
4   /*-
5    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
6    * Jawk
7    * ჻჻჻჻჻჻
8    * Copyright (C) 2006 - 2023 Sentry Software
9    * ჻჻჻჻჻჻
10   * This program is free software: you can redistribute it and/or modify
11   * it under the terms of the GNU Lesser General Public License as
12   * published by the Free Software Foundation, either version 3 of the
13   * License, or (at your option) any later version.
14   *
15   * This program is distributed in the hope that it will be useful,
16   * but WITHOUT ANY WARRANTY; without even the implied warranty of
17   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18   * GNU General Lesser Public License for more details.
19   *
20   * You should have received a copy of the GNU General Lesser Public
21   * License along with this program.  If not, see
22   * <http://www.gnu.org/licenses/lgpl-3.0.html>.
23   * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
24   */
25  
26  import java.io.File;
27  import java.text.SimpleDateFormat;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.Date;
31  import java.util.HashMap;
32  import java.util.Iterator;
33  import java.util.Map;
34  
35  import org.sentrysoftware.jawk.NotImplementedError;
36  import org.sentrysoftware.jawk.jrt.AssocArray;
37  import org.sentrysoftware.jawk.jrt.AwkRuntimeException;
38  import org.sentrysoftware.jawk.jrt.BlockObject;
39  import org.sentrysoftware.jawk.jrt.IllegalAwkArgumentException;
40  import org.sentrysoftware.jawk.jrt.JRT;
41  import org.sentrysoftware.jawk.jrt.VariableManager;
42  import org.sentrysoftware.jawk.util.AwkLogger;
43  import org.slf4j.Logger;
44  
45  /**
46   * Extensions which make developing in Jawk and
47   * interfacing other extensions with Jawk
48   * much easier.
49   * <p>
50   * The extension functions which are available are as follows:
51   * <ul>
52   * <li><strong>Array</strong> - <code>Array(array,1,3,5,7,9)</code><br>
53   * Inserts elements into an associative array whose keys
54   * are ordered non-negative integers, and the values
55   * are the arguments themselves. The first argument is
56   * the associative array itself.
57   * <li><strong>Map/HashMap/TreeMap/LinkedMap</strong> - <code>Map(map,k1,v1,k2,v2,...,kN,vN)</code>,
58   * or <code>Map(k1,v1,k2,v2,...,kN,vN)</code>.<br>
59   * Build an associative array with its keys/values as
60   * parameters. The odd parameter count version takes
61   * the map name as the first parameter, while the even
62   * parameter count version returns an anonymous associative
63   * array for the purposes of providing a map by function
64   * call parameter.<br>
65   * Map/HashMap configures the associative array as a
66   * hash map, TreeMap as an ordered map, and LinkedMap
67   * as a map which traverses the key set in order of
68   * insertion.
69   * <li><strong>MapUnion</strong> - <code>MapUnion(map,k1,v1,k2,v2,...,kN,vN)</code><br>
70   * Similar to Map, except that map is not cleared prior
71   * to populating it with key/value pairs from the
72   * parameter list.
73   * <li><strong>MapCopy</strong> - <code>cnt = MapCopy(aaTarget, aaSource)</code><br>
74   * Clears the target associative array and copies the
75   * contents of the source associative array to the
76   * target associative array.
77   * <li><strong>TypeOf</strong> - <code>typestring = TypeOf(item)</code><br>
78   * Returns one of the following depending on the argument:
79   * 	<ul>
80   * 	<li>"String"
81   * 	<li>"Integer"
82   * 	<li>"AssocArray"
83   * 	<li>"Reference" (see below)
84   * 	</ul>
85   * <li><strong>String</strong> - <code>str = String(3)</code><br>
86   * Converts its argument to a String.
87   * Similar to the _STRING extension, but provided
88   * for completeness/normalization.
89   * <li><strong>Double</strong> - <code>dbl = Double(3)</code><br>
90   * Converts its argument to a Double.
91   * Similar to the _DOUBLE extension, but provided
92   * for completeness/normalization.
93   * <li><strong>Halt</strong> - <code>Halt()</code><br>
94   * Similar to exit(), except that END blocks are
95   * not executed if Halt() called before END
96   * block processing.
97   * <li><strong>Timeout</strong> - <code>r = Timeout(300)</code><br>
98   * A blocking function which waits N milliseconds
99   * before unblocking (continuing). This is useful in scripts which
100  * employ blocking, but occasionally needs to break out
101  * of the block to perform some calculation, polling, etc.
102  * <li><strong>Throw</strong> - <code>Throw("this is an awkruntimeexception")</code><br>
103  * Throws an AwkRuntimeException from within the script.
104  * <li><strong>Version</strong> - <code>print Version(aa)</code><br>
105  * Prints the version of the Java class which represents the parameter.
106  * <li><strong>Date</strong> - <code>str = Date()</code><br>
107  * Similar to the Java equivalent : str = new Date().toString();
108  * <li><strong>FileExists</strong> - <code>b = FileExists("/a/b/c")</code><br>
109  * Returns 0 if the file doesn't exist, 1 otherwise.
110  * <li><strong>NewRef[erence]/Dereference/DeRef/Unreference/UnRef/etc.</strong> -
111  * Reference Management Functions. These are described in detail below.
112  * </ul>
113  *
114  * <h2>Reference Management</h2>
115  * AWK's memory model provides only 4 types of variables
116  * for use within AWK scripts:
117  * <ul>
118  * <li>Integer
119  * <li>Double
120  * <li>String
121  * <li>Associative Array
122  * </ul>
123  * Variables can hold any of these types. However, unlike
124  * for scalar types (integer/double/string), AWK applies
125  * the following restrictions with regard to associative
126  * arrays:
127  * <ul>
128  * <li>Associative array assignments (i.e., assocarray1 = associarray2)
129  *	are prohibited.
130  * <li>Functions cannot return associative arrays.
131  * </ul>
132  * These restrictions, while sufficient for AWK, are detrimental
133  * to extensions because associative arrays are excellent vehicles
134  * for configuration and return values for user extensions.
135  * Plus, associative arrays can be overriden, which can be used
136  * to enforce type safety within user extensions. Unfortunately, the
137  * memory model restrictions make using associative arrays in this
138  * capacity very difficult.
139  *
140  * <p>
141  * We attempt to alleviate these difficulties by adding references
142  * to Jawk via the CoreExtension module.
143  * References convert associative arrays into
144  * unique strings called <strong>reference handles</strong>.
145  * Since reference handles are strings, they can be
146  * assigned and returned via AWK functions without restriction.
147  * And, reference handles are then used by other reference extension
148  * functions to perform common associative array operations, such as
149  * associative array cell lookup and assignment, key existence
150  * check, and key iteration.
151  *
152  * <p>
153  * The reference model functions are explained below:
154  * <ul>
155  * <li><strong>NewRef / NewReference</strong> - <code>handle = NewRef(assocarray)</code><br>
156  * Store map into reference cache. Return the unique string handle
157  * for this associative array.
158  * <li><strong>DeRef / Dereference</strong> - <code>val = DeRef(handle, key)</code><br>
159  * Return the cell value of the associative array referenced by the key.
160  * In other words:
161  * <blockquote><pre>
162  * return assocarray[key]</pre></blockquote>
163  * <li><strong>UnRef / Unreference</strong> - <code>UnRef(handle)</code><br>
164  * Eliminate the reference occupied by the reference cache.
165  * <li><strong>InRef</strong> - <code>while(key = InRef(handle)) ...</code><br>
166  * Iterate through the key-set of the associative array
167  * referred to by handle in the reference cache.
168  * This is similar to:
169  * <blockquote><pre>
170  * for (key in assocarray)
171  * 	...</pre></blockquote>
172  * where <code>assocarray</code> is the associative array referred to by
173  * handle in the reference cache.
174  * <br>
175  * <strong>Warning:</strong> unlike the IN keyword, InRef
176  * will maintain state regardless of scope. That is,
177  * if one were to break; out of the while loop above,
178  * the next call to InRef() will be the next anticipated
179  * element of the <code>assoc</code> array.
180  * <li><strong>IsInRef</strong> - <code>b = IsInRef(handle, key)</code><br>
181  * Checks whether the associative array in the reference cache
182  * contains the key. This is similar to:
183  * <blockquote><pre>
184  * if (key in assocarray)
185  *	...</pre></blockquote>
186  * where <code>assocarray</code> is the associative array referred to by
187  * handle in the reference cache.
188  * <li><strong>DumpRefs</strong> - <code>DumpRefs()</code><br>
189  * Dumps the reference cache to stdout.
190  * </ul>
191  *
192  * @author Danny Daglas
193  */
194 public class CoreExtension extends AbstractExtension implements JawkExtension {
195 
196 	private static CoreExtension instance = null; // FIXME Ugly form of singleton implementation (which is ugly by itsself)
197 	private static final Object INSTANCE_LOCK = new Object();
198 	private static final Logger LOG = AwkLogger.getLogger(CoreExtension.class);
199 
200 	private int refMapIdx = 0;
201 	private Map<String, Object> referenceMap = new HashMap<String, Object>();
202 	private Map<AssocArray, Iterator<?>> iterators = new HashMap<AssocArray, Iterator<?>>();
203 	private static final Integer ZERO = Integer.valueOf(0);
204 	private static final Integer ONE = Integer.valueOf(1);
205 	private int waitInt = 0;
206 
207 	// single threaded, so one Date object (unsynchronized) will do
208 	private final Date dateObj = new Date();
209 	private final SimpleDateFormat dateFormat = new SimpleDateFormat();
210 
211 	private final BlockObject timeoutBlocker = new BlockObject() {
212 
213 		@Override
214 		public String getNotifierTag() {
215 			return "Timeout";
216 		}
217 
218 		@Override
219 		public final void block()
220 				throws InterruptedException
221 		{
222 			synchronized (timeoutBlocker) {
223 				timeoutBlocker.wait(waitInt);
224 			}
225 		}
226 	};
227 
228 	/**
229 	 * <p>Constructor for CoreExtension.</p>
230 	 */
231 	public CoreExtension() {
232 		synchronized (INSTANCE_LOCK) {
233 			if (instance == null) {
234 				instance = this;
235 			} else {
236 				LOG.warn("Multiple CoreExtension instances in this VM. Using original instance.");
237 			}
238 		}
239 	}
240 
241 	/** {@inheritDoc} */
242 	@Override
243 	public String getExtensionName() {
244 		return "Core Extension";
245 	}
246 
247 	/** {@inheritDoc} */
248 	@Override
249 	public String[] extensionKeywords() {
250 		return new String[] {
251 				"Array",	// i.e. Array(array,1,3,5,7,9,11)
252 				"Map",		// i.e. Map(assocarray, "hi", "there", "testing", 3, 5, Map("item1", "item2", "i3", 4))
253 				"HashMap",	// i.e. HashMap(assocarray, "hi", "there", "testing", 3, 5, Map("item1", "item2", "i3", 4))
254 				"TreeMap",	// i.e. TreeMap(assocarray, "hi", "there", "testing", 3, 5, Map("item1", "item2", "i3", 4))
255 				"LinkedMap",	// i.e. LinkedMap(assocarray, "hi", "there", "testing", 3, 5, Map("item1", "item2", "i3", 4))
256 				"MapUnion",	// i.e. MapUnion(assocarray, "hi", "there", "testing", 3, 5, Map("item1", "item2", "i3", 4))
257 				"MapCopy",	// i.e. cnt = MapCopy(aaTarget, aaSource)
258 				"TypeOf",	// i.e. typestring = TypeOf(item)
259 				"String",	// i.e. str = String(3)
260 				"Double",	// i.e. dbl = Double(3)
261 				"Halt",		// i.e. Halt()
262 				"Dereference",	// i.e. f(Dereference(r1))
263 				"DeRef",	// i.e. 	(see above, but replace Dereference with DeRef)
264 				"NewReference",	// i.e. ref = NewReference(Map("hi","there"))
265 				"NewRef",	// i.e. 	(see above, but replace Reference with Ref)
266 				"Unreference",	// i.e. b = Unreference(ref)
267 				"UnRef",	// i.e. 	(see above, but replace Unreference with UnRef)
268 				"InRef",	// i.e. while(k = InRef(r2)) [ same as for(k in assocarr) ]
269 				"IsInRef",	// i.e. if (IsInRef(r1, "key")) [ same as if("key" in assocarr) ]
270 				"DumpRefs",	// i.e. DumpRefs()
271 				"Timeout",	// i.e. r = Timeout(300)
272 				"Throw",	// i.e. Throw("this is an awkruntimeexception")
273 				"Version",	// i.e. print Version(aa)
274 
275 				"Date",		// i.e. str = Date()
276 				"FileExists",	// i.e. b = FileExists("/a/b/c")
277 				};
278 	}
279 
280 	/** {@inheritDoc} */
281 	@Override
282 	public int[] getAssocArrayParameterPositions(String extensionKeyword, int numArgs) {
283 		if ((      extensionKeyword.equals("Map")
284 				|| extensionKeyword.equals("HashMap")
285 				|| extensionKeyword.equals("LinkedMap")
286 				|| extensionKeyword.equals("TreeMap")) && ((numArgs % 2) == 1))
287 		{
288 			// first argument of a *Map() function
289 			// must be an associative array
290 			return new int[] {0};
291 		} else if (extensionKeyword.equals("Array")) {
292 			// first argument of Array must be
293 			// an associative array
294 			return new int[] {0};
295 		} else if (extensionKeyword.equals("NewReference")
296 				|| extensionKeyword.equals("NewRef"))
297 		{
298 			if (numArgs == 1) {
299 				return new int[] {0};
300 			} else {
301 				return super.getAssocArrayParameterPositions(extensionKeyword, numArgs);
302 			}
303 		} else {
304 			return super.getAssocArrayParameterPositions(extensionKeyword, numArgs);
305 		}
306 	}
307 
308 	/** {@inheritDoc} */
309 	@Override
310 	public Object invoke(String keyword, Object[] args) {
311 		if        (keyword.equals("Map") || keyword.equals("HashMap")) {
312 			return map(args, getVm(), AssocArray.MT_HASH);
313 		} else if (keyword.equals("LinkedMap")) {
314 			return map(args, getVm(), AssocArray.MT_LINKED);
315 		} else if (keyword.equals("TreeMap")) {
316 			return map(args, getVm(), AssocArray.MT_TREE);
317 		} else if (keyword.equals("MapUnion")) {
318 			return mapUnion(args, getVm(), AssocArray.MT_LINKED);
319 		} else if (keyword.equals("MapCopy")) {
320 			checkNumArgs(args, 2);
321 			return mapCopy(args);
322 		} else if (keyword.equals("Array")) {
323 			return array(args, getVm());
324 		} else if (keyword.equals("TypeOf")) {
325 			checkNumArgs(args, 1);
326 			return typeOf(args[0], getVm());
327 		} else if (keyword.equals("String")) {
328 			checkNumArgs(args, 1);
329 			return toString(args[0], getVm());
330 		} else if (keyword.equals("Double")) {
331 			checkNumArgs(args, 1);
332 			return toDouble(args[0], getVm());
333 		} else if (keyword.equals("Halt")) {
334 			if (args.length == 0) {
335 				Runtime.getRuntime().halt(0);
336 			} else if (args.length == 1) {
337 				Runtime.getRuntime().halt((int) JRT.toDouble(args[0]));
338 			} else {
339 				throw new IllegalAwkArgumentException(keyword + " requires 0 or 1 argument, not " + args.length);
340 			}
341 		} else if (keyword.equals("NewReference") || keyword.equals("NewRef")) {
342 			if (args.length == 1) {
343 				return newReference(args[0]);
344 			} else if (args.length == 3) {
345 				return newReference(toAwkString(args[0]), args[1], args[2]);
346 			} else {
347 				throw new IllegalAwkArgumentException(keyword + " requires 1 or 3 arguments, not " + args.length);
348 			}
349 		} else if (keyword.equals("Dereference") || keyword.equals("DeRef")) {
350 			if (args.length == 1) {
351 				return resolve(dereference(args[0], getVm()), getVm());
352 			} else if (args.length == 2) {
353 				return resolve(dereference(toAwkString(args[0]), args[1], getVm()), getVm());
354 			} else {
355 				throw new IllegalAwkArgumentException(keyword + " requires 1 or 2 arguments, not " + args.length);
356 			}
357 		} else if (keyword.equals("Unreference") || keyword.equals("UnRef")) {
358 			checkNumArgs(args, 1);
359 			return unreference(args[0], getVm());
360 		} else if (keyword.equals("InRef")) {
361 			checkNumArgs(args, 1);
362 			return inref(args[0], getVm());
363 		} else if (keyword.equals("IsInRef")) {
364 			checkNumArgs(args, 2);
365 			return isInRef(args[0], args[1], getVm());
366 		} else if (keyword.equals("DumpRefs")) {
367 			checkNumArgs(args, 0);
368 			dumpRefs();
369 		} else if (keyword.equals("Timeout")) {
370 			checkNumArgs(args, 1);
371 			return timeout((int) JRT.toDouble(args[0]));
372 		} else if (keyword.equals("Throw")) {
373 			throw new AwkRuntimeException(Arrays.toString(args));
374 		} else if (keyword.equals("Version")) {
375 			checkNumArgs(args, 1);
376 			return version(args[0]);
377 		} else if (keyword.equals("Date")) {
378 			if (args.length == 0) {
379 				return date();
380 			} else if (args.length == 1) {
381 				return date(toAwkString(args[0]));
382 			} else {
383 				throw new IllegalAwkArgumentException(keyword + " expects 0 or 1 argument, not " + args.length);
384 			}
385 		} else if (keyword.equals("FileExists")) {
386 			checkNumArgs(args, 1);
387 			return fileExists(toAwkString(args[0]));
388 		} else {
389 			throw new NotImplementedError(keyword);
390 		}
391 		// never reached
392 		return null;
393 	}
394 
395 	private Object resolve(Object arg, VariableManager vm) {
396 
397 		Object obj = arg;
398 		while (true) {
399 			if (obj instanceof AssocArray) {
400 				return obj;
401 			}
402 			String argCheck = toAwkString(obj);
403 			if (referenceMap.get(argCheck) != null) {
404 				obj = referenceMap.get(argCheck);
405 			} else {
406 				return obj;
407 			}
408 		}
409 	}
410 
411 	static String newReference(Object arg) {
412 		if (!(arg instanceof AssocArray)) { // FIXME see other FIXME below
413 			throw new IllegalAwkArgumentException("NewRef[erence] requires an assoc array, not " + arg.getClass().getName());
414 		}
415 
416 		// otherwise, set the reference and return the new key
417 
418 		// get next refmapIdx
419 		int refIdx = instance.refMapIdx++;
420 		// inspect the argument
421 		String argStr;
422 		if (arg instanceof AssocArray) { // FIXME This does not make sense with the FIXME marked line above
423 			argStr = arg.getClass().getName();
424 		} else {
425 			argStr = arg.toString();
426 		}
427 		if (argStr.length() > 63) {
428 			argStr = argStr.substring(0, 60) + "...";
429 		}
430 		// build Reference (scalar) string to this argument
431 		String retval = "@REFERENCE@ " + refIdx + " <" + argStr + ">";
432 		instance.referenceMap.put(retval, arg);
433 		return retval;
434 	}
435 
436 	// this version assigns an assoc array a key/value pair
437 	static Object newReference(String refstring, Object key, Object value) {
438 		AssocArray aa = (AssocArray) instance.referenceMap.get(refstring);
439 		if (aa == null) {
440 			throw new IllegalAwkArgumentException("AssocArray reference doesn't exist.");
441 		}
442 		return aa.put(key, value);
443 	}
444 
445 	// this version assigns an object to a reference
446 	private Object dereference(Object arg, VariableManager vm) {
447 		// return the reference if the arg is a reference key
448 		if (arg instanceof AssocArray) {
449 			throw new IllegalAwkArgumentException("an assoc array cannot be a reference handle");
450 		} else {
451 			String argCheck = toAwkString(arg);
452 			return dereference(argCheck);
453 		}
454 	}
455 
456 	// split this out for static access by other extensions
457 	static Object dereference(String argCheck) {
458 		if (instance.referenceMap.get(argCheck) != null) {
459 			return instance.referenceMap.get(argCheck);
460 		} else {
461 			throw new IllegalAwkArgumentException(argCheck + " not a valid reference");
462 		}
463 	}
464 
465 	// this version assumes an assoc array is stored as a reference,
466 	// and to retrieve the stored value
467 	static Object dereference(String refstring, Object key, VariableManager vm) {
468 		AssocArray aa = (AssocArray) instance.referenceMap.get(refstring);
469 		if (aa == null) {
470 			throw new IllegalAwkArgumentException("AssocArray reference doesn't exist.");
471 		}
472 		if (!(key instanceof AssocArray)) {
473 			// check if key is a reference string!
474 			String keyCheck = instance.toAwkString(key);
475 			if (instance.referenceMap.get(keyCheck) != null) {
476 				// assume it is a reference rather than an assoc array key itself
477 				key = instance.referenceMap.get(keyCheck);
478 			}
479 		}
480 		return aa.get(key);
481 	}
482 
483 	static int unreference(Object arg, VariableManager vm) {
484 		String argCheck = instance.toAwkString(arg);
485 		if (instance.referenceMap.get(argCheck) == null) {
486 			throw new IllegalAwkArgumentException("Not a reference : " + argCheck);
487 		}
488 
489 		instance.referenceMap.remove(argCheck);
490 		assert instance.referenceMap.get(argCheck) == null;
491 		return 1;
492 	}
493 
494 	private Object inref(Object arg, VariableManager vm) {
495 		if (arg instanceof AssocArray) {
496 			throw new IllegalAwkArgumentException("InRef requires a Reference (string) argument, not an assoc array");
497 		}
498 		String argCheck = toAwkString(arg);
499 		if (referenceMap.get(argCheck) == null) {
500 			throw new IllegalAwkArgumentException("Not a reference : " + argCheck);
501 		}
502 		Object o = referenceMap.get(argCheck);
503 		if (!(o instanceof AssocArray)) {
504 			throw new IllegalAwkArgumentException("Reference not an assoc array. ref.class = " + o.getClass().getName());
505 		}
506 
507 		AssocArray aa = (AssocArray) o;
508 
509 		// use an inMap to keep track of existing iterators
510 
511 		//Iterator<Object> iter = iterators.get(aa);
512 		Iterator<?> iter = iterators.get(aa);
513 		if (iter == null) //iterators.put(aa, iter = aa.keySet().iterator());
514 		// without a new Collection, modification to the
515 		// assoc array during iteration causes a ConcurrentModificationException
516 		{
517 			iter = new ArrayList<Object>(aa.keySet()).iterator();
518 			iterators.put(aa, iter);
519 		}
520 
521 		Object retval = null;
522 
523 		while (iter.hasNext()) {
524 			retval = iter.next();
525 			if (retval instanceof String && retval.toString().equals("")) {
526 				throw new AwkRuntimeException("Assoc array key contains a blank string ?!");
527 			}
528 			break;
529 		}
530 
531 		if (retval == null) {
532 			iterators.remove(aa);
533 			retval = "";
534 		}
535 
536 		if (retval instanceof AssocArray) {
537 			// search if item is referred to already
538 			for (String ref : referenceMap.keySet()) {
539 				if (referenceMap.get(ref) == retval) {
540 					return ref;
541 				}
542 			}
543 			// otherwise, return new reference to this item
544 			//return newReference(argCheck, retval);
545 			return newReference(retval);
546 		} else {
547 			return retval;
548 		}
549 	}
550 
551 	private int isInRef(Object ref, Object key, VariableManager vm) {
552 		if (ref instanceof AssocArray) {
553 			throw new IllegalAwkArgumentException("Expecting a reference string for the 1st argument, not an assoc array.");
554 		}
555 		String refstring = toAwkString(ref);
556 		return isInRef(refstring, key);
557 	}
558 
559 	static int isInRef(String refstring, Object key) {
560 		Object o = instance.referenceMap.get(refstring);
561 		if (o == null) {
562 			throw new IllegalAwkArgumentException("Invalid refstring : " + refstring);
563 		}
564 		AssocArray aa = (AssocArray) o;
565 		return aa.isIn(key) ? ONE : ZERO;
566 	}
567 
568 	private void dumpRefs() {
569 		for (Map.Entry<String, Object> entry : referenceMap.entrySet()) {
570 			Object value = entry.getValue();
571 			if (value instanceof AssocArray) {
572 				value = ((AssocArray) value).mapString();
573 			}
574 			LOG.info("REF : {} = {}", new Object[] {entry.getKey(), value});
575 		}
576 	}
577 
578 	static String typeOf(Object arg, VariableManager vm) {
579 		if        (arg instanceof AssocArray) {
580 			return "AssocArray";
581 		} else if (arg instanceof Integer) {
582 			return "Integer";
583 		} else if (arg instanceof Double) {
584 			return "Double";
585 		} else {
586 			String stringRep = instance.toAwkString(arg);
587 			if (instance.referenceMap.get(stringRep) != null) {
588 				return "Reference";
589 			} else {
590 				return "String";
591 			}
592 		}
593 	}
594 
595 	@SuppressWarnings("unused")
596 	private int get(AssocArray retval, AssocArray map, Object key) {
597 		retval.clear();
598 		retval.put(0, map.get(key));
599 		return 1;
600 	}
601 
602 	@SuppressWarnings("unused")
603 	private Object toScalar(AssocArray aa) {
604 		return aa.get(0);
605 	}
606 
607 	private Object map(Object[] args, VariableManager vm, int mapType) {
608 		if (args.length % 2 == 0) {
609 			return subMap(args, vm, mapType);
610 		} else {
611 			return topLevelMap(args, vm, mapType, false);	// false = map assignment
612 		}
613 	}
614 
615 	private Object mapUnion(Object[] args, VariableManager vm, int mapType) {
616 		return topLevelMap(args, vm, mapType, true);	// true = map union
617 	}
618 
619 	private int topLevelMap(Object[] args, VariableManager vm, int mapType, boolean mapUnion) {
620 		AssocArray aa = (AssocArray) args[0];
621 		if (!mapUnion) {
622 			aa.clear();
623 			aa.useMapType(mapType);
624 		}
625 		int cnt = 0;
626 		for (int i = 1; i < args.length; i += 2) {
627 			if (args[i] instanceof AssocArray) {
628 				args[i] = newReference(args[i]);
629 			}
630 			if (args[i + 1] instanceof AssocArray) {
631 				args[i + 1] = newReference(args[i + 1]);
632 			}
633 
634 			aa.put(args[i], args[i + 1]);
635 
636 			++cnt;
637 		}
638 		return cnt;
639 	}
640 
641 	private AssocArray subMap(Object[] args, VariableManager vm, int mapType) {
642 		AssocArray aa = new AssocArray(false);
643 		aa.useMapType(mapType);
644 		for (int i = 0; i < args.length; i += 2) {
645 			if (args[i] instanceof AssocArray) {
646 				args[i] = newReference(args[i]);
647 			}
648 			if (args[i + 1] instanceof AssocArray) {
649 				args[i + 1] = newReference(args[i + 1]);
650 			}
651 
652 			aa.put(args[i], args[i + 1]);
653 		}
654 		return aa;
655 	}
656 
657 	private int array(Object[] args, VariableManager vm) {
658 		AssocArray aa = (AssocArray) args[0];
659 		aa.clear();
660 		aa.useMapType(AssocArray.MT_TREE);
661 		String subsep = toAwkString(vm.getSUBSEP());
662 		int cnt = 0;
663 		for (int i = 1; i < args.length; ++i) {
664 			Object o = args[i];
665 			if (o instanceof AssocArray) {
666 				AssocArray arr = (AssocArray) o;
667 				for (Object key : arr.keySet()) {
668 					aa.put("" + i + subsep + key, arr.get(key));
669 				}
670 			} else {
671 				aa.put("" + i, o);
672 			}
673 			//aa.put(args[i], args[i+1]);
674 			++cnt;
675 		}
676 		return cnt;
677 	}
678 
679 	/*private AssocArray subarray(Object[] args, VariableManager vm) {
680 		AssocArray aa = new AssocArray(false);
681 		aa.clear();
682 		//aa.useLinkedHashMap();
683 		aa.useMapType(AssocArray.MT_TREE);
684 		String subsep = toAwkString(vm.getSUBSEP());
685 		int cnt = 0;
686 		for (int i = 1; i <= args.length; ++i) {
687 			Object o = args[i - 1];
688 			if (o instanceof AssocArray) {
689 				AssocArray arr = (AssocArray) o;
690 				for (Object key : arr.keySet()) {
691 					aa.put("" + i + subsep + key, arr.get(key));
692 				}
693 			} else {
694 				aa.put("" + i, o);
695 			}
696 			//aa.put(args[i], args[i+1]);
697 			++cnt;
698 		}
699 		return aa;
700 	}*/
701 
702 	private int mapCopy(Object[] args) {
703 		AssocArray aaTarget = (AssocArray) args[0];
704 		AssocArray aaSource = (AssocArray) args[1];
705 		aaTarget.clear();
706 		int cnt = 0;
707 		for (Object o : aaSource.keySet()) {
708 			aaTarget.put(o, aaSource.get(o));
709 			++cnt;
710 		}
711 		return cnt;
712 	}
713 
714 	private Object toDouble(Object arg, VariableManager vm) {
715 		if (arg instanceof AssocArray) {
716 			throw new IllegalArgumentException("Cannot deduce double value from an associative array.");
717 		}
718 		if (arg instanceof Number) {
719 			return ((Number) arg).doubleValue();
720 		}
721 
722 		// otherwise, a string
723 
724 		try {
725 			String str = toAwkString(arg);
726 			double d = Double.parseDouble(str);
727 			return d;
728 		} catch (NumberFormatException nfe) {
729 			return "";
730 		}
731 	}
732 
733 	private static String toString(Object arg, VariableManager vm) {
734 		if (arg instanceof AssocArray) {
735 			return ((AssocArray) arg).mapString();
736 		} else {
737 			return instance.toAwkString(arg);
738 		}
739 	}
740 
741 	private Object timeout(int ms) {
742 		if (ms <= 0) {
743 			throw new IllegalAwkArgumentException("Timeout requires a positive # argument, not " + ms + ".");
744 		}
745 		waitInt = ms;
746 		return timeoutBlocker;
747 	}
748 
749 	private String version(Object obj) {
750 		if (obj instanceof AssocArray) {
751 			return ((AssocArray) obj).getMapVersion();
752 		} else {
753 			Class<?> cls = (Class<?>) obj.getClass();
754 			return cls.getPackage().getSpecificationVersion();
755 		}
756 	}
757 
758 	private String date() {
759 		dateObj.setTime(System.currentTimeMillis());
760 		return dateObj.toString();
761 	}
762 
763 	private String date(String formatString) {
764 		dateObj.setTime(System.currentTimeMillis());
765 		dateFormat.applyPattern(formatString);
766 		return dateFormat.format(dateObj);
767 	}
768 
769 	private int fileExists(String path) {
770 		if (new File(path).exists()) {
771 			return ONE;
772 		} else {
773 			return ZERO;
774 		}
775 	}
776 }