static bool structure_showTiming, structure_checkTokenCount; static String structure(Object o) { new structure_Data d; structure_1(o, d); long time = now(); S s = str(d.out); if (structure_checkTokenCount) { print("token count=" + d.n); assertEquals("token count", l(javaTokC(s)), d.n); } ret s; } // leave to false, unless unstructure() breaks static boolean structure_allowShortening = false; static int structure_shareStringsLongerThan = 20; static class structure_Data { new StringBuilder out; int stringSizeLimit; new IdentityHashMap seen; new BitSet refd; new HashMap strings; new HashSet concepts; HashMap> fieldsByClass = new HashMap; Class conceptClass = findClass("Concept"); int n; // token count // append single token structure_Data append(S token) { out.append(token); ++n; ret this; } structure_Data append(int i) { out.append(i); ++n; ret this; } // append multiple tokens structure_Data append(S token, int tokCount) { out.append(token); n += tokCount; ret this; } // extend last token structure_Data app(S token) { out.append(token); ret this; } structure_Data app(int i) { out.append(i); ret this; } } static void structure_1(Object o, structure_Data d) { StringBuilder out = d.out; if (o == null) { d.append("null"); ret; } Class c = o.getClass(); S name = c.getName(); S dynName = shortDynamicClassName(o); bool concept = d.conceptClass != null && d.conceptClass.isInstance(o); L lFields = d.fieldsByClass.get(c); if (lFields == null) { // these are never back-referenced (for readability) if (o instanceof Number) { if (o instanceof Integer) { out.append((int) o); d.n++; ret; } if (o instanceof Long) { out.append((long) o).append("L"); d.n++; ret; } if (o instanceof Float) { d.append("fl ", 2); quote_impl(str(o), out); ret; } if (o instanceof Double) { d.append("d(", 3); quote_impl(str(o), out); d.append(")"); ret; } if (o instanceof BigInteger) { out.append("bigint(").append(o).append(")"); d.n += 4; ret; } } if (o instanceof Boolean) { d.append(((Boolean) o).booleanValue() ? "t" : "f"); ret; } if (o instanceof Character) { d.append(quoteCharacter((Character) o)); ret; } if (o instanceof File) { d.append("File ").append(quote(((File) o).getPath())); ret; } // referencable objects follow Integer ref = d.seen.get(o); if (o instanceof S && ref == null) ref = d.strings.get((S) o); if (ref != null) { d.refd.set(ref); d.append("t").app(ref); ret; } ref = d.n; //d.seen.size()+1; d.seen.put(o, ref); //d.append("m").app(ref).app(" "); // 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); quote_impl(s, out); d.n++; ret; } if (o instanceof HashSet) { d.append("hashset "); structure_1(new ArrayList((Set) o), d); ret; } if (o instanceof TreeSet) { d.append("treeset "); structure_1(new ArrayList((Set) o), d); ret; } if (o instanceof Collection && neq(name, "main$Concept$RefL")) { d.append("["); int l = out.length(); for (Object x : (Collection) o) { if (out.length() != l) d.append(", "); structure_1(x, d); } d.append("]"); ret; } if (o instanceof Map) { if (o instanceof HashMap) d.append("hm"); d.append("{"); int l = out.length(); for (Object e : ((Map) o).entrySet()) { if (out.length() != l) d.append(", "); structure_1(((Map.Entry) e).getKey(), d); d.append("="); structure_1(((Map.Entry) e).getValue(), d); } d.append("}"); ret; } if (c.isArray()) { if (o instanceof byte[]) { d.append("ba ").append(quote(bytesToHex((byte[]) o))); ret; } 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; d.append("boolarray ").append(n).app(" ").append(quote(substring(hex, 0, i))); ret; } S atype = "array", sep = ", "; if (o instanceof int[]) { //ret "intarray " + quote(intArrayToHex((int[]) o)); atype = "intarray"; sep = " "; } d.append(atype).append("{"); for (int i = 0; i < n; i++) { if (i != 0) d.append(sep); structure_1(Array.get(o, i), d); } d.append("}"); ret; } if (o instanceof Class) { d.append("class(", 2).append(quote(((Class) o).getName())).append(")"); ret; } if (o instanceof Throwable) { d.append("exception(", 2).append(quote(((Throwable) o).getMessage())).append(")"); ret; } if (o instanceof BitSet) { BitSet bs = (BitSet) o; d.append("bitset{", 2); int l = out.length(); for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i+1)) { if (out.length() != l) d.append(", "); d.append(i); } d.append("}"); ret; } // Need more cases? This should cover all library classes... if (name.startsWith("java.") || name.startsWith("javax.")) { d.append("j ").append(quote(str(o))); ret; // Hm. this is not unstructure-able } if (name.equals("main$Lisp")) { d.append("l(", 2); structure_1(getOpt(o, "head"), d); L args = cast getOpt(o, "args"); if (nempty(args)) for (int i = 0; i < l(args); i++) { d.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"); structure_1(arg, d); } d.append(")"); ret; } if (concept && !d.concepts.contains(dynName)) { d.concepts.add(dynName); d.append("c "); } // serialize an object with fields. // first, collect all fields and values in fv. TreeSet fields = new TreeSet(new Comparator() { public int compare(Field a, Field b) { ret stdcompare(a.getName(), b.getName()); } }); while (c != Object.class) { for (Field field : getDeclaredFields_cached(c)) { if ((field.getModifiers() & (Modifier.STATIC | Modifier.TRANSIENT)) != 0) continue; S fieldName = field.getName(); fields.add(field); // put special cases here... } c = c.getSuperclass(); } lFields = asList(fields); // put this$1 first for (int i = 0; i < l(lFields); i++) { Field f = lFields.get(i); if (f.getName().equals("this$1")) { lFields.remove(i); lFields.add(0, f); break; } } d.fieldsByClass.put(c, lFields); } new LinkedHashMap fv; for (Field f : lFields) { Object value; try { value = f.get(o); } catch (Exception e) { value = "?"; } if (value != null) fv.put(f.getName(), value); } String shortName = dropPrefix("main$", name); // Now we have fields & values. Process fieldValues if it's a DynamicObject. // omit field "className" if equal to class's name if (concept && eq(fv.get("className"), shortName)) fv.remove("className"); if (o instanceof DynamicObject) { fv.putAll((Map) fv.get("fieldValues")); fv.remove("fieldValues"); shortName = dynName; fv.remove("className"); } S singleField = fv.size() == 1 ? first(fv.keySet()) : null; // Render this$1 first because unstructure needs it for constructor call. d.append(shortName).append("("); int l = out.length(); for (S fieldName : fv.keySet()) { if (out.length() != l) d.append(", "); d.append(fieldName).append("="); structure_1(fv.get(fieldName), d); } if (out.length() == l) { out.setLength(l-1); d.n--; } // drop "(" else d.append(")"); }