package org.dcache.services.info.serialisation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.dcache.services.info.base.BooleanStateValue;
import org.dcache.services.info.base.FloatingPointStateValue;
import org.dcache.services.info.base.IntegerStateValue;
import org.dcache.services.info.base.State;
import org.dcache.services.info.base.StateExhibitor;
import org.dcache.services.info.base.StatePath;
import org.dcache.services.info.base.StateValue;
import org.dcache.services.info.base.StringStateValue;
/**
* Create a pretty-print output of dCache state using ASCII-art.
* <p>
* This output has the advantage of making the tree structure more clear
* (compared to SimpleTextSerialiser) but the disadvantage of taking up more
* space.
*
* @see SimpleTextSerialiser
*/
public class PrettyPrintTextSerialiser extends SubtreeVisitor implements StateSerialiser
{
private static final String ROOT_ELEMENT_LABEL = "dCache";
public static final String NAME = "pretty-print";
private StateExhibitor _exhibitor;
private final Stack<Chunk> _lastChunkStack = new Stack<>();
private final List<Chunk> _pendingChunks = new ArrayList<>();
private StatePath _topMostElement;
private StringBuilder _out;
private boolean _foundSomething;
private boolean _nextChunkHasStalk;
public void setStateExhibitor(StateExhibitor exhibitor)
{
_exhibitor = exhibitor;
}
/**
* Our official name.
*/
@Override
public String getName()
{
return NAME;
}
/**
* Build out pretty-print output.
* <p>
* NB. This method is <i>not</i> thread-safe.
*/
@Override
public String serialise(StatePath path)
{
clearState();
_topMostElement = path;
setVisitScopeToSubtree(path);
_exhibitor.visitState(this);
String output = "";
if (_foundSomething) {
String header = buildHeader(path.toString());
_out.append(header).append("\n");
flushPendingChunks();
output = _out.toString();
}
return output;
}
/**
* Provide serialisation, starting from top-most dCache state.
*/
@Override
public String serialise()
{
clearState();
_topMostElement = null;
setVisitScopeToEverything();
_exhibitor.visitState(this);
String header = buildHeader(null);
_out.append(header).append("\n");
flushPendingChunks();
return _out.toString();
}
public String buildHeader(String path)
{
StringBuilder sb = new StringBuilder();
sb.append("[" + ROOT_ELEMENT_LABEL);
if (path != null) {
sb.append(".").append(path);
}
sb.append("]");
return sb.toString();
}
private void flushPendingChunks()
{
for (Chunk chunk : _pendingChunks) {
String chunkOutput = chunk.getOutput();
_out.append(chunkOutput);
}
}
private void clearState()
{
_out = new StringBuilder();
_pendingChunks.clear();
_foundSomething = false;
_nextChunkHasStalk = true;
_lastChunkStack.clear();
_lastChunkStack.add(new Chunk());
}
private boolean arePathsSame(StatePath p1, StatePath p2)
{
if (p1 == null && p2 == null) {
return true;
}
return (p1 == null) ? false : p1.equals(p2);
}
@Override
public void visitCompositePreDescend(StatePath path,
Map<String, String> metadata)
{
if (!isInsideScope(path)) {
return;
}
_foundSomething = true;
if (arePathsSame(path, _topMostElement)) {
return;
}
addBranchChunk(path.getLastElement(), metadata);
descend();
_nextChunkHasStalk = true;
}
private void addBranchChunk(String name, Map<String, String> metadata)
{
String type = null;
String idName = null;
if (metadata != null) {
type = metadata.get(State.METADATA_BRANCH_CLASS_KEY);
idName = metadata.get(State.METADATA_BRANCH_IDNAME_KEY);
}
EndOfChunkItem item;
if (type != null && idName != null) {
item = new ListItem(_nextChunkHasStalk, type, idName, name);
} else {
item = new BranchItem(_nextChunkHasStalk, name);
}
addSiblingChunk(item);
}
private Chunk getThisBranchLastChunk()
{
return _lastChunkStack.lastElement();
}
private void setThisBranchLastChunk(Chunk chunk)
{
int lastItemIndex = _lastChunkStack.size() - 1;
_lastChunkStack.set(lastItemIndex, chunk);
}
private void addSiblingChunk(EndOfChunkItem item)
{
Chunk siblingChunk = getThisBranchLastChunk();
Chunk thisChunk = siblingChunk.newSiblingChunk(item);
_pendingChunks.add(thisChunk);
setThisBranchLastChunk(thisChunk);
}
private void descend()
{
Chunk siblingChunk = getThisBranchLastChunk();
_lastChunkStack.add(siblingChunk.newPhantomChildChunk());
}
@Override
public void visitCompositePostDescend(StatePath path,
Map<String, String> metadata)
{
if (!isInsideScope(path)) {
return;
}
Chunk lastChunk = getThisBranchLastChunk();
lastChunk.setEndOfList();
ascend();
}
private void ascend()
{
_lastChunkStack.pop();
_nextChunkHasStalk = true;
}
@Override
public void visitBoolean(StatePath path, BooleanStateValue value)
{
addMetricChunk(path, value);
}
@Override
public void visitFloatingPoint(StatePath path,
FloatingPointStateValue value)
{
addMetricChunk(path, value);
}
@Override
public void visitInteger(StatePath path, IntegerStateValue value)
{
addMetricChunk(path, value);
}
@Override
public void visitString(StatePath path, StringStateValue value)
{
addMetricChunk(path, value);
}
private void addMetricChunk(StatePath path, StateValue value)
{
String name = path.getLastElement();
EndOfChunkItem item = new MetricItem(_nextChunkHasStalk, name, value);
addSiblingChunk(item);
_nextChunkHasStalk = false;
}
/**
* This class represents one or two horizontal lines of output. It may
* represent some dCache item (a metric or branch), or is a phantom.
* Phantoms take up no vertical space but introduce a new Stem.
*/
private static class Chunk
{
private static final EndOfChunkItem END_ITEM_FOR_PHANTOM_CHUNK = null;
private final List<Stem> _stems = new ArrayList<>();
private final EndOfChunkItem _endItem;
private Stem _stemForChild;
@SuppressWarnings("unchecked")
public Chunk()
{
this(END_ITEM_FOR_PHANTOM_CHUNK, Collections.<Stem>emptyList());
}
public Chunk(List<Stem> stems)
{
this(END_ITEM_FOR_PHANTOM_CHUNK, stems);
}
public Chunk(EndOfChunkItem item, List<Stem> stems)
{
_endItem = item;
_stems.addAll(stems);
}
public boolean isPhantom()
{
return _endItem == END_ITEM_FOR_PHANTOM_CHUNK;
}
private Stem addNewEndStem()
{
Stem newStem = new Stem();
_stems.add(newStem);
return newStem;
}
public String getOutput()
{
return isPhantom() ? "" : getNonPhantomOutput();
}
private String getNonPhantomOutput()
{
StringBuilder sb = new StringBuilder();
String stemsPrefix = buildStemsPrefix();
for (String endItemLine : _endItem.getOutput()) {
sb.append(stemsPrefix);
sb.append(endItemLine);
}
return sb.toString();
}
private String buildStemsPrefix()
{
StringBuilder sb = new StringBuilder();
for (Stem stem : _stems) {
sb.append(stem.getOutput());
}
return sb.toString();
}
public Chunk newSiblingChunk(EndOfChunkItem item)
{
return new Chunk(item, _stems);
}
public Chunk newPhantomChildChunk()
{
Chunk childChunk = new Chunk(_stems);
_stemForChild = childChunk.addNewEndStem();
return childChunk;
}
public void setEndOfList()
{
if (_stemForChild != null) {
_stemForChild.setInvisable();
}
}
}
/** The Stem class represents the potential to draw a vertical line */
private static class Stem
{
public static final String STEM_ITEM = " | ";
public static final String BLANK_ITEM = " ";
private boolean _visable = true;
public void setInvisable()
{
_visable = false;
}
public String getOutput()
{
return _visable ? STEM_ITEM : BLANK_ITEM;
}
}
/**
* Objects of this class represents something to be displayed at the
* right-hand end of a chunk past the Stems, if any. An EndOfChunkItem
* may have a "stalk": this introduces a one-line spacer between this
* chunk and the previous chunk, where the end-item is displayed with a
* vertical line joining it to the previous end-item.
*/
private abstract static class EndOfChunkItem
{
private final boolean _hasStalk;
public EndOfChunkItem(boolean hasStalk)
{
_hasStalk = hasStalk;
}
public List<String> getOutput()
{
List<String> output = new ArrayList<>();
if (_hasStalk) {
output.add(Stem.STEM_ITEM + "\n");
}
output.add(" +-" + getItemLabel() + "\n");
return output;
}
abstract String getItemLabel();
}
/** Represent a metric as an EndOfChunkItem */
private static class MetricItem extends EndOfChunkItem
{
private final String _label;
public MetricItem(boolean hasStalk, String name, StateValue metric)
{
super(hasStalk);
String value = getValueOfMetric(metric);
String type = metric.getTypeName();
_label = "-" + name + ": " + value + " [" + type + "]";
}
private String getValueOfMetric(StateValue metricValue)
{
String value;
if (metricValue instanceof StringStateValue) {
value = "\"" + metricValue.toString() + "\"";
} else {
value = metricValue.toString();
}
return value;
}
@Override
String getItemLabel()
{
return _label;
}
}
/** Represent a branch as an EndOfChunkItem */
private static class BranchItem extends EndOfChunkItem
{
private final String _label;
public BranchItem(boolean hasStalk, String name)
{
super(hasStalk);
_label = "[" + name + "]";
}
@Override
String getItemLabel()
{
return _label;
}
}
/** Represent a list-item as an EndOfChunkItem */
private static class ListItem extends EndOfChunkItem
{
private final String _label;
public ListItem(boolean hasStalk, String type, String idName, String name)
{
super(hasStalk);
_label = "[" + type + ", " + idName + "=\"" + name + "\"]";
}
@Override
String getItemLabel()
{
return _label;
}
}
}