/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included  with this distribution in *
 * the LICENSE file.                                                         *
 *****************************************************************************/


import java.util.HashMap;
import java.util.LinkedList;

/**
 * This class provides a MRU cache algorithm. It combines a HashMap
 * and a LinkedList to create a so called MRU (Most Recently Used) cache.
 *
 * @author Gerhard Froehlich <a href="mailto:g-froehlich@gmx.de">
 *      g-froehlich@gmx.de</a>
 * @version $Id: MRUMap.java,v 1.2 2002/01/27 18:46:50 froehlich Exp $
 */
public final class MRUMap
extends HashMap {

    private int mMaxObjects = 10000;
    private LinkedList mMRUList;

    /**
     * This method returns the current set of the object limit.
     *
     * @return value of the object limit.
     */
    public int getMaxObjects() {
        return this.mMaxObjects;
    }

    /**
     * This method initializes the MRUMemoryStore. NOTE: You should
     * first call the setMaxObjects(int maxobjects) method to set the
     * the limit of the Store. Default the limit is 100 Objects.
     */
    public MRUMap(int maxobjects) {
        super((int) (maxobjects * 1.2));
        this.mMRUList = new LinkedList();
        this.mMaxObjects = maxobjects;
    }

    /**
     *  Returns the value to which this map maps the specified key.
     *
     * @param key whose associated value is to be returned
     * @return the value to which this map maps the specified key,
     * or null if the map contains no mapping for this key
     */
    public Object get(Object key) {
        Object tmpobject = super.get(key);
        if (tmpobject != null) {
            this.mMRUList.remove(key);
            this.mMRUList.addFirst(key);
            return tmpobject;
        }
        return null;
    }

    /**
     *  Associates the specified value with the specified key in this map
     * (optional operation). If the map previously contained a mapping for
     * this key, the old value is replaced.
     *
     * @param key with which the specified value is to be associated.
     * @param value to be associated with the specified key.
     */
    public Object put(Object key, Object value) {
        while (this.mMRUList.size() >= this.mMaxObjects) {
            this.free();
        }
        Object obj = super.put(key, value);
        this.mMRUList.remove(key);
        this.mMRUList.addFirst(key);
        return obj;
    }

    /**
     *  Remove the object associated to the given key.
     *
     * @param key the Key object
     */
    public Object remove(Object key) {
        this.mMRUList.remove(key);
        return super.remove(key);
    }


    /**
     *  Frees some of the fast memory used by this store. It removes the last
     *  element in the store.
     */
    private void free() {
        try {
            if (super.size() > 0) {
                super.remove(this.mMRUList.getLast());
                this.mMRUList.removeLast();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main( String[] args ) {

        int size = 1000;
        if ( args.length > 0 ) {
            try {
                size = Integer.parseInt(args[0]);
            } catch ( NumberFormatException e ) {
                System.out.println( "arg 1 (size) should be a number" );
            }
        }

        int cycles = 2;
        if ( args.length > 1 ) {
            try {
                cycles = Integer.parseInt(args[1]);
            } catch ( NumberFormatException e ) {
                System.out.println( "arg 2 (cycles) should be a number" );
            }
        }

        MRUMap map = new MRUMap( size );

        boolean show = false;
        if ( args.length > 2 ) {
            try {
                show = Boolean.valueOf(args[2]).booleanValue();;
            } catch ( Exception e ) {
                System.out.println( "arg 3 (show) should be true or false" );
            }
        }

        for ( int i = 0; i < cycles; i++ ) {
            long startP = System.currentTimeMillis();
            for ( int j = 0; j <= size; j++ ) {
                map.put( "key" + j, "value" + j );
            }
            long endP = System.currentTimeMillis();
            long deltaP = endP - startP;
            System.out.println( "took " + deltaP + " ms. to put " + size );

            long startG = System.currentTimeMillis();
            for ( int j = 0; j <= size; j++ ) {
                String val = (String)map.get( "key" + j );
                if ( show ) {
                   System.out.println( val );
                }
            }
            long endG = System.currentTimeMillis();
            long deltaG = endG - startG;
            System.out.println( "took " + deltaG + " ms. to get " + size );

        }

    } // end main

}

