JsonFunction.java

/*-
 * #%L
 * JSQLParser library
 * %%
 * Copyright (C) 2004 - 2019 JSQLParser
 * %%
 * Dual licensed under GNU LGPL 2.1 or Apache License 2.0
 * #L%
 */
package net.sf.jsqlparser.expression;

import java.util.ArrayList;
import java.util.Objects;

import net.sf.jsqlparser.parser.ASTNodeAccessImpl;

/**
 * Represents a JSON-Function.<br>
 * Currently supported are the types in {@link JsonFunctionType}.<br>
 * <br>
 * For JSON_OBJECT the parameters are available from {@link #getKeyValuePairs()}<br>
 * <br>
 * For JSON_ARRAY the parameters are availble from {@link #getExpressions()}.<br>
 *
 * @author <a href="mailto:andreas@manticore-projects.com">Andreas Reichel</a>
 */
public class JsonFunction extends ASTNodeAccessImpl implements Expression {
    private final ArrayList<JsonKeyValuePair> keyValuePairs = new ArrayList<>();
    private final ArrayList<JsonFunctionExpression> expressions = new ArrayList<>();
    private JsonFunctionType functionType;
    private JsonAggregateOnNullType onNullType;
    private JsonAggregateUniqueKeysType uniqueKeysType;

    private boolean isStrict = false;

    public JsonFunction() {}

    public JsonFunction(JsonFunctionType functionType) {
        this.functionType = functionType;
    }

    /**
     * Returns the Parameters of an JSON_OBJECT<br>
     * The KeyValuePairs may not have both key and value set, in some cases only the Key is set.
     *
     * @see net.sf.jsqlparser.parser.feature.Feature#allowCommaAsKeyValueSeparator
     *
     * @return A List of KeyValuePairs, never NULL
     */
    public ArrayList<JsonKeyValuePair> getKeyValuePairs() {
        return keyValuePairs;
    }

    /**
     * Returns the parameters of JSON_ARRAY<br>
     *
     * @return A List of {@link JsonFunctionExpression}s, never NULL
     */
    public ArrayList<JsonFunctionExpression> getExpressions() {
        return expressions;
    }

    public JsonKeyValuePair getKeyValuePair(int i) {
        return keyValuePairs.get(i);
    }

    public JsonFunctionExpression getExpression(int i) {
        return expressions.get(i);
    }

    public boolean add(JsonKeyValuePair keyValuePair) {
        return keyValuePairs.add(keyValuePair);
    }

    public void add(int i, JsonKeyValuePair keyValuePair) {
        keyValuePairs.add(i, keyValuePair);
    }

    public boolean add(JsonFunctionExpression expression) {
        return expressions.add(expression);
    }

    public void add(int i, JsonFunctionExpression expression) {
        expressions.add(i, expression);
    }

    public boolean isEmpty() {
        return keyValuePairs.isEmpty();
    }

    public JsonAggregateOnNullType getOnNullType() {
        return onNullType;
    }

    public void setOnNullType(JsonAggregateOnNullType onNullType) {
        this.onNullType = onNullType;
    }

    public JsonFunction withOnNullType(JsonAggregateOnNullType onNullType) {
        this.setOnNullType(onNullType);
        return this;
    }

    public JsonAggregateUniqueKeysType getUniqueKeysType() {
        return uniqueKeysType;
    }

    public void setUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
        this.uniqueKeysType = uniqueKeysType;
    }

    public JsonFunction withUniqueKeysType(JsonAggregateUniqueKeysType uniqueKeysType) {
        this.setUniqueKeysType(uniqueKeysType);
        return this;
    }

    public JsonFunctionType getType() {
        return functionType;
    }

    public void setType(JsonFunctionType type) {
        this.functionType =
                Objects.requireNonNull(type,
                        "The Type of the JSON Aggregate Function must not be null");
    }

    public void setType(String typeName) {
        this.functionType = JsonFunctionType.valueOf(
                Objects.requireNonNull(typeName,
                        "The Type of the JSON Aggregate Function must not be null")
                        .toUpperCase());
    }

    public JsonFunction withType(JsonFunctionType type) {
        this.setType(type);
        return this;
    }

    public JsonFunction withType(String typeName) {
        this.setType(typeName);
        return this;
    }

    public boolean isStrict() {
        return isStrict;
    }

    public void setStrict(boolean strict) {
        isStrict = strict;
    }

    public JsonFunction withStrict(boolean strict) {
        this.setStrict(strict);
        return this;
    }

    @Override
    public <T, S> T accept(ExpressionVisitor<T> expressionVisitor, S context) {
        return expressionVisitor.visit(this, context);
    }

    // avoid countless Builder --> String conversion
    public StringBuilder append(StringBuilder builder) {
        switch (functionType) {
            case OBJECT:
            case POSTGRES_OBJECT:
            case MYSQL_OBJECT:
                appendObject(builder);
                break;
            case ARRAY:
                appendArray(builder);
                break;
            default:
                // this should never happen really
        }
        return builder;
    }

    @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
    public StringBuilder appendObject(StringBuilder builder) {
        builder.append("JSON_OBJECT( ");
        int i = 0;
        for (JsonKeyValuePair keyValuePair : keyValuePairs) {
            if (i > 0) {
                builder.append(", ");
            }
            keyValuePair.append(builder);
            i++;
        }

        appendOnNullType(builder);
        if (isStrict) {
            builder.append(" STRICT");
        }
        appendUniqueKeys(builder);

        builder.append(" ) ");

        return builder;
    }

    private void appendOnNullType(StringBuilder builder) {
        if (onNullType != null) {
            switch (onNullType) {
                case NULL:
                    builder.append(" NULL ON NULL");
                    break;
                case ABSENT:
                    builder.append(" ABSENT ON NULL");
                    break;
                default:
                    // this should never happen
            }
        }
    }

    private void appendUniqueKeys(StringBuilder builder) {
        if (uniqueKeysType != null) {
            switch (uniqueKeysType) {
                case WITH:
                    builder.append(" WITH UNIQUE KEYS");
                    break;
                case WITHOUT:
                    builder.append(" WITHOUT UNIQUE KEYS");
                    break;
                default:
                    // this should never happen
            }
        }
    }

    @SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.NPathComplexity"})
    public StringBuilder appendArray(StringBuilder builder) {
        builder.append("JSON_ARRAY( ");
        int i = 0;

        for (JsonFunctionExpression expr : expressions) {
            if (i > 0) {
                builder.append(", ");
            }
            expr.append(builder);
            i++;
        }

        appendOnNullType(builder);
        builder.append(") ");

        return builder;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        return append(builder).toString();
    }
}