AI2 Component  (Version nb184)
YailDictionary.java
Go to the documentation of this file.
1 // -*- mode: java; c-basic-offset: 2; -*-
2 // Copyright 2019-2020 MIT, All rights reserved
3 // Released under the Apache License, Version 2.0
4 // http://www.apache.org/licenses/LICENSE-2.0
5 
6 package com.google.appinventor.components.runtime.util;
7 
8 import android.util.Log;
9 import androidx.annotation.NonNull;
13 import gnu.lists.FString;
14 import gnu.lists.LList;
15 
16 import java.util.ArrayList;
17 import java.util.Collection;
18 import java.util.Collections;
19 import java.util.Iterator;
20 import java.util.LinkedHashMap;
21 import java.util.List;
22 import java.util.Map;
23 import org.json.JSONException;
24 
32 public class YailDictionary extends LinkedHashMap<Object, Object>
33  implements YailObject<YailList> {
34 
35  private static final String LOG_TAG = "YailDictionary";
36  public static final Object ALL = new Object() {
37  @Override
38  public String toString() {
39  return "ALL_ITEMS";
40  }
41  };
42 
46  public YailDictionary() {
47  super();
48  }
49 
51  super(prevMap);
52  }
53 
57  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
58  public static YailDictionary makeDictionary() {
59  return new YailDictionary();
60  }
61 
70  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
71  public static YailDictionary makeDictionary(Map<Object, Object> prevMap) {
72  return new YailDictionary(prevMap);
73  }
74 
99  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
100  public static YailDictionary makeDictionary(Object... keysAndValues) {
101  if (keysAndValues.length % 2 == 1) {
102  throw new IllegalArgumentException("Expected an even number of key-value entries.");
103  }
104  YailDictionary dict = new YailDictionary();
105  for (int i = 0; i < keysAndValues.length; i += 2) {
106  dict.put(keysAndValues[i], keysAndValues[i + 1]);
107  }
108  return dict;
109  }
110 
118  @SuppressWarnings({"unused", "WeakerAccess"}) // Called from runtime.scm
119  public static YailDictionary makeDictionary(List<YailList> pairs) {
120  Map<Object, Object> map = new LinkedHashMap<>();
121 
122  for (YailList currentYailList : pairs) {
123  Object currentKey = currentYailList.getObject(0);
124  Object currentValue = currentYailList.getObject(1);
125 
126  if (currentValue instanceof YailList) {
127  if (isAlist((YailList) currentValue)) {
128  map.put(currentKey, alistToDict((YailList) currentValue));
129  } else {
130  map.put(currentKey, checkList((YailList) currentValue));
131  }
132  } else {
133  map.put(currentKey, currentValue);
134  }
135  }
136 
137  return new YailDictionary(map);
138  }
139 
140  private static Boolean isAlist(YailList yailList) {
141  boolean hadPair = false;
142 
143  for (Object currentPair : ((LList) yailList.getCdr())) {
144  if (!(currentPair instanceof YailList)) {
145  return false;
146  }
147 
148  if (((YailList) currentPair).size() != 2) {
149  return false;
150  }
151 
152  hadPair = true;
153  }
154 
155  return hadPair;
156  }
157 
158  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
159  public static YailDictionary alistToDict(YailList alist) {
160  LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
161 
162  for (Object o : ((LList) alist.getCdr())) {
163  YailList currentPair = (YailList) o;
164 
165  Object currentKey = currentPair.getObject(0);
166  Object currentValue = currentPair.getObject(1);
167 
168  if (currentValue instanceof YailList && isAlist((YailList) currentValue)) {
169  map.put(currentKey, alistToDict((YailList) currentValue));
170  } else {
171  if (currentValue instanceof YailList) {
172  map.put(currentKey, checkList((YailList) currentValue));
173  } else {
174  map.put(currentKey, currentValue);
175  }
176  }
177  }
178 
179  return new YailDictionary(map);
180  }
181 
182  private static YailList checkList(YailList list) {
183  Object[] checked = new Object[list.size()];
184  int i = 0;
185  Iterator<?> it = list.iterator();
186  it.next(); // skip *list* symbol
187  boolean processed = false;
188  while (it.hasNext()) {
189  Object o = it.next();
190  if (o instanceof YailList) {
191  if (isAlist((YailList) o)) {
192  checked[i] = alistToDict((YailList) o);
193  processed = true;
194  } else {
195  checked[i] = checkList((YailList) o);
196  if (checked[i] != o) { // identity is used to determine whether the list contained an alist
197  processed = true;
198  }
199  }
200  } else {
201  checked[i] = o;
202  }
203  i++;
204  }
205  if (processed) {
206  return YailList.makeList(checked);
207  } else {
208  return list; // nothing has changed
209  }
210  }
211 
212  private static YailList checkListForDicts(YailList list) {
213  List<Object> copy = new ArrayList<>();
214  for (Object o : (LList) list.getCdr()) {
215  if (o instanceof YailDictionary) {
216  copy.add(dictToAlist((YailDictionary) o));
217  } else if (o instanceof YailList) {
218  copy.add(checkListForDicts((YailList) o));
219  } else {
220  copy.add(o);
221  }
222  }
223  return YailList.makeList(copy);
224  }
225 
226  @SuppressWarnings({"unused", "WeakerAccess"}) // Called from runtime.scm
227  public static YailList dictToAlist(YailDictionary dict) {
228  List<Object> list = new ArrayList<>();
229  for (Map.Entry<Object, Object> entry : dict.entrySet()) {
230  list.add(YailList.makeList(new Object[] {entry.getKey(), entry.getValue()}));
231  }
232  return YailList.makeList(list);
233  }
234 
235  public void setPair(YailList pair) {
236  this.put(pair.getObject(0), pair.getObject(1));
237  }
238 
239  private Object getFromList(List<?> target, Object currentKey) {
240  int offset = target instanceof YailList ? 0 : 1;
241  try {
242  if (currentKey instanceof FString) {
243  return target.get(Integer.parseInt(currentKey.toString()) - offset);
244  } else if (currentKey instanceof String) {
245  return target.get(Integer.parseInt((String) currentKey) - offset);
246  } else if (currentKey instanceof Number) {
247  return target.get(((Number) currentKey).intValue() - offset);
248  }
249  } catch (NumberFormatException e) {
250  Log.w(LOG_TAG, "Unable to parse key as integer: " + currentKey, e);
251  throw new YailRuntimeError("Unable to parse key as integer: " + currentKey,
252  "NumberParseException");
253  } catch (IndexOutOfBoundsException e) {
254  Log.w(LOG_TAG, "Requested too large of an index: " + currentKey, e);
255  throw new YailRuntimeError("Requested too large of an index: " + currentKey,
256  "IndexOutOfBoundsException");
257  }
258  return null;
259  }
260 
261  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
262  public Object getObjectAtKeyPath(List<?> keysOrIndices) {
263  Object target = this;
264 
265  for (Object currentKey : keysOrIndices) {
266  if (target instanceof Map) {
267  target = ((Map<?, ?>) target).get(currentKey);
268  } else if (target instanceof YailList && isAlist((YailList) target)) {
269  target = alistToDict((YailList) target).get(currentKey);
270  } else if (target instanceof List) {
271  target = getFromList((List<?>) target, currentKey);
272  } else {
273  return null;
274  }
275  }
276 
277  return target;
278  }
279 
280  private static Collection<Object> allOf(Map<Object, Object> map) {
281  return map.values();
282  }
283 
284  @SuppressWarnings("unchecked") // Kawa is compiled without generics for Java 5
285  private static Collection<Object> allOf(List<Object> list) {
286  if (list instanceof YailList) {
287  if (isAlist((YailList) list)) {
288  ArrayList<Object> result = new ArrayList<>();
289  for (Object o : (LList) ((YailList) list).getCdr()) {
290  result.add(((YailList) o).getObject(1));
291  }
292  return result;
293  } else {
294  return (Collection<Object>) ((YailList) list).getCdr();
295  }
296  }
297  return list;
298  }
299 
300  @SuppressWarnings("unchecked") // everything extends Object
301  private static Collection<Object> allOf(Object object) {
302  if (object instanceof Map) {
303  return allOf((Map<Object, Object>) object);
304  } else if (object instanceof List) {
305  return allOf((List<Object>) object);
306  } else {
307  return Collections.emptyList();
308  }
309  }
310 
311  private static Object alistLookup(YailList alist, Object target) {
312  for (Object o : (LList) alist.getCdr()) {
313  if (o instanceof YailList) {
314  Object key = ((YailList) o).getObject(0);
315  if (key.equals(target)) {
316  return ((YailList) o).getObject(1);
317  }
318  } else {
319  return null;
320  }
321  }
322  return null;
323  }
324 
325  private static <T> List<Object> walkKeyPath(Object root, List<T> keysOrIndices,
326  List<Object> result) {
327  if (keysOrIndices.isEmpty()) {
328  if (root != null) {
329  result.add(root);
330  }
331  return result;
332  } else if (root == null) {
333  return result;
334  }
335 
336  Object currentKey = keysOrIndices.get(0);
337  List<T> childKeys = keysOrIndices.subList(1, keysOrIndices.size());
338  if (currentKey == ALL) {
339  for (Object child : allOf(root)) {
340  walkKeyPath(child, childKeys, result);
341  }
342  } else if (root instanceof Map) {
343  walkKeyPath(((Map<?, ?>) root).get(currentKey), childKeys, result);
344  } else if (root instanceof YailList && isAlist((YailList) root)) {
345  Object value = alistLookup((YailList) root, currentKey);
346  if (value != null) {
347  walkKeyPath(value, childKeys, result);
348  }
349  } else if (root instanceof List) {
350  int index = keyToIndex((List<?>) root, currentKey);
351  try {
352  walkKeyPath(((List<?>) root).get(index), childKeys, result);
353  } catch (Exception e) {
354  // Suppressed, as we are walking the tree and other paths might match.
355  }
356  }
357  return result;
358  }
359 
360  @SuppressWarnings("WeakerAccess") // called from runtime.scm
361  public static <T> List<Object> walkKeyPath(YailObject<?> object, List<T> keysOrIndices) {
362  return walkKeyPath(object, keysOrIndices, new ArrayList<>());
363  }
364 
365  private static int keyToIndex(List<?> target, Object key) {
366  int offset = target instanceof YailList ? 0 : 1;
367  int index;
368  if (key instanceof Number) {
369  index = ((Number) key).intValue();
370  } else {
371  try {
372  index = Integer.parseInt(key.toString());
373  } catch (NumberFormatException e) {
374  throw new DispatchableError(ErrorMessages.ERROR_NUMBER_FORMAT_EXCEPTION,
375  key.toString());
376  }
377  }
378  index -= offset;
379  if (index < 0 || index >= target.size() + 1 - offset) {
380  try {
381  throw new DispatchableError(ErrorMessages.ERROR_INDEX_MISSING_IN_LIST,
382  index + offset, JsonUtil.getJsonRepresentation(target));
383  } catch (JSONException e) {
384  // We just parsed this...
385  Log.e(LOG_TAG, "Unable to serialize object as JSON", e);
386  throw new YailRuntimeError(e.getMessage(), "JSON Error");
387  }
388  }
389  return index;
390  }
391 
392  private Object lookupTargetForKey(Object target, Object key) {
393  if (target instanceof YailDictionary) {
394  return ((YailDictionary) target).get(key);
395  } else if (target instanceof List) {
396  return ((List<?>) target).get(keyToIndex((List<?>) target, key));
397  } else {
398  throw new DispatchableError(ErrorMessages.ERROR_INVALID_VALUE_IN_PATH,
399  target == null ? "null" : target.getClass().getSimpleName());
400  }
401  }
402 
468  @SuppressWarnings("WeakerAccess") // Called from runtime.scm
469  public void setValueForKeyPath(List<?> keys, Object value) {
470  Object target = this;
471  Iterator<?> it = keys.iterator();
472 
473  // Updating with an empty key path is a no-op as there isn't a path to update.
474  if (keys.isEmpty()) {
475  return;
476  }
477 
478  while (it.hasNext()) {
479  Object key = it.next();
480  if (it.hasNext()) {
481  // More keys to go
482  target = lookupTargetForKey(target, key);
483  } else {
484  if (target instanceof YailDictionary) {
485  ((YailDictionary) target).put(key, value);
486  } else if (target instanceof YailList) {
487  LList l = (LList) target;
488  l.getIterator(keyToIndex((List<?>) target, key)).set(value);
489  } else if (target instanceof List) {
490  //noinspection unchecked
491  ((List) target).set(keyToIndex((List<?>) target, key), value);
492  } else {
494  }
495  }
496  }
497  }
498 
499  @Override
500  public boolean containsKey(Object key) {
501  if (key instanceof FString) {
502  return super.containsKey(key.toString());
503  }
504  return super.containsKey(key);
505  }
506 
507  @Override
508  public boolean containsValue(Object value) {
509  if (value instanceof FString) {
510  return super.containsValue(value.toString());
511  }
512  return super.containsValue(value);
513  }
514 
515  @Override
516  public Object get(Object key) {
517  if (key instanceof FString) {
518  return super.get(key.toString());
519  }
520  return super.get(key);
521  }
522 
523  @Override
524  public Object put(Object key, Object value) {
525  if (key instanceof FString) {
526  key = key.toString();
527  }
528  if (value instanceof FString) {
529  value = value.toString();
530  }
531  return super.put(key, value);
532  }
533 
534  @Override
535  public Object remove(Object key) {
536  if (key instanceof FString) {
537  return super.remove(key.toString());
538  }
539  return super.remove(key);
540  }
541 
542  @Override
543  public String toString() {
544  try {
545  return JsonUtil.getJsonRepresentation(this);
546  } catch (JSONException e) {
547  throw new YailRuntimeError(e.getMessage(), "JSON Error");
548  }
549  }
550 
551  @Override
552  public Object getObject(int index) {
553  if (index < 0 || index >= size()) {
554  throw new IndexOutOfBoundsException();
555  }
556  int i = index;
557  for (Map.Entry<Object, Object> e : entrySet()) {
558  if (i == 0) {
559  return Lists.newArrayList(e.getKey(), e.getValue());
560  }
561  i--;
562  }
563  // We shouldn't get here as the check at the beginning of the function
564  // should have already covered this (and we aren't concurrently editable).
565  throw new IndexOutOfBoundsException();
566  }
567 
568  @NonNull
569  @Override
570  public Iterator<YailList> iterator() {
571  return new DictIterator(entrySet().iterator());
572  }
573 
574  private static class DictIterator implements Iterator<YailList> {
575 
576  final Iterator<Map.Entry<Object, Object>> it;
577 
578  DictIterator(Iterator<Map.Entry<Object, Object>> it) {
579  this.it = it;
580  }
581 
582  @Override
583  public boolean hasNext() {
584  return it.hasNext();
585  }
586 
587  @Override
588  public YailList next() {
589  Map.Entry<Object, Object> e = it.next();
590  return YailList.makeList(new Object[] { e.getKey(), e.getValue() });
591  }
592 
593  @Override
594  public void remove() {
595  it.remove();
596  }
597  }
598 }
com.google.appinventor.components.runtime.util.YailDictionary.get
Object get(Object key)
Definition: YailDictionary.java:516
com.google.appinventor.components.runtime.util.YailList
Definition: YailList.java:26
com.google.appinventor.components.runtime.util.YailObject< YailList >::size
int size()
com.google.appinventor.components.runtime.errors.DispatchableError
Definition: DispatchableError.java:12
com.google.appinventor.components.runtime.util.ErrorMessages
Definition: ErrorMessages.java:17
com.google.appinventor.components.runtime.util.YailDictionary.setPair
void setPair(YailList pair)
Definition: YailDictionary.java:235
com.google.appinventor.components.runtime.util.YailDictionary.YailDictionary
YailDictionary(Map< Object, Object > prevMap)
Definition: YailDictionary.java:50
com.google.appinventor.components
com.google.appinventor.components.runtime.util.YailList.makeList
static YailList makeList(Object[] objects)
Definition: YailList.java:59
com.google.appinventor.components.runtime.util.JsonUtil
Definition: JsonUtil.java:42
com.google.appinventor.components.runtime.util.YailDictionary.containsKey
boolean containsKey(Object key)
Definition: YailDictionary.java:500
com.google.appinventor.components.runtime.util.YailDictionary.toString
String toString()
Definition: YailDictionary.java:543
com.google.appinventor.components.runtime.util.YailDictionary.setValueForKeyPath
void setValueForKeyPath(List<?> keys, Object value)
Definition: YailDictionary.java:469
com.google.appinventor.components.runtime.util.ErrorMessages.ERROR_INVALID_VALUE_IN_PATH
static final int ERROR_INVALID_VALUE_IN_PATH
Definition: ErrorMessages.java:224
com.google.appinventor.components.runtime.collect
Definition: Lists.java:7
com.google.appinventor.components.runtime.collect.Lists.newArrayList
static< E > ArrayList< E > newArrayList()
Definition: Lists.java:30
com.google.appinventor.components.runtime.util.YailDictionary.makeDictionary
static YailDictionary makeDictionary()
Definition: YailDictionary.java:58
com.google.appinventor.components.runtime.util.YailDictionary.getObjectAtKeyPath
Object getObjectAtKeyPath(List<?> keysOrIndices)
Definition: YailDictionary.java:262
com.google.appinventor.components.runtime.util.YailDictionary.getObject
Object getObject(int index)
Definition: YailDictionary.java:552
com.google.appinventor.components.runtime.util.YailObject.iterator
Iterator< T > iterator()
com.google.appinventor.components.runtime.errors.YailRuntimeError
Definition: YailRuntimeError.java:14
com.google.appinventor.components.runtime.util.YailDictionary.makeDictionary
static YailDictionary makeDictionary(List< YailList > pairs)
Definition: YailDictionary.java:119
com.google.appinventor.components.runtime.util.YailDictionary.dictToAlist
static YailList dictToAlist(YailDictionary dict)
Definition: YailDictionary.java:227
com.google.appinventor.components.runtime
Copyright 2009-2011 Google, All Rights reserved.
Definition: AccelerometerSensor.java:8
com.google.appinventor.components.runtime.util.YailDictionary.containsValue
boolean containsValue(Object value)
Definition: YailDictionary.java:508
com.google.appinventor.components.runtime.Map
Definition: Map.java:84
com.google.appinventor.components.runtime.util.YailDictionary.YailDictionary
YailDictionary()
Definition: YailDictionary.java:46
com.google.appinventor.components.runtime.collect.Lists
Definition: Lists.java:20
com.google.appinventor.components.runtime.util.YailDictionary.iterator
Iterator< YailList > iterator()
Definition: YailDictionary.java:570
com.google.appinventor.components.runtime.util.YailObject
Definition: YailObject.java:19
com.google
com.google.appinventor.components.runtime.util.YailDictionary.alistToDict
static YailDictionary alistToDict(YailList alist)
Definition: YailDictionary.java:159
com
com.google.appinventor.components.runtime.util.YailDictionary
Definition: YailDictionary.java:32
com.google.appinventor.components.runtime.errors
Definition: ArrayIndexOutOfBoundsError.java:7
com.google.appinventor.components.runtime.util.JsonUtil.getJsonRepresentation
static String getJsonRepresentation(Object value)
Definition: JsonUtil.java:246
com.google.appinventor.components.runtime.util.YailDictionary.put
Object put(Object key, Object value)
Definition: YailDictionary.java:524
com.google.appinventor.components.runtime.util.YailList.getObject
Object getObject(int index)
Definition: YailList.java:200
com.google.appinventor.components.runtime.util.YailDictionary.ALL
static final Object ALL
Definition: YailDictionary.java:36
com.google.appinventor.components.runtime.util.YailList.size
int size()
Definition: YailList.java:172
com.google.appinventor