View Javadoc
1   package org.sentrysoftware.jawk.jrt;
2   
3   /*-
4    * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
5    * Jawk
6    * ჻჻჻჻჻჻
7    * Copyright (C) 2006 - 2023 Sentry Software
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.HashMap;
27  import java.util.LinkedHashMap;
28  import java.util.Map;
29  import java.util.Set;
30  import java.util.TreeMap;
31  
32  import org.sentrysoftware.jawk.intermediate.UninitializedObject;
33  
34  /**
35   * An AWK associative array.
36   * <p>
37   * The implementation requires the ability to choose,
38   * at runtime, whether the keys are to be maintained in
39   * sorted order or not. Therefore, the implementation
40   * contains a reference to a Map (either TreeMap or
41   * HashMap, depending on whether to maintain keys in
42   * sorted order or not) and delegates calls to it
43   * accordingly.
44   *
45   * @author Danny Daglas
46   */
47  public class AssocArray implements Comparator<Object> {
48  
49  	private Map<Object, Object> map;
50  
51  	/**
52  	 * <p>Constructor for AssocArray.</p>
53  	 *
54  	 * @param sortedArrayKeys Whether keys must be kept sorted
55  	 */
56  	public AssocArray(boolean sortedArrayKeys) {
57  		if (sortedArrayKeys) {
58  			map = new TreeMap<Object, Object>(this);
59  		} else {
60  			map = new HashMap<Object, Object>();
61  		}
62  	}
63  
64  	/**
65  	 * The parameter to useMapType to convert
66  	 * this associative array to a HashMap.
67  	 */
68  	public static final int MT_HASH = 2;
69  	/**
70  	 * The parameter to useMapType to convert
71  	 * this associative array to a LinkedHashMap.
72  	 */
73  	public static final int MT_LINKED = 2 << 1;
74  	/**
75  	 * The parameter to useMapType to convert
76  	 * this associative array to a TreeMap.
77  	 */
78  	public static final int MT_TREE = 2 << 2;
79  
80  	/**
81  	 * Convert the map which backs this associative array
82  	 * into one of HashMap, LinkedHashMap, or TreeMap.
83  	 *
84  	 * @param mapType Can be one of MT_HASH, MT_LINKED,
85  	 *   or MT_TREE.
86  	 */
87  	public void useMapType(int mapType) {
88  		assert map.isEmpty();
89  		switch (mapType) {
90  			case MT_HASH:
91  				map = new HashMap<Object, Object>();
92  				break;
93  			case MT_LINKED:
94  				map = new LinkedHashMap<Object, Object>();
95  				break;
96  			case MT_TREE:
97  				map = new TreeMap<Object, Object>(this);
98  				break;
99  			default:
100 				throw new Error("Invalid map type : " + mapType);
101 		}
102 	}
103 
104 	/**
105 	 * Provide a string representation of the delegated
106 	 * map object.
107 	 * It exists to support the _DUMP keyword.
108 	 *
109 	 * @return string representing the map/array
110 	 */
111 	public String mapString() {
112 		// was:
113 		//return map.toString();
114 		// but since the extensions, assoc arrays can become keys as well
115 		StringBuilder sb = new StringBuilder().append('{');
116 		int cnt = 0;
117 		for (Object o : map.keySet()) {
118 			if (cnt > 0) {
119 				sb.append(", ");
120 			}
121 			if (o instanceof AssocArray) {
122 				sb.append(((AssocArray) o).mapString());
123 			} else {
124 				sb.append(o.toString());
125 			}
126 			//sb.append('=').append(map.get(o));
127 			sb.append('=');
128 			Object o2 = map.get(o);
129 			if (o2 instanceof AssocArray) {
130 				sb.append(((AssocArray) o2).mapString());
131 			} else {
132 				sb.append(o2.toString());
133 			}
134 			++cnt;
135 		}
136 		return sb.append('}').toString();
137 	}
138 
139 	/** a "null" value in Awk */
140 	private static final UninitializedObject BLANK = new UninitializedObject();
141 
142 	/**
143 	 * <p>isIn.</p>
144 	 *
145 	 * @param key Key to be checked
146 	 * @return whether a particular key is
147 	 * contained within the associative array.
148 	 * Unlike get(), which adds a blank (null)
149 	 * reference to the associative array if the
150 	 * element is not found, isIn will not.
151 	 * It exists to support the IN keyword.
152 	 */
153 	public boolean isIn(Object key) {
154 		return map.get(key) != null;
155 	}
156 
157 	/**
158 	 * <p>get.</p>
159 	 *
160 	 * @param key Key to retrieve in the array
161 	 * @return the value of an associative array
162 	 * element given a particular key.
163 	 * If the key does not exist, a null value
164 	 * (blank string) is inserted into the array
165 	 * with this key, and the null value is returned.
166 	 */
167 	public Object get(Object key) {
168 		if (key == null || key instanceof UninitializedObject) {
169 			key = (long)0;
170 		}
171 		Object result = map.get(key);
172 		if (result != null) {
173 			return result;
174 		}
175 
176 		// Did not find it?
177 		try {
178 			// try a integer version key
179 			key = Long.parseLong(key.toString());
180 			result = map.get(key);
181 			if (result != null) {
182 				return result;
183 			}
184 		} catch (Exception e) {}
185 
186 		// based on the AWK specification:
187 		// Any reference (except for IN expressions) to a non-existent
188 		// array element will automatically create it.
189 		result = BLANK;
190 		map.put(key, result);
191 
192 		return result;
193 	}
194 
195 	/**
196 	 * Added to support insertion of primitive key types.
197 	 *
198 	 * @param key Key of the entry to put in the array
199 	 * @param value Value of the key
200 	 * @return the previous value of the specified key, or null if key didn't exist
201 	 */
202 	public Object put(Object key, Object value) {
203 		if (key == null || key instanceof UninitializedObject) {
204 			key = (long)0;
205 		}
206 		try {
207 			// Save a primitive version
208 			long iKey = Long.parseLong(key.toString());
209 			return map.put(iKey, value);
210 		} catch (Exception e) {
211 		}
212 
213 		return map.put(key, value);
214 	}
215 
216 	/**
217 	 * Added to support insertion of primitive key types.
218 	 *
219 	 * @param key Index of the entry to put in the array
220 	 * @param value Value of the key
221 	 * @return the previous value of the specified key, or null if key didn't exist
222 	 */
223 	public Object put(long key, Object value) {
224 		return map.put(key, value);
225 	}
226 
227 	/**
228 	 * <p>keySet.</p>
229 	 *
230 	 * @return the set of keys
231 	 */
232 	public Set<Object> keySet() {
233 		return map.keySet();
234 	}
235 
236 	/**
237 	 * Clear the array
238 	 */
239 	public void clear() {
240 		map.clear();
241 	}
242 
243 	/**
244 	 * Delete the specified entry
245 	 *
246 	 * @param key Key of the entry to remove from the array
247 	 * @return the value of the entry before it was removed
248 	 */
249 	public Object remove(Object key) {
250 		return map.remove(key);
251 	}
252 
253 	/**
254 	 * {@inheritDoc}
255 	 *
256 	 * Do nothing. Should not be called in this state.
257 	 */
258 	@Override
259 	public String toString() {
260 		throw new AwkRuntimeException("Cannot evaluate an unindexed array.");
261 	}
262 
263 	/**
264 	 * {@inheritDoc}
265 	 *
266 	 * Comparator implementation used by the TreeMap
267 	 * when keys are to be maintained in sorted order.
268 	 */
269 	@Override
270 	public int compare(Object o1, Object o2) {
271 
272 		if (o1 instanceof String || o2 instanceof String) {
273 			// use string comparison
274 			String s1 = o1.toString();
275 			String s2 = o2.toString();
276 			return s1.compareTo(s2);
277 		} else {
278 			if (o1 instanceof Double || o2 instanceof Double) {
279 				Double d1 = ((Double) o1);
280 				Double d2 = ((Double) o2);
281 				return d1.compareTo(d2);
282 			} else {
283 				Integer i1 = ((Integer) o1);
284 				Integer i2 = ((Integer) o2);
285 				return i1.compareTo(i2);
286 			}
287 		}
288 	}
289 
290 	/**
291 	 * <p>getMapVersion.</p>
292 	 *
293 	 * @return the specification version of this class
294 	 */
295 	public String getMapVersion() {
296 		return map.getClass().getPackage().getSpecificationVersion();
297 	}
298 }