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.List;
26 import java.util.Map;
27 import io.jawk.intermediate.UninitializedObject;
28
29 /**
30 * An AWK associative array.
31 * <p>
32 * This interface extends {@link Map} and provides AWK-specific behaviour:
33 * automatic key normalization (null and uninitialized values map to {@code ""}),
34 * numeric key coercion ({@code "1"} and {@code 1L} address the same slot), and
35 * auto-creation of blank entries on first access.
36 * </p>
37 * <p>
38 * Concrete implementations directly extend a JDK {@link Map} class to avoid
39 * delegation overhead:
40 * </p>
41 * <ul>
42 * <li>{@link HashAssocArray} — backed by {@link java.util.HashMap}</li>
43 * <li>{@link ListAssocArray} — materialized from a {@link java.util.List}
44 * and backed by {@link java.util.HashMap}</li>
45 * <li>{@link SortedAssocArray} — backed by {@link java.util.TreeMap} with
46 * AWK key ordering</li>
47 * </ul>
48 * <p>
49 * Use the factory methods to create instances:
50 * </p>
51 *
52 * <pre>
53 * AssocArray hash = AssocArray.createHash();
54 * AssocArray sorted = AssocArray.createSorted();
55 * AssocArray list = AssocArray.createFromList(values, sortedArrayKeys);
56 * AssocArray aa = AssocArray.create(sortedArrayKeys);
57 * </pre>
58 *
59 * @author Danny Daglas
60 */
61 public interface AssocArray extends Map<Object, Object> {
62
63 /** A blank (uninitialized) value shared across all AWK array accesses. */
64 UninitializedObject BLANK = new UninitializedObject();
65
66 // -------------------------------------------------------------------------
67 // Key-normalization helpers (used by concrete implementations)
68 // -------------------------------------------------------------------------
69
70 /**
71 * Converts a key to the canonical form expected by AWK: {@code null} and
72 * {@link UninitializedObject} map to the empty string.
73 *
74 * @param key the raw key
75 * @return the normalized key, never {@code null}
76 */
77 static Object normalizeKey(Object key) {
78 return (key == null || key instanceof UninitializedObject) ? "" : key;
79 }
80
81 /**
82 * Attempts to parse the key as a {@code Long}.
83 *
84 * @param key the key to parse (must not be {@code null})
85 * @return the {@code Long} value, or {@code null} if the key cannot be parsed
86 * as a long integer
87 */
88 static Long toLongKey(Object key) {
89 try {
90 return Long.parseLong(key.toString());
91 } catch (Exception e) { // NOPMD - EmptyCatchBlock: intentionally ignored
92 return null;
93 }
94 }
95
96 // -------------------------------------------------------------------------
97 // AWK-specific default methods
98 // -------------------------------------------------------------------------
99
100 /**
101 * Returns whether a particular key is contained within the associative array.
102 * <p>
103 * Unlike {@link #get(Object)}, which auto-creates a blank entry when the key
104 * is absent, this method does not modify the array. It exists to support the
105 * AWK {@code IN} keyword.
106 * </p>
107 *
108 * @param key Key to be checked
109 * @return {@code true} if the key (or its numeric equivalent) is present
110 */
111 default boolean isIn(Object key) {
112 if (key == null || key instanceof UninitializedObject) {
113 // According to AWK semantics, an uninitialized index
114 // evaluates to the empty string, not numeric zero
115 key = "";
116 }
117 if (containsKey(key)) {
118 return true;
119 }
120 try {
121 long iKey = Long.parseLong(key.toString());
122 return containsKey(iKey);
123 } catch (Exception e) { // NOPMD - EmptyCatchBlock: intentionally ignored
124 }
125 return false;
126 }
127
128 /**
129 * Provides a string representation of this associative array, recursively
130 * rendering nested arrays.
131 *
132 * @return a human-readable map string of the form {@code {key=value, ...}}
133 */
134 default String mapString() {
135 // Since extensions allow assoc arrays to become keys as well,
136 // we render nested arrays recursively rather than using toString().
137 StringBuilder sb = new StringBuilder().append('{');
138 int cnt = 0;
139 for (Map.Entry<Object, Object> entry : entrySet()) {
140 if (cnt > 0) {
141 sb.append(", ");
142 }
143 Object key = entry.getKey();
144 if (key instanceof AssocArray) {
145 sb.append(((AssocArray) key).mapString());
146 } else {
147 sb.append(key.toString());
148 }
149 sb.append('=');
150 Object value = entry.getValue();
151 if (value instanceof AssocArray) {
152 sb.append(((AssocArray) value).mapString());
153 } else {
154 sb.append(value.toString());
155 }
156 ++cnt;
157 }
158 return sb.append('}').toString();
159 }
160
161 /**
162 * Stores a value using a primitive {@code long} key, bypassing string parsing.
163 * <p>
164 * This is a convenience overload for callers that already hold a {@code long}
165 * key. The default implementation boxes the key and delegates to
166 * {@link #put(Object, Object)}.
167 * </p>
168 *
169 * @param key the long key
170 * @param value the value to associate with the key
171 * @return the previous value associated with the key, or {@code null}
172 */
173 default Object put(long key, Object value) {
174 return put(Long.valueOf(key), value);
175 }
176
177 /**
178 * Returns the specification version of the underlying JDK {@link Map} class
179 * that backs this implementation.
180 *
181 * @return the specification version string, or {@code null} if unavailable
182 */
183 default String getMapVersion() {
184 return getClass().getSuperclass().getPackage().getSpecificationVersion();
185 }
186
187 // -------------------------------------------------------------------------
188 // Factory methods
189 // -------------------------------------------------------------------------
190
191 /**
192 * Creates a new hash-based associative array (backed by {@link java.util.HashMap}).
193 *
194 * @return a new {@link HashAssocArray}
195 */
196 static AssocArray createHash() {
197 return new HashAssocArray();
198 }
199
200 /**
201 * Creates a new sorted associative array (backed by {@link java.util.TreeMap}
202 * with AWK key ordering).
203 *
204 * @return a new {@link SortedAssocArray}
205 */
206 static AssocArray createSorted() {
207 return new SortedAssocArray();
208 }
209
210 /**
211 * Creates a new associative array of the appropriate type.
212 *
213 * @param sortedArrayKeys {@code true} to create a sorted (tree-backed) array,
214 * {@code false} for a hash-backed array
215 * @return a new {@link AssocArray} instance
216 */
217 static AssocArray create(boolean sortedArrayKeys) {
218 return sortedArrayKeys ? createSorted() : createHash();
219 }
220
221 /**
222 * Creates a new associative array materialized from a Java {@link List}.
223 * <p>
224 * List elements are stored under zero-based {@link Long} keys. Nested
225 * {@link List} values are recursively materialized as associative arrays so
226 * JSON-like object trees can be traversed with AWK array syntax.
227 * </p>
228 *
229 * @param values list values to expose as an AWK array
230 * @param sortedArrayKeys {@code true} to create a sorted array, {@code false}
231 * for a hash-backed array
232 * @return a new {@link AssocArray} containing the list values
233 */
234 static AssocArray createFromList(List<?> values, boolean sortedArrayKeys) {
235 return ListAssocArray.createFromList(values, sortedArrayKeys);
236 }
237
238 /**
239 * Normalizes an externally supplied structured value before the AVM stores it.
240 * <p>
241 * {@link List} values are converted to {@link AssocArray} instances.
242 * {@link Map} values are kept in place to preserve direct-map performance, but
243 * their nested list values are recursively converted.
244 * </p>
245 *
246 * @param value value to normalize
247 * @param sortedArrayKeys {@code true} when converted lists should use sorted
248 * array keys
249 * @return the normalized value
250 */
251 static Object normalizeValue(Object value, boolean sortedArrayKeys) {
252 return ListAssocArray.normalizeValue(value, sortedArrayKeys);
253 }
254
255 }