/** * */ package xapi.dev.source; import xapi.collect.impl.StringStack; import xapi.fu.Coercible; /** * A lightweight utility to assemble strings using a tree of linked nodes, * so you can easily "tear off" a pointer in the buffer, and generate text from multiple locations at once. * * @author James X. Nelson (james@wetheinter.net, @james) */ @SuppressWarnings({"rawtypes", "unchecked"}) public class CharBuffer implements Coercible { protected static final class CharBufferStack extends StringStack<CharBuffer> { } volatile StringBuilder target; protected String indent = ""; CharBufferStack head; CharBufferStack tail; public CharBuffer() { this(new StringBuilder()); } public CharBuffer(final String text) { this(new StringBuilder(text)); } public CharBuffer(final CharBuffer preamble) { this(new StringBuilder()); head.setValue(preamble); } public CharBuffer(final StringBuilder target) { this.target = target; tail = head = new CharBufferStack(); } CharBuffer append(final StringBuilder chars) { target.append(chars); return this; } CharBuffer makeChild(){ final CharBuffer child = newChild(); child.indent = indent; addToEnd(child); return child; } protected CharBuffer newChild() { return new CharBuffer(); } protected CharBuffer newChild(final StringBuilder suffix) { return new CharBuffer(suffix); } public void onAppend() { } public CharBuffer append(final Object obj) { onAppend(); target.append(coerce(obj)); return this; } public CharBuffer append(final String str) { onAppend(); target.append(str); return this; } public void addToBeginning(final CharBuffer buffer) { assert notContained(buffer) : "Infinite recursion!"; final CharBufferStack newHead = new CharBufferStack(); newHead.next = head; newHead.setValue(buffer); head = newHead; } /** * Append the given string, and return a printbuffer to append to this point. * * @param suffix * - The text to append * @return - A buffer pointed at this text, capable of further before/after * branching */ public CharBuffer printAfter(final String suffix) { final CharBuffer buffer = newChild(new StringBuilder(suffix)); addToEnd(buffer); return buffer; } public CharBuffer clear() { tail = head = new CharBufferStack(); target.setLength(0); return this; } public void addToEnd(final CharBuffer buffer) { assert notContained(buffer) : "Infinite recursion! On [" + buffer + "] in " + this; final CharBufferStack newTail = new CharBufferStack(); newTail.setValue(buffer); synchronized (target) { // we synchro on this volatile variable, because we only need the lock if we share a reference. final StringBuilder myTarget = target; newTail.setPrefix(()->myTarget.toString()); target = new StringBuilder(); // everyone else will see this change w/out having to synchro } tail.next = newTail; tail = newTail; } /** * Tests to ensure there is no recursion between nodes. * * Only called when -ea [enable assertions = true] */ private boolean notContained(final CharBuffer buffer) { if (buffer == this) { System.err.println("Trying to add a buffer to itself"); return false; } StringStack<CharBuffer> next = head; while (next != null) { if (next.getValue() == buffer) { System.err.println("Trying to add a buffer that is already a child"); return false; } next = next.next; } next = buffer.head; while (next != null) { if (next.getValue() == this) { System.err.println("Trying to add an ancestor to a child"); return false; } next = next.next; } return true; } public CharBuffer add(Object ... values) { for (Object value : values) { append(value); } return this; } public CharBuffer ln() { append("\n"); return this; } @Override @Deprecated // prefer .toSource(); public final String toString() { return toSource(); } public String toSource() { final StringBuilder body = new StringBuilder(); body.append(head); body.append(target.toString()); return body.toString(); } }