@Override
    @SuppressWarnings("unchecked")
    protected void visitOperation(Class<?> type, Operator operator, List<? extends Expression<?>> args) {
        boolean oldInCaseOperation = inCaseOperation;
        inCaseOperation = CASE_OPS.contains(operator);
        boolean oldWrapElements = wrapElements;
        wrapElements = templates.wrapElements(operator);

        if (operator == Ops.EQ && args.get(1) instanceof Operation &&
                ((Operation) args.get(1)).getOperator() == Ops.QuantOps.ANY) {
            args = Arrays.<Expression<?>> asList(args.get(0), ((Operation) args.get(1)).getArg(0));
            visitOperation(type, Ops.IN, args);

        } else if (operator == Ops.NE && args.get(1) instanceof Operation &&
                ((Operation) args.get(1)).getOperator() == Ops.QuantOps.ANY) {
            args = Arrays.<Expression<?>> asList(args.get(0), ((Operation) args.get(1)).getArg(0));
            visitOperation(type, Ops.NOT_IN, args);

        } else if (operator == Ops.IN || operator == Ops.NOT_IN) {
            if (args.get(1) instanceof Path) {
                visitAnyInPath(type, operator, args);
            } else if (args.get(0) instanceof Path && args.get(1) instanceof Constant<?>) {
                visitPathInCollection(type, operator, args);
            } else {
                super.visitOperation(type, operator, args);
            }

        } else if (operator == Ops.NUMCAST) {
            visitNumCast(args);

        } else if (operator == Ops.EXISTS && args.get(0) instanceof SubQueryExpression) {
            final SubQueryExpression subQuery = (SubQueryExpression) args.get(0);
            append("exists (");
            serialize(subQuery.getMetadata(), false, templates.getExistsProjection());
            append(")");

        } else if (operator == Ops.MATCHES || operator == Ops.MATCHES_IC) {
            super.visitOperation(type, Ops.LIKE,
                    Arrays.asList(args.get(0), ExpressionUtils.regexToLike((Expression<String>) args.get(1))));

        } else if (operator == Ops.LIKE && args.get(1) instanceof Constant<?>) {
            final String escape = String.valueOf(templates.getEscapeChar());
            final String escaped = args.get(1).toString().replace(escape, escape + escape);
            super.visitOperation(String.class, Ops.LIKE,
                    Arrays.asList(args.get(0), ConstantImpl.create(escaped)));

        } else if (NUMERIC.contains(operator)) {
            super.visitOperation(type, operator, normalizeNumericArgs(args));

        } else if (operator == Ops.ALIAS) {
            if (args.get(1) instanceof Path && !((Path<?>) args.get(1)).getMetadata().isRoot()) {
                Path<?> path = (Path<?>) args.get(1);
                args = Arrays.asList(args.get(0),
                        ExpressionUtils.path(path.getType(), path.getMetadata().getName()));
            }
            super.visitOperation(type, operator, args);

        } else {
            try {
                super.visitOperation(type, operator, args);
            } catch (IllegalArgumentException e) {
                if (operator.getClass().getName().endsWith("SQLOps")) {
                    throw new IllegalArgumentException(String.format("SQL Expressions like %s are not supported in JPQL - the query language for JPA. " +
                            "SQLExpressions.* can only be used in JPQL queries when these functions are registered as custom function in your ORM.%n" +
                            "\tTo fix this issue, you have three options:%n" +
                            "\t1) If you do want to use advanced, dialect specific, SQL functions within JPQL, make sure to make these functions available to " +
                            "your ORM through custom functions and register these with your JPATemplates instance.%n" +
                            "\t2) Use JPASQLQuery instead. This allows you to generate a pure SQL query based on your JPA metamodel.%n" +
                            "\t3) Consider using the Blaze-Persistence QueryDSL integration. Blaze-Persistence is an extension on top of " +
                            "JPA that makes various SQL specific functions like window functions available to JPQL.", operator.name()), e);
                } else {
                    throw e;
                }
            }
        }

        inCaseOperation = oldInCaseOperation;
        wrapElements = oldWrapElements;
    }
{@code JPQLSerializer} serializes Querydsl expressions into JPQL syntax.

@author tiwe
querydsl-jpa/src/main/java/com/querydsl/jpa/JPQLSerializer.java