View Javadoc
1   package io.jawk.jrt;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2026 MetricsHub
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  import java.util.Comparator;
26  import java.util.TreeMap;
27  
28  /**
29   * An AWK associative array backed by a {@link TreeMap}.
30   * <p>
31   * Keys are maintained in sorted order using AWK comparison semantics: numeric
32   * keys are compared numerically, string keys lexicographically, and mixed
33   * key types fall back to string comparison.
34   * </p>
35   *
36   * @author MetricsHub
37   */
38  public class SortedAssocArray extends TreeMap<Object, Object> implements AssocArray {
39  
40  	private static final long serialVersionUID = 1L;
41  
42  	/**
43  	 * AWK-specific key comparator: numeric keys are compared numerically; mixed or
44  	 * string keys fall back to lexicographic string comparison.
45  	 */
46  	private static final Comparator<Object> AWK_COMPARATOR = (o1, o2) -> {
47  		if (o1 instanceof String || o2 instanceof String || !(o1 instanceof Number && o2 instanceof Number)) {
48  			return o1.toString().compareTo(o2.toString());
49  		}
50  		if (o1 instanceof Double || o2 instanceof Double || o1 instanceof Float || o2 instanceof Float) {
51  			return Double.compare(((Number) o1).doubleValue(), ((Number) o2).doubleValue());
52  		}
53  		return Long.compare(((Number) o1).longValue(), ((Number) o2).longValue());
54  	};
55  
56  	/**
57  	 * Creates a new sorted associative array using AWK key ordering.
58  	 */
59  	public SortedAssocArray() {
60  		super(AWK_COMPARATOR);
61  	}
62  
63  	/**
64  	 * Returns the value to which the specified key is mapped, normalizing the key
65  	 * first. If the key does not exist, a blank ({@link io.jawk.intermediate.UninitializedObject})
66  	 * is inserted and returned, as required by AWK semantics.
67  	 *
68  	 * @param key the key whose associated value is to be returned
69  	 * @return the value associated with the key, or a blank value if not found
70  	 */
71  	@Override
72  	public Object get(Object key) {
73  		key = AssocArray.normalizeKey(key);
74  		Object result = super.get(key);
75  		if (result != null) {
76  			return result;
77  		}
78  		Long lKey = AssocArray.toLongKey(key);
79  		if (lKey != null) {
80  			result = super.get(lKey);
81  			if (result != null) {
82  				return result;
83  			}
84  			key = lKey;
85  		}
86  		result = BLANK;
87  		super.put(key, result);
88  		return result;
89  	}
90  
91  	/**
92  	 * Associates the specified value with the specified key, normalizing the key
93  	 * to a {@code Long} when the key is a valid integer string.
94  	 *
95  	 * @param key the key
96  	 * @param value the value
97  	 * @return the previous value associated with the key, or {@code null}
98  	 */
99  	@Override
100 	public Object put(Object key, Object value) {
101 		key = AssocArray.normalizeKey(key);
102 		Long lKey = AssocArray.toLongKey(key);
103 		return super.put(lKey != null ? lKey : key, value);
104 	}
105 
106 	/**
107 	 * Removes the mapping for the specified key, trying both the original and its
108 	 * {@code Long} equivalent.
109 	 *
110 	 * @param key the key whose mapping is to be removed
111 	 * @return the previous value associated with the key, or {@code null}
112 	 */
113 	@Override
114 	public Object remove(Object key) {
115 		key = AssocArray.normalizeKey(key);
116 		Object result = super.remove(key);
117 		if (result != null) {
118 			return result;
119 		}
120 		Long lKey = AssocArray.toLongKey(key);
121 		return lKey != null ? super.remove(lKey) : null;
122 	}
123 
124 	/**
125 	 * Returns the specification version of the underlying {@link TreeMap} class.
126 	 *
127 	 * @return the specification version string, or {@code null} if unavailable
128 	 */
129 	@Override
130 	public String getMapVersion() {
131 		return TreeMap.class.getPackage().getSpecificationVersion();
132 	}
133 
134 	/**
135 	 * {@inheritDoc}
136 	 *
137 	 * @throws AwkRuntimeException always, to prevent accidental use of
138 	 *         {@link TreeMap#toString()} in an AWK evaluation
139 	 *         context
140 	 */
141 	@Override
142 	public String toString() {
143 		throw new AwkRuntimeException("Cannot evaluate an unindexed array.");
144 	}
145 }