OperationHandler.java

package com.github.mygreen.sqlmapper.core.where.metamodel;

import java.text.MessageFormat;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import com.github.mygreen.sqlmapper.core.meta.PropertyMeta;
import com.github.mygreen.sqlmapper.core.query.IllegalQueryException;
import com.github.mygreen.sqlmapper.core.type.ValueType;
import com.github.mygreen.sqlmapper.core.util.QueryUtils;
import com.github.mygreen.sqlmapper.metamodel.Path;
import com.github.mygreen.sqlmapper.metamodel.PathType;
import com.github.mygreen.sqlmapper.metamodel.Visitor;
import com.github.mygreen.sqlmapper.metamodel.expression.Constant;
import com.github.mygreen.sqlmapper.metamodel.expression.Expression;
import com.github.mygreen.sqlmapper.metamodel.expression.SubQueryExpression;
import com.github.mygreen.sqlmapper.metamodel.operation.Operation;
import com.github.mygreen.sqlmapper.metamodel.operator.Operator;
import com.github.mygreen.sqlmapper.metamodel.support.OperationUtils;

/**
 * 演算子に対する処理を行うためのテンプレートクラス。
 *
 *
 * @author T.TSUCHIE
 *
 * @param <T> 処理対象の演算子
 */
public abstract class OperationHandler<T extends Operator> {

    /**
     * 演算子に対するテンプレートのマップ
     * マップの値ととなるテンプレートは{@link MessageFormat}の形式。
     */
    protected Map<T, String> templateMap = new HashMap<>();

    /**
     * 初期化処理
     */
    protected abstract void init();

    /**
     * 演算子に対する処理を行います。
     * @param operator 演算子
     * @param expr 演算子と非演算子を含む処理対象の式の情報
     * @param visitor Visitor
     * @param context このンテキスト
     */
    public abstract void handle(T operator, Operation<?> expr, Visitor<VisitorContext> visitor, VisitorContext context);

    /**
     * テンプレートを追加します。
     * @param op 演算子
     * @param template テンプレート({@link MessageFormat}の形式。)
     * @return 既に演算子に対するテンプレートが追加されている場合は、古いテンプレートの値を返します。
     */
    public String addTemplate(T op, String template) {
        return templateMap.put(op, template);
    }

    /**
     * 演算子に対応するテンプレートを取得します。
     * @param op 演算子
     * @return 対応するテンプレートが存在しない場合は、nullを返します。
     */
    public String getTemplate(T op) {
        return templateMap.get(op);
    }

    /**
     * テンプレートを使用してフォーマットします。
     * @param op 演算子
     * @param args 引数
     * @return フォーマットした値
     */
    public String formatWithTemplate(T op, Object... args) {
        String template = templateMap.get(op);
        return MessageFormat.format(template, args);
    }

    /**
     * 式がプロパティパスかどうか判定します。
     * @param exp 式
     * @return プロパティパスの場合、{@literal true}を返します。
     */
    protected boolean isPropertyPath(Expression<?> exp) {

        if(!(exp instanceof Path)) {
            return false;
        }

        Path<?> path = (Path<?>) exp;
        return path.getPathMeta().getType() == PathType.PROPERTY;

    }

    /**
     * プロパティが確定しているのとき定数の処理。
     * @param propertyPath プロパティパス
     * @param expr 定数
     * @param context コンテキスト
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    protected void visitConstantWithPropertyPath(Path<?> propertyPath, Constant<?> expr, VisitorContext context) {

        assert propertyPath.getPathMeta().getType() == PathType.PROPERTY;

        Path<?> rootPath = propertyPath.getPathMeta().findRootPath();
        Class<?> rootClassType = rootPath.getType();
        String propertyName = propertyPath.getPathMeta().getElement();
        Optional<PropertyMeta> propertyMeta = context.getEntityMetaMap().get(rootClassType).findPropertyMeta(propertyName);
        if(propertyMeta.isEmpty()) {
            throw new IllegalQueryException("unknwon property : " + propertyName);
        }

        ValueType valueType = propertyMeta.get().getValueType();

        // 値はプレースホルダーを追加
        if(expr.isExpandable()) {
            // 展開可能な複数の要素の場合
            Collection<?> values = (Collection<?>)expr.getValue();
            for(Object value : values) {
                context.addParamValue(valueType.getSqlParameterValue(value));
            }
            context.appendSql("(")
                .append(QueryUtils.repeat("?", ", ", values.size()))
                .append(")");
        } else {
            context.addParamValue(valueType.getSqlParameterValue(expr.getValue()));
            context.appendSql("?");
        }

    }

    /**
     * 各処理に振り分ける
     * @param parentOperator 親ノードの演算子
     * @param expr 評価対象の式
     * @param visitor Visitor
     * @param context コンテキスト
     */
    protected void invoke(Operator parentOperator, Expression<?> expr, Visitor<VisitorContext> visitor, VisitorContext context) {

        if(expr instanceof Operation) {
            Operation<?> operation = (Operation<?>)expr;
            // /子ノードが演算子の場合、括弧で囲むか判定する。
            if(OperationUtils.isEnclosedParenthesis(parentOperator, operation.getOperator())) {
                context.appendSql("(");
                visitor.visit(operation, context);
                context.appendSql(")");
            } else {
                visitor.visit(operation, context);
            }

        } else if(expr instanceof Constant) {
            visitor.visit((Constant<?>)expr, context);
        } else if (expr instanceof Path) {
            visitor.visit((Path<?>)expr, context);
        } else if(expr instanceof SubQueryExpression) {
            context.appendSql("(");
            visitor.visit((SubQueryExpression<?>)expr, context);
            context.appendSql(")");
        } else {
            throw new IllegalArgumentException("not support Expression instance of " + expr.getClass());
        }

    }

}