/FileExtraction/Java_unseen/dlitz_resin/modules/resin/src/com/caucho/db/index/IndexCache.java
/*
 * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source 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.
 *
 * Resin Open Source 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, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *
 *   Free Software Foundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.db.index;

import com.caucho.util.*;
import com.caucho.db.xa.DbTransaction;
import com.caucho.env.thread.TaskWorker;

import java.io.IOException;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.*;
import java.sql.SQLException;

/**
 * Manages the block cache
 */
public final class IndexCache
{
  private static final Logger log
    = Logger.getLogger(IndexCache.class.getName());
  private static final L10N L = new L10N(IndexCache.class);

  private static IndexCache _staticCache;

  private final LruCache<IndexKey,IndexKey> _cache;

  private final ArrayList<IndexKey> _writeQueue
    = new ArrayList<IndexKey>();

  private final AtomicReference<IndexKey> _freeKey
    = new AtomicReference<IndexKey>();

  private IndexCacheWriter _indexWriter = new IndexCacheWriter();

  private IndexCache(int capacity)
  {
    _cache = new LruCache<IndexKey,IndexKey>(capacity);
  }

  /**
   * Returns the block manager, ensuring a minimum number of entries.
   */
  public static IndexCache create()
  {
    if (_staticCache == null) {
      int size;
      
      if (Alarm.isTest())
        size = 8 * 1024;
      else
        size = 64 * 1024;
      
      _staticCache = new IndexCache(size);
    }

    return _staticCache;
  }

  public static IndexCache getCurrent()
  {
    return _staticCache;
  }

  /**
   * Gets the index entry.
   */
  public long lookup(BTree btree,
                     byte []buffer, int offset, int length,
                     DbTransaction xa)
    throws SQLException
  {
    IndexKey value = lookupValue(btree, buffer, offset, length);

    if (value != null && value.isValid()) {
      return value.getValue();
    }

    long btreeValue;
    
    try {
      btreeValue = btree.lookup(buffer, offset, length);
    } catch (IOException e) {
      throw new SQLException(e);
    }

    value = IndexKey.create(btree, buffer, offset, length, btreeValue);
    value.setValid(true);

    _cache.compareAndPut(null, value, value);

    return btreeValue;
  }

  /**
   * Gets the index entry.
   */
  public void insert(BTree btree,
                     byte []buffer, int offset, int length,
                     long value,
                     DbTransaction xa)
    throws SQLException
  {
    IndexKey key = IndexKey.create(btree, buffer, offset, length, value);

    if (! _cache.compareAndPut(null, key, key)) {
      // XXX:
      throw new SQLException(L.l("duplicate key exception"));
    }

    long btreeValue;
    
    try {
      btreeValue = btree.lookup(buffer, offset, length);
    } catch (IOException e) {
      throw new SQLException(e);
    }

    if (btreeValue != 0) {
      key.setValue(btreeValue);
      key.setValid(true);
      
      throw new SQLException(L.l("duplicate key exception"));
    }
    
    key.setValid(true);
  }

  /**
   * Remove the index entry.
   */
  public void delete(BTree btree,
                     byte []buffer, int offset, int length,
                     DbTransaction xa)
    throws SQLException
  {
    IndexKey value = lookupValue(btree, buffer, offset, length);

    if (value != null) {
      value.setValue(0); // any updates will get written by the thread
    }
    else {
      btree.remove(buffer, offset, length);
    }
  }

  private IndexKey lookupValue(BTree btree,
                               byte []buffer, int offset, int length)
  {
    IndexKey key = _freeKey.getAndSet(null);

    if (key == null)
      key = new IndexKey();

    key.init(btree, buffer, offset, length);

    IndexKey value = _cache.get(key);

    if (value == null)  {
      synchronized (_writeQueue) {
        int size = _writeQueue.size();

        for (int i = 0; i < size; i++) {
          IndexKey writeKey = _writeQueue.get(i);

          if (key.equals(writeKey)) {
            value = writeKey;
            _cache.compareAndPut(null, value, value);
          }
        }
      }
    }

    _freeKey.set(key);

    return value;
  }

  /**
   * Adds a block that's needs to be flushed.
   */
  void addWrite(IndexKey key)
  {
    key.setStored(true);
    
    while (_writeQueue.size() > 1024) {
      _indexWriter.wake();
    
      synchronized (_writeQueue) {
        if (_writeQueue.size() > 1024) {
          try {
            _writeQueue.wait(1000);
          } catch (Exception e) {
            log.log(Level.FINEST, e.toString(), e);
          }
        }
      }
    }

    synchronized (_writeQueue) {
      _writeQueue.add(key);
    }
    
    _indexWriter.wake();
  }

  class IndexCacheWriter extends TaskWorker {
    public long runTask()
    {
      DbTransaction xa = DbTransaction.create();
      
      try {
        IndexKey key = null;

        Thread.interrupted();

        synchronized (_writeQueue) {
          if (_writeQueue.size() == 0)
            return -1;

          key = _writeQueue.get(0);
        }

        if (key != null) {
          BTree btree = key.getBTree();
          long value = key.getValue();

          if (! key.isStored()) {
          }
          else if (value != 0) {
            btree.insert(key.getBuffer(), key.getOffset(), key.getLength(),
                         value, true);
          }
          else {
            btree.remove(key.getBuffer(), key.getOffset(), key.getLength());
          }
        }

        synchronized (_writeQueue) {
          if (key != null)
            _writeQueue.remove(0);

          _writeQueue.notify();
        }
      } catch (Throwable e) {
        log.log(Level.WARNING, e.toString(), e);
      }
      
      return -1;
    }
  }
}
