/*


    ========== licence begin GPL
    Copyright (C) 2002-2003 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end


*/


package com.sap.dbtech.jdbc;

import java.util.*;
import com.sap.dbtech.vsp001.FunctionCode;
/**
 *
 */
public class ParseinfoCache
    extends com.sap.dbtech.util.cache.LruCache
{
    static final int defaultSizeC = 1000;
    private static final int maxFunctionCodeC = FunctionCode.Delete_FC + 1;
    private boolean keepStats;
    private boolean [] kindFilter;
    private Info [] stats;
    /**
     * creates a new ParseinfoCache
     */
    public
    ParseinfoCache (
        Properties info)
    {
        super (getSizeProperty (info));
        this.setOptions (info);
    }
    /**
     *
     */
    static int
    getSizeProperty (
        Properties info)
    {
        int result;

        result = DriverSapDB.getIntProperty (
            info, DriverSapDB.cacheSizeName_C, defaultSizeC);
        return result;
    }
    /**
     *
     */
    private void
    setOptions (
        Properties info)
    {
        String kindDecl = info.getProperty (DriverSapDB.cacheName_C);

        this.kindFilter = new boolean [maxFunctionCodeC];
        if (kindDecl.indexOf('?') >= 0) {
            this.initStats ();
        }
        if (kindDecl.indexOf ("all") >= 0) {
            for (int i = 0; i < maxFunctionCodeC; ++i) {
                this.kindFilter [i] = true;
            }
        }
        else {
            if (kindDecl.indexOf ("i") >= 0) {
                this.kindFilter [FunctionCode.Insert_FC] = true;
            }
            if (kindDecl.indexOf ("u") >= 0) {
                this.kindFilter [FunctionCode.Update_FC] = true;
            }
            if (kindDecl.indexOf ("d") >= 0) {
                this.kindFilter [FunctionCode.Delete_FC] = true;
            }
            if (kindDecl.indexOf ("s") >= 0) {
                this.kindFilter [FunctionCode.Select_FC] = true;
            }
        }
    }
    /**
     *
     */
    private void
    initStats ()
    {
        this.keepStats = true;
        this.stats = new Info [maxFunctionCodeC];
        this.stats [FunctionCode.Nil_FC] = new Info ("other");
        this.stats [FunctionCode.Insert_FC] = new Info ("Insert");
        this.stats [FunctionCode.Select_FC] = new Info ("select");
        this.stats [FunctionCode.Update_FC] = new Info ("update");
        this.stats [FunctionCode.Delete_FC] = new Info ("delete");
    }
    /**
     *
     */
    public Parseinfo
    findParseinfo (
        String sqlCmd)
    {
        Parseinfo result = null;

        result = (Parseinfo) this.get (sqlCmd);
        if (this.keepStats) {
            if (result != null) {
                this.stats [mapFunctionCode(result.functionCode)].addHit ();
            }
        }
        //System.out.println ("Looking for: " + sqlCmd + " -> " + result); //#print
        return result;
    }
    /**
     *
     */
    public void
    addParseinfo (
        Parseinfo parseinfo)
    {
        int functionCode = mapFunctionCode (parseinfo.functionCode);
        if (this.kindFilter [functionCode]) {
            //System.out.println ("adding " + parseinfo.sqlCmd); //#print
            this.put (parseinfo.sqlCmd, parseinfo);
            parseinfo.cached = true;
            if (this.keepStats) {
                this.stats [functionCode].addMiss ();
            }
        }
    }
    /**
     *
     */
    static private int
    mapFunctionCode (
        int functionCode)
    {
        switch (functionCode) {
            case FunctionCode.Insert_FC:
            case FunctionCode.Update_FC:
            case FunctionCode.Delete_FC:
            case FunctionCode.Select_FC:
                // keep the value
                break;
            default:
                functionCode = FunctionCode.Nil_FC;
                break;
        }
        return functionCode;
    }
    /**
     *
     */
    public Info []
    getStats ()
    {
        if (!this.keepStats) {
            return null;
        }
        Info [] result = new Info [6];

        result [0] = this.stats [FunctionCode.Nil_FC];
        result [1] = this.stats [FunctionCode.Insert_FC];
        result [2] = this.stats [FunctionCode.Update_FC];
        result [3] = this.stats [FunctionCode.Delete_FC];
        result [4] = this.stats [FunctionCode.Select_FC];
        result [5] = Info.cummulate (result);
        return result;
    }
    /**
     *
     */
    public void
    dumpStats (java.io.PrintWriter stream)
    {
        if (!this.keepStats) {
            //System.out.println ("no stats available");
            return;
        }
        stream.println ("Parseinfo cache statistics:");
        Info [] allStats = this.getStats ();
        for (int i = 0; i < allStats.length; ++i) {
            stream.println (allStats [i].toString ());
        }
    }
    /**
     *
     */
    public void
    dumpStats (java.io.PrintStream stream)
    {
        if (!this.keepStats) {
            //System.out.println ("no stats available");
            return;
        }
        stream.println ("Parseinfo cache statistics:");
        Info [] stats = this.getStats ();
        for (int i = 0; i < stats.length; ++i) {
            stream.println (stats [i].toString ());
        }
    }
    /**
     *
     */
    public static class Info
    {
        private String name;
        private long hits;
        private long misses;
        Info (
            String name)
        {
            this.name = name;
            this.hits = 0;
            this.misses = 0;
        }
        /**
         *
         */
        public String
        toString ()
        {
            return this.name + ": " + this.hits + " hits, "
                + this.misses + " misses, " + this.hitrate () + "%";
        }
        /**
         *
         */
        final void
        addHit ()
        {
            ++this.hits;
        }
        /**
         *
         */
        final void
        addMiss ()
        {
            ++this.misses;
        }
        /**
         *
         */
        public double
        hitrate ()
        {
            long all = this.hits + this.misses;
            return (double) hits / (double) all * 100.0;
        }
        /**
         *
         */
        static protected Info
        cummulate (
            Info [] stats)
        {
            Info result = new Info ("all");

            for (int i = 0; i < stats.length; ++i) {
                if (stats [i] != null) {
                    result.hits += stats [i].hits;
                    result.misses += stats [i].misses;
                }
            }
            return result;
        }
    }

}
