CSTPrinter.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.core.metapath.cst;

import gov.nist.secauto.metaschema.core.metapath.cst.comparison.GeneralComparison;
import gov.nist.secauto.metaschema.core.metapath.cst.comparison.ValueComparison;
import gov.nist.secauto.metaschema.core.metapath.cst.math.Addition;
import gov.nist.secauto.metaschema.core.metapath.cst.math.Division;
import gov.nist.secauto.metaschema.core.metapath.cst.math.IntegerDivision;
import gov.nist.secauto.metaschema.core.metapath.cst.math.Modulo;
import gov.nist.secauto.metaschema.core.metapath.cst.math.Multiplication;
import gov.nist.secauto.metaschema.core.metapath.cst.math.Subtraction;
import gov.nist.secauto.metaschema.core.metapath.cst.path.Axis;
import gov.nist.secauto.metaschema.core.metapath.cst.path.ContextItem;
import gov.nist.secauto.metaschema.core.metapath.cst.path.Flag;
import gov.nist.secauto.metaschema.core.metapath.cst.path.ModelInstance;
import gov.nist.secauto.metaschema.core.metapath.cst.path.NameTest;
import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeDoubleSlashPath;
import gov.nist.secauto.metaschema.core.metapath.cst.path.RelativeSlashPath;
import gov.nist.secauto.metaschema.core.metapath.cst.path.RootDoubleSlashPath;
import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashOnlyPath;
import gov.nist.secauto.metaschema.core.metapath.cst.path.RootSlashPath;
import gov.nist.secauto.metaschema.core.metapath.cst.path.Step;
import gov.nist.secauto.metaschema.core.metapath.cst.path.Wildcard;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

@SuppressWarnings("PMD.CouplingBetweenObjects")
public final class CSTPrinter {
  private CSTPrinter() {
    // disable construction
  }

  /**
   * Generate a string representation of the CST tree.
   *
   * @param expr
   *          an expression that is a branch in the tree to visualize.
   * @return a string representation of the CST graph
   */
  public static String toString(@NonNull IExpression expr) {
    return new CSTPrinterVisitor().visit(expr);
  }

  @SuppressWarnings("PMD.ExcessivePublicCount")
  private static final class CSTPrinterVisitor
      extends AbstractExpressionVisitor<String, State> {

    @Override
    protected String visitChildren(IExpression expr, State context) {
      context.push();
      String result = super.visitChildren(expr, context);
      context.pop();
      return result;
    }

    @Override
    protected String aggregateResult(String result, String nextResult, State context) {
      StringBuilder buffer = new StringBuilder();
      if (result != null) {
        buffer.append(result);
        // buffer.append(" ar "+System.lineSeparator());
      }

      buffer.append(context.getIndentation())
          .append(nextResult);
      return buffer.toString();
    }

    @Override
    protected String defaultResult() {
      return "";
    }

    /**
     * Append the {@code childResult} to the record produced for the current node.
     *
     * @param expr
     *          the current node
     * @param childResult
     *          the output generated for the curren't node's children
     * @param context
     *          the output context state
     * @return the string representation of the node tree for the current node and
     *         its children
     */
    @SuppressWarnings("static-method")
    protected String appendNode(@NonNull IExpression expr, @Nullable String childResult, @NonNull State context) {
      StringBuilder buffer = new StringBuilder();
      buffer.append(context.getIndentation())
          .append(expr.toASTString());
      if (childResult != null) {
        buffer.append(System.lineSeparator())
            .append(childResult);
      }
      return buffer.toString();
    }

    /**
     * Visit a node and produce a string representation of its the node tree.
     *
     * @param expression
     *          the node to build the node tree for
     * @return the string representation of the node tree for the provided
     *         expression node and its children
     */
    public String visit(@NonNull IExpression expression) {
      return visit(expression, new State());
    }

    @Override
    public String visitAddition(Addition expr, State context) {
      return appendNode(expr, super.visitAddition(expr, context), context);
    }

    @Override
    public String visitAnd(And expr, State context) {
      return appendNode(expr, super.visitAnd(expr, context), context);
    }

    @Override
    public String visitStep(Step expr, State context) {
      return appendNode(expr, super.visitStep(expr, context), context);
    }

    @Override
    public String visitValueComparison(ValueComparison expr, State context) {
      return appendNode(expr, super.visitValueComparison(expr, context), context);
    }

    @Override
    public String visitGeneralComparison(GeneralComparison expr, State context) {
      return appendNode(expr, super.visitGeneralComparison(expr, context), context);
    }

    @Override
    public String visitContextItem(ContextItem expr, State context) {
      return appendNode(expr, super.visitContextItem(expr, context), context);
    }

    @Override
    public String visitDecimalLiteral(DecimalLiteral expr, State context) {
      return appendNode(expr, super.visitDecimalLiteral(expr, context), context);
    }

    @Override
    public String visitDivision(Division expr, State context) {
      return appendNode(expr, super.visitDivision(expr, context), context);
    }

    @Override
    public String visitExcept(@NonNull Except expr, State context) {
      return appendNode(expr, super.visitExcept(expr, context), context);
    }

    @Override
    public String visitFlag(Flag expr, State context) {
      return appendNode(expr, super.visitFlag(expr, context), context);
    }

    @Override
    public String visitFunctionCall(StaticFunctionCall expr, State context) {
      return appendNode(expr, super.visitFunctionCall(expr, context), context);
    }

    @Override
    public String visitIntegerDivision(IntegerDivision expr, State context) {
      return appendNode(expr, super.visitIntegerDivision(expr, context), context);
    }

    @Override
    public String visitIntegerLiteral(IntegerLiteral expr, State context) {
      return appendNode(expr, super.visitIntegerLiteral(expr, context), context);
    }

    @Override
    public String visitIntersect(Intersect expr, State context) {
      return appendNode(expr, super.visitIntersect(expr, context), context);
    }

    @Override
    public String visitMetapath(Metapath expr, State context) {
      return appendNode(expr, super.visitMetapath(expr, context), context);
    }

    @Override
    public String visitModulo(Modulo expr, State context) {
      return appendNode(expr, super.visitModulo(expr, context), context);
    }

    @Override
    public String visitModelInstance(ModelInstance expr, State context) {
      return appendNode(expr, super.visitModelInstance(expr, context), context);
    }

    @Override
    public String visitMultiplication(Multiplication expr, State context) {
      return appendNode(expr, super.visitMultiplication(expr, context), context);
    }

    @Override
    public String visitName(NameTest expr, State context) {
      return appendNode(expr, super.visitName(expr, context), context);
    }

    @Override
    public String visitNegate(Negate expr, State context) {
      return appendNode(expr, super.visitNegate(expr, context), context);
    }

    @Override
    public String visitOr(Or expr, State context) {
      return appendNode(expr, super.visitOr(expr, context), context);
    }

    @Override
    public String visitAxis(Axis expr, State context) {
      return appendNode(expr, super.visitAxis(expr, context), context);
    }

    @Override
    public String visitPredicate(PredicateExpression expr, State context) {
      return appendNode(expr, super.visitPredicate(expr, context), context);
    }

    @Override
    public String visitRelativeDoubleSlashPath(RelativeDoubleSlashPath expr, State context) {
      return appendNode(expr, super.visitRelativeDoubleSlashPath(expr, context), context);
    }

    @Override
    public String visitRelativeSlashPath(RelativeSlashPath expr, State context) {
      return appendNode(expr, super.visitRelativeSlashPath(expr, context), context);
    }

    @Override
    public String visitRootDoubleSlashPath(RootDoubleSlashPath expr, State context) {
      return appendNode(expr, super.visitRootDoubleSlashPath(expr, context), context);
    }

    @Override
    public String visitRootSlashOnlyPath(RootSlashOnlyPath expr, State context) {
      return appendNode(expr, super.visitRootSlashOnlyPath(expr, context), context);
    }

    @Override
    public String visitRootSlashPath(RootSlashPath expr, State context) {
      return appendNode(expr, super.visitRootSlashPath(expr, context), context);
    }

    @Override
    public String visitStringConcat(StringConcat expr, State context) {
      return appendNode(expr, super.visitStringConcat(expr, context), context);
    }

    @Override
    public String visitStringLiteral(StringLiteral expr, State context) {
      return appendNode(expr, super.visitStringLiteral(expr, context), context);
    }

    @Override
    public String visitSubtraction(Subtraction expr, State context) {
      return appendNode(expr, super.visitSubtraction(expr, context), context);
    }

    @Override
    public String visitUnion(Union expr, State context) {
      return appendNode(expr, super.visitUnion(expr, context), context);
    }

    @Override
    public String visitWildcard(Wildcard expr, State context) {
      return appendNode(expr, super.visitWildcard(expr, context), context);
    }

    @Override
    public String visitLet(Let expr, State context) {
      return appendNode(expr, super.visitLet(expr, context), context);
    }

    @Override
    public String visitVariableReference(VariableReference expr, State context) {
      return appendNode(expr, super.visitVariableReference(expr, context), context);
    }

    @Override
    public String visitEmptySequence(EmptySequence<?> expr, State context) {
      return appendNode(expr, super.visitEmptySequence(expr, context), context);
    }

    @Override
    public String visitRange(Range expr, State context) {
      return appendNode(expr, super.visitRange(expr, context), context);
    }

    @Override
    public String visitIf(If expr, State context) {
      return appendNode(expr, super.visitIf(expr, context), context);
    }

    @Override
    public String visitQuantified(Quantified expr, State context) {
      return appendNode(expr, super.visitQuantified(expr, context), context);
    }

    @Override
    public String visitFor(For expr, State context) {
      return appendNode(expr, super.visitFor(expr, context), context);
    }

    @Override
    public String visitSimpleMap(SimpleMap expr, State context) {
      return appendNode(expr, super.visitSimpleMap(expr, context), context);
    }

    @Override
    public String visitArray(ArraySequenceConstructor expr, State context) {
      return appendNode(expr, super.visitArray(expr, context), context);
    }

    @Override
    public String visitArray(ArraySquareConstructor expr, State context) {
      return appendNode(expr, super.visitArray(expr, context), context);
    }

    @Override
    public String visitPostfixLookup(PostfixLookup expr, State context) {
      return appendNode(expr, super.visitPostfixLookup(expr, context), context);
    }

    @Override
    public String visitFunctionCallAccessor(FunctionCallAccessor expr, State context) {
      return appendNode(expr, super.visitFunctionCallAccessor(expr, context), context);
    }

    @Override
    public String visitUnaryLookup(UnaryLookup expr, State context) {
      return appendNode(expr, super.visitUnaryLookup(expr, context), context);
    }

    @Override
    public String visitMapConstructor(MapConstructor expr, State context) {
      return appendNode(expr, super.visitMapConstructor(expr, context), context);
    }

    @Override
    public String visitMapConstructorEntry(MapConstructor.Entry expr, State context) {
      return appendNode(expr, super.visitMapConstructorEntry(expr, context), context);
    }
  }

  static class State {
    private int indentation; // 0;
    private int lastIndentation; // 0;
    private String indentationPadding = "";

    public String getIndentation() {
      if (indentation != lastIndentation) {
        StringBuilder buffer = new StringBuilder();
        for (int i = 0; i < indentation; i++) {
          buffer.append("  ");
        }
        lastIndentation = indentation;
        indentationPadding = buffer.toString();
      }
      return indentationPadding;
    }

    public State push() {
      indentation++;
      return this;
    }

    public State pop() {
      indentation--;
      return this;
    }
  }
}