| 972 | } |
| 973 | |
| 974 | @SuppressWarnings("unchecked") |
| 975 | public static void addElementToObject(Object target, Object... args) { |
| 976 | if (target instanceof Map<?, ?> map) { |
| 977 | if (args.length != 2) |
| 978 | throw new IllegalArgumentException("Map requires (key, value)"); |
| 979 | Map<Object, Object> m = (Map<Object, Object>) map; |
| 980 | // ConcurrentHashMap rejects null values. We use it for thread-safe |
| 981 | // shared state (Exchange.options); translate null put → remove |
| 982 | // there since TS `obj[key] = null` is semantically equivalent to |
| 983 | // "key is not set" (safeValue returns null either way). |
| 984 | // Other maps (request payloads, responses) keep explicit null |
| 985 | // entries — many static-request/response tests check for them. |
| 986 | // The synchronized block pairs with objectKeys() above: any reader |
| 987 | // taking the map's monitor sees a stable view of put/remove here. |
| 988 | synchronized (m) { |
| 989 | if (args[1] == null && m instanceof java.util.concurrent.ConcurrentHashMap) { |
| 990 | m.remove(args[0]); |
| 991 | } else { |
| 992 | m.put(args[0], args[1]); |
| 993 | } |
| 994 | } |
| 995 | return; |
| 996 | } |
| 997 | |
| 998 | if (target instanceof List<?> list) { |
| 999 | List<Object> l = (List<Object>) list; |
| 1000 | if (args.length == 1) { |
| 1001 | l.add(args[0]); // append |
| 1002 | return; |
| 1003 | } |
| 1004 | if (args.length == 2 && IsInteger(args[0])) { |
| 1005 | int i = toInt(args[0]); |
| 1006 | if (i < 0 || i > l.size()) { |
| 1007 | throw new IndexOutOfBoundsException("Index " + i + " out of bounds [0," + l.size() + "]"); |
| 1008 | } |
| 1009 | // l.add(i, args[1]); |
| 1010 | if (i == l.size()) { |
| 1011 | l.add(args[1]); // append |
| 1012 | } else { |
| 1013 | l.set(i, args[1]); // overwrite existing |
| 1014 | } |
| 1015 | return; |
| 1016 | } |
| 1017 | throw new IllegalArgumentException( |
| 1018 | "List requires (value) to append or (index(Integer), value) to insert"); |
| 1019 | } |
| 1020 | |
| 1021 | // Fallback: set field via reflection for arbitrary objects (e.g., WsOrderBook). |
| 1022 | // Sync on the target so a sequence of writes (e.g. timestamp + datetime updated |
| 1023 | // together by an exchange's handler) appears atomic to readers that take the |
| 1024 | // same monitor (see WsOrderBook.toMap, which synchronizes on `this`). Without |
| 1025 | // it, apex/gate orderbooks showed datetime drift / null because the two writes |
| 1026 | // weren't memory-visible together. |
| 1027 | if (args.length == 2 && args[0] instanceof String fieldName) { |
| 1028 | synchronized (target) { |
| 1029 | try { |
| 1030 | java.lang.reflect.Field field = target.getClass().getField(fieldName); |
| 1031 | field.setAccessible(true); |