All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.language.AstPrinter Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
package graphql.language;

import graphql.AssertException;
import graphql.PublicApi;

import java.io.PrintWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static graphql.Assert.assertTrue;
import static graphql.util.EscapeUtil.escapeJsonString;
import static java.lang.String.valueOf;

/**
 * This can take graphql language AST and print it out as a string
 */
@SuppressWarnings("UnnecessaryLocalVariable")
@PublicApi
public class AstPrinter {
    private final Map, NodePrinter> printers = new LinkedHashMap<>();

    private final boolean compactMode;

    private AstPrinter(boolean compactMode) {
        this.compactMode = compactMode;
        printers.put(Argument.class, argument());
        printers.put(ArrayValue.class, value());
        printers.put(BooleanValue.class, value());
        printers.put(NullValue.class, value());
        printers.put(Directive.class, directive());
        printers.put(DirectiveDefinition.class, directiveDefinition());
        printers.put(DirectiveLocation.class, directiveLocation());
        printers.put(Document.class, document());
        printers.put(EnumTypeDefinition.class, enumTypeDefinition());
        printers.put(EnumTypeExtensionDefinition.class, enumTypeExtensionDefinition());
        printers.put(EnumValue.class, enumValue());
        printers.put(EnumValueDefinition.class, enumValueDefinition());
        printers.put(Field.class, field());
        printers.put(FieldDefinition.class, fieldDefinition());
        printers.put(FloatValue.class, value());
        printers.put(FragmentDefinition.class, fragmentDefinition());
        printers.put(FragmentSpread.class, fragmentSpread());
        printers.put(InlineFragment.class, inlineFragment());
        printers.put(InputObjectTypeDefinition.class, inputObjectTypeDefinition());
        printers.put(InputObjectTypeExtensionDefinition.class, inputObjectTypeExtensionDefinition());
        printers.put(InputValueDefinition.class, inputValueDefinition());
        printers.put(InterfaceTypeDefinition.class, interfaceTypeDefinition());
        printers.put(InterfaceTypeExtensionDefinition.class, interfaceTypeExtensionDefinition());
        printers.put(IntValue.class, value());
        printers.put(ListType.class, type());
        printers.put(NonNullType.class, type());
        printers.put(ObjectField.class, objectField());
        printers.put(ObjectTypeDefinition.class, objectTypeDefinition());
        printers.put(ObjectTypeExtensionDefinition.class, objectTypeExtensionDefinition());
        printers.put(ObjectValue.class, value());
        printers.put(OperationDefinition.class, operationDefinition());
        printers.put(OperationTypeDefinition.class, operationTypeDefinition());
        printers.put(ScalarTypeDefinition.class, scalarTypeDefinition());
        printers.put(ScalarTypeExtensionDefinition.class, scalarTypeExtensionDefinition());
        printers.put(SchemaDefinition.class, schemaDefinition());
        printers.put(SchemaExtensionDefinition.class, schemaExtensionDefinition());
        printers.put(SelectionSet.class, selectionSet());
        printers.put(StringValue.class, value());
        printers.put(TypeName.class, type());
        printers.put(UnionTypeDefinition.class, unionTypeDefinition());
        printers.put(UnionTypeExtensionDefinition.class, unionTypeExtensionDefinition());
        printers.put(VariableDefinition.class, variableDefinition());
        printers.put(VariableReference.class, variableReference());
    }

    private NodePrinter argument() {
        if (compactMode) {
            return (out, node) -> out.append(node.getName()).append(':').append(value(node.getValue()));
        }
        return (out, node) -> out.append(node.getName()).append(": ").append(value(node.getValue()));
    }

    private NodePrinter document() {
        if (compactMode) {
            return (out, node) -> out.append(join(node.getDefinitions(), " "));
        }
        return (out, node) -> out.append(join(node.getDefinitions(), "\n\n")).append("\n");
    }

    private NodePrinter directive() {
        final String argSep = compactMode ? "," : ", ";
        return (out, node) -> {
            String arguments = wrap("(", join(node.getArguments(), argSep), ")");
            out.append('@').append(node.getName()).append(arguments);
        };
    }

    private NodePrinter directiveDefinition() {
        final String argSep = compactMode ? "," : ", ";
        return (out, node) -> {
            out.append(description(node));
            String arguments = wrap("(", join(node.getInputValueDefinitions(), argSep), ")");
            String locations = join(node.getDirectiveLocations(), " | ");
            String repeatable = node.isRepeatable() ? "repeatable " : "";
            out.append("directive @")
                    .append(node.getName())
                    .append(arguments)
                    .append(" ")
                    .append(repeatable)
                    .append("on ")
                    .append(locations);
        };
    }

    private NodePrinter directiveLocation() {
        return (out, node) -> out.append(node.getName());
    }

    private NodePrinter enumTypeDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "enum",
                    node.getName(),
                    directives(node.getDirectives()),
                    block(node.getEnumValueDefinitions())
            ));
        };
    }

    private NodePrinter enumValue() {
        return (out, node) -> out.append(node.getName());
    }

    private NodePrinter enumValueDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    node.getName(),
                    directives(node.getDirectives())
            ));
        };
    }

    private NodePrinter field() {
        final String argSep = compactMode ? "," : ", ";
        final String aliasSuffix = compactMode ? ":" : ": ";
        return (out, node) -> {
            String alias = wrap("", node.getAlias(), aliasSuffix);
            String name = node.getName();
            String arguments = wrap("(", join(node.getArguments(), argSep), ")");
            String directives = directives(node.getDirectives());
            String selectionSet = node(node.getSelectionSet());

            out.append(spaced(
                    alias + name + arguments,
                    directives,
                    selectionSet
            ));
        };
    }


    private NodePrinter fieldDefinition() {
        final String argSep = compactMode ? "," : ", ";
        return (out, node) -> {
            String args;
            if (hasDescription(node.getInputValueDefinitions()) && !compactMode) {
                out.append(description(node));
                args = join(node.getInputValueDefinitions(), "\n");
                out.append(node.getName())
                        .append(wrap("(\n", args, ")"))
                        .append(": ")
                        .append(spaced(
                                type(node.getType()),
                                directives(node.getDirectives())
                        ));
            } else {
                args = join(node.getInputValueDefinitions(), argSep);
                out.append(node.getName())
                        .append(wrap("(", args, ")"))
                        .append(": ")
                        .append(spaced(
                                type(node.getType()),
                                directives(node.getDirectives())
                        ));
            }
        };
    }

    private boolean hasDescription(List nodes) {
        for (Node node : nodes) {
            if (node instanceof AbstractDescribedNode) {
                AbstractDescribedNode describedNode = (AbstractDescribedNode) node;
                if (describedNode.getDescription() != null) {
                    return true;
                }
            }
        }

        return false;
    }

    private NodePrinter fragmentDefinition() {
        return (out, node) -> {
            String name = node.getName();
            String typeCondition = type(node.getTypeCondition());
            String directives = directives(node.getDirectives());
            String selectionSet = node(node.getSelectionSet());

            out.append("fragment ").append(name).append(" on ").append(typeCondition)
                    .append(' ')
                    .append(directives)
                    .append(selectionSet);
        };
    }

    private NodePrinter fragmentSpread() {
        return (out, node) -> {
            String name = node.getName();
            String directives = directives(node.getDirectives());

            out.append("...").append(name).append(directives);
        };
    }

    private NodePrinter inlineFragment() {
        return (out, node) -> {
            TypeName typeName = node.getTypeCondition();
            //Inline fragments may not have a type condition
            String typeCondition = typeName == null ? "" : wrap("on ", type(typeName), "");
            String directives = directives(node.getDirectives());
            String selectionSet = node(node.getSelectionSet());

            out.append(spaced(
                    "...",
                    typeCondition,
                    directives,
                    selectionSet
            ));
        };
    }

    private NodePrinter inputObjectTypeDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "input",
                    node.getName(),
                    directives(node.getDirectives()),
                    block(node.getInputValueDefinitions())
            ));
        };
    }

    private NodePrinter inputValueDefinition() {
        String nameTypeSep = compactMode ? ":" : ": ";
        String defaultValueEquals = compactMode ? "=" : "= ";
        return (out, node) -> {
            Value defaultValue = node.getDefaultValue();
            out.append(description(node));
            out.append(spaced(
                    node.getName() + nameTypeSep + type(node.getType()),
                    wrap(defaultValueEquals, defaultValue, ""),
                    directives(node.getDirectives())
            ));
        };
    }

    private NodePrinter interfaceTypeDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "interface",
                    node.getName(),
                    wrap("implements ", join(node.getImplements(), " & "), ""),
                    directives(node.getDirectives()),
                    block(node.getFieldDefinitions())
            ));
        };
    }

    private NodePrinter objectField() {
        String nameValueSep = compactMode ? ":" : " : ";
        return (out, node) -> out.append(node.getName()).append(nameValueSep).append(value(node.getValue()));
    }

    private NodePrinter operationDefinition() {
        final String argSep = compactMode ? "," : ", ";
        return (out, node) -> {
            String op = node.getOperation().toString().toLowerCase();
            String name = node.getName();
            String varDefinitions = wrap("(", join(nvl(node.getVariableDefinitions()), argSep), ")");
            String directives = directives(node.getDirectives());
            String selectionSet = node(node.getSelectionSet());

            // Anonymous queries with no directives or variable definitions can use
            // the query short form.
            if (isEmpty(name) && isEmpty(directives) && isEmpty(varDefinitions) && op.equals("QUERY")) {
                out.append(selectionSet);
            } else {
                out.append(spaced(op, smooshed(name, varDefinitions), directives, selectionSet));
            }
        };
    }

    private NodePrinter operationTypeDefinition() {
        String nameTypeSep = compactMode ? ":" : ": ";
        return (out, node) -> out.append(node.getName()).append(nameTypeSep).append(type(node.getTypeName()));
    }

    private NodePrinter objectTypeDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "type",
                    node.getName(),
                    wrap("implements ", join(node.getImplements(), " & "), ""),
                    directives(node.getDirectives()),
                    block(node.getFieldDefinitions())
            ));
        };
    }

    private NodePrinter selectionSet() {
        return (out, node) -> {
            out.append(block(node.getSelections()));
        };
    }

    private NodePrinter scalarTypeDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "scalar",
                    node.getName(),
                    directives(node.getDirectives())));
        };
    }


    private NodePrinter schemaDefinition() {
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "schema",
                    directives(node.getDirectives()),
                    block(node.getOperationTypeDefinitions())
            ));
        };
    }


    private NodePrinter type() {
        return (out, node) -> out.append(type(node));
    }

    private String type(Type type) {
        if (type instanceof NonNullType) {
            NonNullType inner = (NonNullType) type;
            return wrap("", type(inner.getType()), "!");
        } else if (type instanceof ListType) {
            ListType inner = (ListType) type;
            return wrap("[", type(inner.getType()), "]");
        } else {
            TypeName inner = (TypeName) type;
            return inner.getName();
        }
    }

    private NodePrinter objectTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, ObjectTypeDefinition.class));
    }

    private NodePrinter enumTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, EnumTypeDefinition.class));
    }

    private NodePrinter interfaceTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, InterfaceTypeDefinition.class));
    }

    private NodePrinter unionTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, UnionTypeDefinition.class));
    }

    private NodePrinter scalarTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, ScalarTypeDefinition.class));
    }

    private NodePrinter inputObjectTypeExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, InputObjectTypeDefinition.class));
    }

    private NodePrinter schemaExtensionDefinition() {
        return (out, node) -> out.append("extend ").append(node(node, SchemaDefinition.class));
    }

    private NodePrinter unionTypeDefinition() {
        String barSep = compactMode ? "|" : " | ";
        String equals = compactMode ? "=" : "= ";
        return (out, node) -> {
            out.append(description(node));
            out.append(spaced(
                    "union",
                    node.getName(),
                    directives(node.getDirectives()),
                    equals + join(node.getMemberTypes(), barSep)
            ));
        };
    }

    private NodePrinter variableDefinition() {
        String nameTypeSep = compactMode ? ":" : ": ";
        String defaultValueEquals = compactMode ? "=" : " = ";
        return (out, node) -> out.append('$')
                .append(node.getName())
                .append(nameTypeSep)
                .append(type(node.getType()))
                .append(wrap(defaultValueEquals, node.getDefaultValue(), ""))
                .append(directives(node.getDirectives()));
    }

    private NodePrinter variableReference() {
        return (out, node) -> out.append('$').append(node.getName());
    }

    private String node(Node node) {
        return node(node, null);
    }

    private String node(Node node, Class startClass) {
        if (startClass != null) {
            assertTrue(startClass.isInstance(node), () -> "The starting class must be in the inherit tree");
        }
        StringBuilder builder = new StringBuilder();
        NodePrinter printer = _findPrinter(node, startClass);
        printer.print(builder, node);
        return builder.toString();
    }

    @SuppressWarnings("unchecked")
    private  NodePrinter _findPrinter(Node node) {
        return _findPrinter(node, null);
    }

    private  NodePrinter _findPrinter(Node node, Class startClass) {
        if (node == null) {
            return (out, type) -> {
            };
        }
        Class clazz = startClass != null ? startClass : node.getClass();
        while (clazz != Object.class) {
            NodePrinter nodePrinter = printers.get(clazz);
            if (nodePrinter != null) {
                //noinspection unchecked
                return nodePrinter;
            }
            clazz = clazz.getSuperclass();
        }
        throw new AssertException(String.format("We have a missing printer implementation for %s : report a bug!", clazz));
    }

    private  boolean isEmpty(List list) {
        return list == null || list.isEmpty();
    }

    private boolean isEmpty(String s) {
        return s == null || s.trim().length() == 0;
    }

    private  List nvl(List list) {
        return list != null ? list : Collections.emptyList();
    }

    private NodePrinter value() {
        return (out, node) -> out.append(value(node));
    }

    private String value(Value value) {
        String argSep = compactMode ? "," : ", ";
        if (value instanceof IntValue) {
            return valueOf(((IntValue) value).getValue());
        } else if (value instanceof FloatValue) {
            return valueOf(((FloatValue) value).getValue());
        } else if (value instanceof StringValue) {
            return wrap("\"", escapeJsonString(((StringValue) value).getValue()), "\"");
        } else if (value instanceof EnumValue) {
            return valueOf(((EnumValue) value).getName());
        } else if (value instanceof BooleanValue) {
            return valueOf(((BooleanValue) value).isValue());
        } else if (value instanceof NullValue) {
            return "null";
        } else if (value instanceof ArrayValue) {
            return "[" + join(((ArrayValue) value).getValues(), argSep) + "]";
        } else if (value instanceof ObjectValue) {
            return "{" + join(((ObjectValue) value).getObjectFields(), argSep) + "}";
        } else if (value instanceof VariableReference) {
            return "$" + ((VariableReference) value).getName();
        }
        return "";
    }

    private String description(Node node) {
        Description description = ((AbstractDescribedNode) node).getDescription();
        if (description == null || description.getContent() == null || compactMode) {
            return "";
        }
        String s;
        boolean startNewLine = description.getContent().length() > 0 && description.getContent().charAt(0) == '\n';
        if (description.isMultiLine()) {
            s = "\"\"\"" + (startNewLine ? "" : "\n") + description.getContent() + "\n\"\"\"\n";
        } else {
            s = "\"" + description.getContent() + "\"\n";
        }
        return s;
    }

    private String directives(List directives) {
        return join(nvl(directives), " ");
    }

    private  String join(List nodes, String delim) {
        return join(nodes, delim, "", "");
    }

    @SuppressWarnings("SameParameterValue")
    private  String join(List nodes, String delim, String prefix, String suffix) {
        StringBuilder joined = new StringBuilder();
        joined.append(prefix);

        boolean first = true;
        for (T node : nodes) {
            if (first) {
                first = false;
            } else {
                joined.append(delim);
            }
            joined.append(this.node(node));
        }

        joined.append(suffix);
        return joined.toString();
    }

    private String spaced(String... args) {
        return join(" ", args);
    }

    private String smooshed(String... args) {
        return join("", args);
    }

    private String join(String delim, String... args) {
        StringBuilder builder = new StringBuilder();

        boolean first = true;
        for (final String arg : args) {
            if (isEmpty(arg)) {
                continue;
            }
            if (first) {
                first = false;
            } else {
                builder.append(delim);
            }
            builder.append(arg);
        }

        return builder.toString();
    }

    String wrap(String start, String maybeString, String end) {
        if (isEmpty(maybeString)) {
            if (start.equals("\"") && end.equals("\"")) {
                return "\"\"";
            }
            return "";
        }
        return start + maybeString + (!isEmpty(end) ? end : "");
    }

    private  String block(List nodes) {
        if (isEmpty(nodes)) {
            return "{}";
        }
        if (compactMode) {
            return "{"
                    + join(nodes, " ")
                    + "}";
        }
        return indent("{\n"
                + join(nodes, "\n"))
                + "\n}";
    }

    private String indent(String maybeString) {
        if (isEmpty(maybeString)) {
            return "";
        }
        maybeString = maybeString.replaceAll("\\n", "\n  ");
        return maybeString;
    }

    @SuppressWarnings("SameParameterValue")
    String wrap(String start, Node maybeNode, String end) {
        if (maybeNode == null) {
            return "";
        }
        return start + node(maybeNode) + (isEmpty(end) ? "" : end);
    }

    /**
     * This will pretty print the AST node in graphql language format
     *
     * @param node the AST node to print
     * @return the printed node in graphql language format
     */
    public static String printAst(Node node) {
        StringBuilder builder = new StringBuilder();
        printImpl(builder, node, false);
        return builder.toString();
    }

    /**
     * This will pretty print the AST node in graphql language format
     *
     * @param writer the place to put the output
     * @param node   the AST node to print
     */
    public static void printAst(Writer writer, Node node) {
        String ast = printAst(node);
        PrintWriter printer = new PrintWriter(writer);
        printer.write(ast);
    }

    /**
     * This will print the Ast node in graphql language format in a compact manner, with no new lines
     * and comments stripped out of the text.
     *
     * @param node the AST node to print
     * @return the printed node in a compact graphql language format
     */
    public static String printAstCompact(Node node) {
        StringBuilder builder = new StringBuilder();
        printImpl(builder, node, true);
        return builder.toString();
    }

    private static void printImpl(StringBuilder writer, Node node, boolean compactMode) {
        AstPrinter astPrinter = new AstPrinter(compactMode);
        NodePrinter printer = astPrinter._findPrinter(node);
        printer.print(writer, node);
    }

    /**
     * These print nodes into output writers
     *
     * @param  the type of node
     */
    private interface NodePrinter {
        void print(StringBuilder out, T node);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy