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 }