/*
 * Copyright 2012 - 2013 Maginatics, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.maginatics.jdbclint;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 * StatementProxy proxies a Statement adding some checks.
 *
 *     * whether Statement was closed
 *     * whether Statement was closed more than once
 *     * whether Statement addBatch was called without executeBatch
 */
final class StatementProxy implements InvocationHandler {
    private final Statement stmt;
    private final Properties properties;
    private final String className;
    private final Exception exception = new SQLException();

    private enum State {
        OPENED,
        IN_ADD_BATCH,
        EXECUTED,
        CLOSED;
    }
    private State state = State.OPENED;

    private final boolean checkDoubleClose;
    private final boolean checkMissingClose;
    private final boolean checkMissingExecute;
    private final boolean checkMissingExecuteBatch;

    static Statement newInstance(final Statement stmt,
            final Properties properties) {
        return (Statement) Proxy.newProxyInstance(
                stmt.getClass().getClassLoader(),
                new Class<?>[] {Statement.class},
                new StatementProxy(stmt, properties));
    }

    static PreparedStatement newInstance(final PreparedStatement stmt,
            final Properties properties) {
        return (PreparedStatement) Proxy.newProxyInstance(
                stmt.getClass().getClassLoader(),
                new Class<?>[] {PreparedStatement.class},
                new StatementProxy(stmt, properties));
    }

    StatementProxy(final Statement stmt, final Properties properties) {
        this.stmt = JdbcLint.checkNotNull(stmt);
        this.properties = JdbcLint.checkNotNull(properties);
        this.className = "Statement";

        checkDoubleClose = JdbcLint.nullEmptyOrTrue(properties.getProperty(
                JdbcLint.STATEMENT_DOUBLE_CLOSE));
        checkMissingClose = JdbcLint.nullEmptyOrTrue(properties.getProperty(
                JdbcLint.STATEMENT_MISSING_CLOSE));
        checkMissingExecute = JdbcLint.nullEmptyOrTrue(
                properties.getProperty(
                        JdbcLint.STATEMENT_MISSING_EXECUTE));
        checkMissingExecuteBatch = JdbcLint.nullEmptyOrTrue(
                properties.getProperty(
                        JdbcLint.STATEMENT_MISSING_EXECUTE_BATCH));
    }

    StatementProxy(final PreparedStatement stmt, final Properties properties) {
        this.stmt = JdbcLint.checkNotNull(stmt);
        this.properties = JdbcLint.checkNotNull(properties);
        this.className = "PreparedStatement";

        checkDoubleClose = JdbcLint.nullEmptyOrTrue(properties.getProperty(
                JdbcLint.PREPARED_STATEMENT_DOUBLE_CLOSE));
        checkMissingClose = JdbcLint.nullEmptyOrTrue(properties.getProperty(
                JdbcLint.PREPARED_STATEMENT_MISSING_CLOSE));
        checkMissingExecute = JdbcLint.nullEmptyOrTrue(
                properties.getProperty(
                        JdbcLint.PREPARED_STATEMENT_MISSING_EXECUTE));
        checkMissingExecuteBatch = JdbcLint.nullEmptyOrTrue(
                properties.getProperty(
                        JdbcLint.PREPARED_STATEMENT_MISSING_EXECUTE_BATCH));
    }

    @Override
    public Object invoke(final Object proxy, final Method method,
            final Object[] args) throws Throwable {
        String name = method.getName();
        if (name.equals("addBatch")) {
            state = State.IN_ADD_BATCH;
        } else if (name.equals("executeBatch")) {
            state = State.EXECUTED;
        } else if (name.startsWith("execute")) {
            state = State.EXECUTED;
        } else if (name.equals("close")) {
            if (checkDoubleClose && state == State.CLOSED) {
                // Closing the same statement twice can cause issues with
                // server-side statements.
                JdbcLint.fail(properties, exception,
                        className + " already closed");
            } else if (checkMissingExecute && state == State.OPENED) {
                state = State.CLOSED;
                stmt.close();
                JdbcLint.fail(properties, exception,
                        className + " without execute");
            } else if (checkMissingExecuteBatch &&
                    state == State.IN_ADD_BATCH) {
                state = State.CLOSED;
                stmt.close();
                JdbcLint.fail(properties, exception,
                        className + " addBatch without executeBatch");
            }
            state = State.CLOSED;
        }

        Object returnVal;
        try {
            returnVal = method.invoke(stmt, args);
        } catch (InvocationTargetException ite) {
            throw ite.getTargetException();
        }
        if (name.equals("executeQuery") || name.equals("getGeneratedKeys") ||
                name.equals("getResultSet")) {
            if (returnVal != null) {
                returnVal = ResultSetProxy.newInstance((ResultSet) returnVal,
                        properties);
            }
        }
        return returnVal;
    }

    @Override
    protected void finalize() throws SQLException {
        if (checkMissingClose && state != State.CLOSED) {
            JdbcLint.fail(properties, exception,
                    className + " not closed");
        }
    }
}

@@ -4353,24 +4353,76 @@
 ecuteBatch%22)
+ %7C%7C%0A                name.equals(%22executeLargeBatch%22)
 ) %7B%0A        
@@ -4477,18 +4477,156 @@
 ame.
-startsWith
+equals(%22execute%22) %7C%7C%0A                name.equals(%22executeLargeUpdate%22) %7C%7C%0A                name.equals(%22executeQuery%22) %7C%7C%0A                name.equals
 (%22ex
@@ -4630,16 +4630,22 @@
 %22execute
+Update
 %22)) %7B%0A  

Support Java 8 Statement large methods