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.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} &mdash; backed by {@link java.util.HashMap}</li>
43   * <li>{@link ListAssocArray} &mdash; materialized from a {@link java.util.List}
44   * and backed by {@link java.util.HashMap}</li>
45   * <li>{@link SortedAssocArray} &mdash; 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 }