static String structure(Object o) { new HashSet refd; return structure_2(structure_1(o, new structure_Data(refd)), refd); } // leave to false, unless unstructure() breaks static boolean structure_allowShortening = false; static int structure_shareStringsLongerThan = 20; static class structure_Data { int stringSizeLimit; new IdentityHashMap seen; HashSet refd; new HashMap strings; new HashSet concepts; Class conceptClass = findClass("Concept"); *(HashSet *refd) {} } static String structure_1(Object o, structure_Data d) { if (o == null) return "null"; // these are never back-referenced (for readability) if (o instanceof BigInteger) return "bigint(" + o + ")"; if (o instanceof Double) return "d(" + quote(str(o)) + ")"; if (o instanceof Float) return "fl " + quote(str(o)); if (o instanceof Long) return o + "L"; if (o instanceof Integer) return str(o); if (o instanceof Boolean) return ((Boolean) o).booleanValue() ? "t" : "f"; if (o instanceof Character) ret quoteCharacter((Character) o); if (o instanceof File) ret "File " + quote(((File) o).getPath()); // referencable objects follow Integer ref = d.seen.get(o); if (ref != null) { d.refd.add(ref); return "r" + ref; } if (o instanceof S && (ref = d.strings.get((S) o)) != null) { d.refd.add(ref); return "r" + ref; } ref = d.seen.size()+1; d.seen.put(o, ref); S r = "m" + ref + " "; // marker if (o instanceof S) { S s = d.stringSizeLimit != 0 ? shorten((S) o, d.stringSizeLimit) : (S) o; if (l(s) >= structure_shareStringsLongerThan) d.strings.put(s, ref); return r + quote(s); } String name = o.getClass().getName(); StringBuilder buf = new StringBuilder(); if (o instanceof HashSet) return r + "hashset " + structure_1(new ArrayList((Set) o), d); if (o instanceof TreeSet) return r + "treeset " + structure_1(new ArrayList((Set) o), d); if (o instanceof Collection && neq(name, "main$Concept$RefL")) { for (Object x : (Collection) o) { if (buf.length() != 0) buf.append(", "); buf.append(structure_1(x, d)); } return r + "[" + buf + "]"; } if (o instanceof Map) { for (Object e : ((Map) o).entrySet()) { if (buf.length() != 0) buf.append(", "); buf.append(structure_1(((Map.Entry) e).getKey(), d)); buf.append("="); buf.append(structure_1(((Map.Entry) e).getValue(), d)); } return r + (o instanceof HashMap ? "hm" : "") + "{" + buf + "}"; } if (o.getClass().isArray()) { if (o instanceof byte[]) ret "ba " + quote(bytesToHex((byte[]) o)); int n = Array.getLength(o); if (o instanceof bool[]) { S hex = boolArrayToHex((bool[]) o); int i = l(hex); while (i > 0 && hex.charAt(i-1) == '0' && hex.charAt(i-2) == '0') i -= 2; ret "boolarray " + n + " " + quote(substring(hex, 0, i)); } S atype = "array", sep = ", "; if (o instanceof int[]) { //ret "intarray " + quote(intArrayToHex((int[]) o)); atype = "intarray"; sep = " "; } for (int i = 0; i < n; i++) { if (buf.length() != 0) buf.append(sep); buf.append(structure_1(Array.get(o, i), d)); } return r + atype + "{" + buf + "}"; } if (o instanceof Class) return r + "class(" + quote(((Class) o).getName()) + ")"; if (o instanceof Throwable) return r + "exception(" + quote(((Throwable) o).getMessage()) + ")"; if (o instanceof BitSet) { BitSet bs = (BitSet) o; for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { if (buf.length() != 0) buf.append(", "); buf.append(i); } return "bitset{" + buf + "}"; } // Need more cases? This should cover all library classes... if (name.startsWith("java.") || name.startsWith("javax.")) return r + String.valueOf(o); String shortName = o.getClass().getName().replaceAll("^main\\$", ""); if (shortName.equals("Lisp")) { buf.append("l(" + structure_1(getOpt(o, "head"), d)); L args = cast getOpt(o, "args"); if (nempty(args)) for (int i = 0; i < l(args); i++) { buf.append(", "); O arg = args.get(i); // sweet shortening if (arg != null && eq(arg.getClass().getName(), "main$Lisp") && isTrue(call(arg, "isEmpty"))) arg = get(arg, "head"); buf.append(structure_1(arg, d)); } buf.append(")"); ret r + str(buf); } Class c = o.getClass(); bool concept = d.conceptClass != null && d.conceptClass.isInstance(o); if (concept && !d.concepts.contains(c.getName())) { d.concepts.add(c.getName()); r += "c "; } // serialize an object with fields. // first, collect all fields and values in fv. new TreeMap fv; while (c != Object.class) { for (Field field : c.getDeclaredFields()) { if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue; S fieldName = field.getName(); Object value; try { field.setAccessible(true); value = field.get(o); } catch (Exception e) { value = "?"; } // omit field "className" if equal to class's name if (concept && eq(fieldName, "className") && eq(value, shortName)) value = null; // put special cases here... if (value != null) fv.put(fieldName, value); } c = c.getSuperclass(); } // Now we have fields & values. Process fieldValues if it's a DynamicObject. if (o instanceof DynamicObject) { fv.putAll((Map) fv.get("fieldValues")); fv.remove("fieldValues"); shortName = (S) fv.get("className"); fv.remove("className"); } S singleField = fv.size() == 1 ? first(fv.keySet()) : null; // Render this$1 first because unstructure needs it for constructor call. if (fv.containsKey("this$1")) { buf.append("this$1=" + structure_1(fv.get("this$1"), d)); fv.remove("this$1"); } // Render the other fields. for (S fieldName : fv.keySet()) { if (buf.length() != 0) buf.append(", "); buf.append(fieldName + "=" + structure_1(fv.get(fieldName), d)); } String b = buf.toString(); if (structure_allowShortening && singleField != null) b = b.replaceAll("^" + singleField + "=", ""); // drop field name if only one String s = shortName; if (buf.length() != 0) s += "(" + b + ")"; return r + s; } // drop unused markers static S structure_2(S s, HashSet refd) { L tok = javaTok(s); new StringBuilder out; for (int i = 1; i < l(tok); i += 2) { S t = tok.get(i); if (t.startsWith("m") && isInteger(t.substring(1)) && !refd.contains(parseInt(t.substring(1)))) continue; out.append(t).append(tok.get(i+1)); } ret str(out); }