
org.gradle.internal.logging.text.TreeFormatter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2018 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.internal.logging.text;
import org.apache.commons.lang.StringUtils;
import org.gradle.util.TextUtil;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* Constructs a tree of diagnostic messages.
*/
public class TreeFormatter implements DiagnosticsVisitor {
private final StringBuilder buffer = new StringBuilder();
private final AbstractStyledTextOutput original;
private Node current;
public TreeFormatter() {
this.original = new AbstractStyledTextOutput() {
@Override
protected void doAppend(String text) {
buffer.append(text);
}
};
this.current = new Node();
}
@Override
public String toString() {
return buffer.toString();
}
/**
* Starts a new node with the given text.
*/
public TreeFormatter node(String text) {
if (current.state == State.TraverseChildren) {
// First child node
current = new Node(current, text);
} else {
// A sibling node
current.state = State.Done;
current = new Node(current.parent, text);
}
if (current.isTopLevelNode()) {
// A new top level node, implicitly finish the previous node
if (current != current.parent.firstChild) {
// Not the first top level node
original.append(TextUtil.getPlatformLineSeparator());
}
original.append(text);
current.valueWritten = true;
}
return this;
}
/**
* Starts a new node with the given type name.
*/
public void node(Class> type) {
// Implementation is currently dumb, can be made smarter
node(StringUtils.capitalize(type.toString()));
}
/**
* Appends text to the current node.
*/
public void append(CharSequence text) {
if (current.state == State.CollectValue) {
current.value.append(text);
if (current.valueWritten) {
original.append(text);
}
} else {
throw new IllegalStateException("Cannot append text to node.");
}
}
/**
* Appends a type name to the current node.
*/
public void appendType(Class> type) {
// Implementation is currently dumb, can be made smarter
append(type.toString());
}
/**
* Appends an annotation name to the current node.
*/
public void appendAnnotation(Class extends Annotation> type) {
append("@" + type.getSimpleName());
}
/**
* Appends a method name to the current node.
*/
public void appendMethod(Method method) {
// Implementation is currently dumb, can be made smarter
append(method.getDeclaringClass().getSimpleName());
append(".");
append(method.getName());
append("()");
}
/**
* Appends some user provided value to the current node.
*/
public void appendValue(@Nullable Object value) {
// Implementation is currently dumb, can be made smarter
if (value == null) {
append("null");
} else if (value instanceof String) {
append("'");
append(value.toString());
append("'");
} else {
append(value.toString());
}
}
/**
* Appends some user provided values to the current node.
*/
public void appendValues(Object[] values) {
// Implementation is currently dumb, can be made smarter
append("[");
for (int i = 0; i < values.length; i++) {
Object value = values[i];
if (i > 0) {
append(", ");
}
appendValue(value);
}
append("]");
}
public TreeFormatter startChildren() {
if (current.state == State.CollectValue) {
current.state = State.TraverseChildren;
} else {
throw new IllegalStateException("Cannot start children again");
}
return this;
}
public TreeFormatter endChildren() {
if (current.parent == null) {
throw new IllegalStateException("Not visiting any node.");
}
if (current.state == State.CollectValue) {
current.state = State.Done;
current = current.parent;
}
if (current.state != State.TraverseChildren) {
throw new IllegalStateException("Cannot end children.");
}
if (current.isTopLevelNode()) {
writeNode(current);
}
current.state = State.Done;
current = current.parent;
return this;
}
private void writeNode(Node node) {
if (node.prefix == null) {
node.prefix = node.isTopLevelNode() ? "" : node.parent.prefix + " ";
}
StyledTextOutput output = new LinePrefixingStyledTextOutput(original, node.prefix, false);
if (!node.valueWritten) {
output.append(node.parent.prefix);
output.append(" - ");
output.append(node.value);
}
Separator separator = node.getFirstChildSeparator();
if (!separator.newLine) {
output.append(separator.text);
Node firstChild = node.firstChild;
output.append(firstChild.value);
firstChild.valueWritten = true;
firstChild.prefix = node.prefix;
writeNode(firstChild);
} else if (node.firstChild != null) {
original.append(separator.text);
writeNode(node.firstChild);
}
if (node.nextSibling != null) {
original.append(TextUtil.getPlatformLineSeparator());
writeNode(node.nextSibling);
}
}
private enum State {
CollectValue, TraverseChildren, Done
}
private enum Separator {
NewLine(true, TextUtil.getPlatformLineSeparator()),
Empty(false, " "),
Colon(false, ": "),
ColonNewLine(true, ":" + TextUtil.getPlatformLineSeparator());
Separator(boolean newLine, String text) {
this.newLine = newLine;
this.text = text;
}
final boolean newLine;
final String text;
}
private static class Node {
final Node parent;
final StringBuilder value;
Node firstChild;
Node lastChild;
Node nextSibling;
String prefix;
State state;
boolean valueWritten;
private Node() {
this.parent = null;
this.value = new StringBuilder();
prefix = "";
state = State.TraverseChildren;
}
private Node(Node parent, String value) {
this.parent = parent;
this.value = new StringBuilder(value);
state = State.CollectValue;
if (parent.firstChild == null) {
parent.firstChild = this;
parent.lastChild = this;
} else {
parent.lastChild.nextSibling = this;
parent.lastChild = this;
}
}
Separator getFirstChildSeparator() {
if (firstChild == null) {
return Separator.NewLine;
}
if (value.length() == 0) {
// Always expand empty node
return Separator.NewLine;
}
char trailing = value.charAt(value.length() - 1);
if (trailing == '.') {
// Always expand with trailing .
return Separator.NewLine;
}
if (firstChild.nextSibling == null
&& firstChild.firstChild == null
&& value.length() + firstChild.value.length() < 60) {
// A single leaf node as child and total text is not too long, collapse
if (trailing == ':') {
return Separator.Empty;
}
return Separator.Colon;
}
// Otherwise, expand
if (trailing == ':') {
return Separator.NewLine;
}
return Separator.ColonNewLine;
}
boolean isTopLevelNode() {
return parent.parent == null;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy