6 package com.google.appinventor.components.runtime.util;
8 import android.util.Log;
9 import androidx.annotation.NonNull;
13 import gnu.lists.FString;
14 import gnu.lists.LList;
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;
23 import org.json.JSONException;
35 private static final String LOG_TAG =
"YailDictionary";
36 public static final Object
ALL =
new Object() {
57 @SuppressWarnings(
"WeakerAccess")
70 @SuppressWarnings(
"WeakerAccess")
99 @SuppressWarnings(
"WeakerAccess")
101 if (keysAndValues.length % 2 == 1) {
102 throw new IllegalArgumentException(
"Expected an even number of key-value entries.");
105 for (
int i = 0; i < keysAndValues.length; i += 2) {
106 dict.
put(keysAndValues[i], keysAndValues[i + 1]);
118 @SuppressWarnings({
"unused",
"WeakerAccess"})
122 for (
YailList currentYailList : pairs) {
123 Object currentKey = currentYailList.getObject(0);
124 Object currentValue = currentYailList.getObject(1);
126 if (currentValue instanceof
YailList) {
127 if (isAlist((
YailList) currentValue)) {
130 map.put(currentKey, checkList((
YailList) currentValue));
133 map.put(currentKey, currentValue);
140 private static Boolean isAlist(
YailList yailList) {
141 boolean hadPair =
false;
143 for (Object currentPair : ((LList) yailList.getCdr())) {
144 if (!(currentPair instanceof
YailList)) {
158 @SuppressWarnings(
"WeakerAccess")
160 LinkedHashMap<Object, Object> map =
new LinkedHashMap<>();
162 for (Object o : ((LList) alist.getCdr())) {
165 Object currentKey = currentPair.
getObject(0);
166 Object currentValue = currentPair.
getObject(1);
171 if (currentValue instanceof
YailList) {
172 map.put(currentKey, checkList((
YailList) currentValue));
174 map.put(currentKey, currentValue);
183 Object[] checked =
new Object[list.
size()];
187 boolean processed =
false;
188 while (it.hasNext()) {
189 Object o = it.next();
195 checked[i] = checkList((
YailList) o);
196 if (checked[i] != o) {
212 private static YailList checkListForDicts(YailList list) {
213 List<Object> copy =
new ArrayList<>();
214 for (Object o : (LList) list.getCdr()) {
217 }
else if (o instanceof YailList) {
218 copy.add(checkListForDicts((YailList) o));
223 return YailList.makeList(copy);
226 @SuppressWarnings({
"unused",
"WeakerAccess"})
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()}));
239 private Object getFromList(List<?> target, Object currentKey) {
240 int offset = target instanceof
YailList ? 0 : 1;
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);
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");
261 @SuppressWarnings(
"WeakerAccess")
263 Object target =
this;
265 for (Object currentKey : keysOrIndices) {
266 if (target instanceof
Map) {
267 target = ((
Map<?, ?>) target).get(currentKey);
270 }
else if (target instanceof List) {
271 target = getFromList((List<?>) target, currentKey);
284 @SuppressWarnings(
"unchecked")
285 private static Collection<Object> allOf(List<Object> list) {
288 ArrayList<Object> result =
new ArrayList<>();
289 for (Object o : (LList) ((
YailList) list).getCdr()) {
294 return (Collection<Object>) ((YailList) list).getCdr();
300 @SuppressWarnings(
"unchecked")
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);
307 return Collections.emptyList();
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)) {
325 private static <T> List<Object> walkKeyPath(Object root, List<T> keysOrIndices,
326 List<Object> result) {
327 if (keysOrIndices.isEmpty()) {
332 }
else if (root ==
null) {
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);
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);
347 walkKeyPath(value, childKeys, result);
349 }
else if (root instanceof List) {
350 int index = keyToIndex((List<?>) root, currentKey);
352 walkKeyPath(((List<?>) root).
get(index), childKeys, result);
353 }
catch (Exception e) {
360 @SuppressWarnings(
"WeakerAccess")
361 public static <T> List<Object> walkKeyPath(
YailObject<?>
object, List<T> keysOrIndices) {
362 return walkKeyPath(
object, keysOrIndices,
new ArrayList<>());
365 private static int keyToIndex(List<?> target, Object key) {
366 int offset = target instanceof
YailList ? 0 : 1;
368 if (key instanceof Number) {
369 index = ((Number) key).intValue();
372 index = Integer.parseInt(key.toString());
373 }
catch (NumberFormatException e) {
374 throw new DispatchableError(ErrorMessages.ERROR_NUMBER_FORMAT_EXCEPTION,
379 if (index < 0 || index >= target.size() + 1 - offset) {
381 throw new DispatchableError(ErrorMessages.ERROR_INDEX_MISSING_IN_LIST,
382 index + offset, JsonUtil.getJsonRepresentation(target));
383 }
catch (JSONException e) {
385 Log.e(LOG_TAG,
"Unable to serialize object as JSON", e);
386 throw new YailRuntimeError(e.getMessage(),
"JSON Error");
392 private Object lookupTargetForKey(Object target, Object key) {
395 }
else if (target instanceof List) {
396 return ((List<?>) target).get(keyToIndex((List<?>) target, key));
398 throw new DispatchableError(ErrorMessages.ERROR_INVALID_VALUE_IN_PATH,
399 target ==
null ?
"null" : target.getClass().getSimpleName());
468 @SuppressWarnings(
"WeakerAccess")
470 Object target =
this;
471 Iterator<?> it = keys.iterator();
474 if (keys.isEmpty()) {
478 while (it.hasNext()) {
479 Object key = it.next();
482 target = lookupTargetForKey(target, key);
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) {
491 ((List) target).set(keyToIndex((List<?>) target, key), value);
501 if (key instanceof FString) {
502 return super.containsKey(key.toString());
504 return super.containsKey(key);
509 if (value instanceof FString) {
510 return super.containsValue(value.toString());
512 return super.containsValue(value);
516 public Object
get(Object key) {
517 if (key instanceof FString) {
518 return super.get(key.toString());
520 return super.get(key);
524 public Object
put(Object key, Object value) {
525 if (key instanceof FString) {
526 key = key.toString();
528 if (value instanceof FString) {
529 value = value.toString();
531 return super.put(key, value);
535 public Object
remove(Object key) {
536 if (key instanceof FString) {
537 return super.remove(key.toString());
539 return super.remove(key);
546 }
catch (JSONException e) {
553 if (index < 0 || index >=
size()) {
554 throw new IndexOutOfBoundsException();
557 for (
Map.Entry<Object, Object> e : entrySet()) {
565 throw new IndexOutOfBoundsException();
571 return new DictIterator(entrySet().
iterator());
574 private static class DictIterator
implements Iterator<YailList> {
576 final Iterator<
Map.Entry<Object, Object>> it;
578 DictIterator(Iterator<
Map.Entry<Object, Object>> it) {
583 public boolean hasNext() {
588 public YailList next() {
589 Map.Entry<Object, Object> e = it.next();
590 return YailList.makeList(
new Object[] { e.getKey(), e.getValue() });
594 public void remove() {