#=================================================================================================
# Ruby API of Villa, the advanced API of QDBM
#                                                       Copyright (C) 2000-2003 Mikio Hirabayashi
# This file is part of QDBM, Quick Database Manager.
# QDBM is free software; you can redistribute it and/or modify it under the terms of the GNU
# Lesser General Public License as published by the Free Software Foundation; either version
# 2.1 of the License or any later version.  QDBM 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 Lesser General Public License for more
# details.
# You should have received a copy of the GNU Lesser General Public License along with QDBM; if
# not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# 02111-1307 USA.
#=================================================================================================


require 'mod_villa'
require 'thread'


##
# require 'villa'
# The library `villa' should be included in application codes.
# An instance of the class `Villa' is used as a database handle.
# `Villa' performs Mix-in of `Enumerable'.
# Each method of `Villa' throws an exception of `Villa::EANY' or its sub classes when an error
# occurs: `Villa::ENOERR', `Villa::EFATAL', `Villa::EMODE', `Villa::EBROKEN', `Villa::EKEEP',
# `Villa::ENOITEM', `Villa::EALLOC', `Villa::EMAP', `Villa::EOPEN', `Villa::ECLOSE',
# `Villa::ETRUNC', `Villa::ESYNC', `Villa::ESTAT', `Villa::ESEEK', `Villa::EREAD',
# `Villa::EWRITE', `Villa::ELOCK', `Villa::EUNLINK', `Villa::EMKDIR', `Villa::ERMDIR' and
# `Villa::EMISC'.
#
class Villa
  include Mod_Villa, Enumerable
  #----------------------------------------------------------------
  # class constants
  #----------------------------------------------------------------
  MyMutex = Mutex::new()
  #----------------------------------------------------------------
  # class methods
  #----------------------------------------------------------------
  public
  ##
  # villa = Villa::new(name, omode, bnum)
  # Constructor: Get a database handle.
  # `name' specifies the name of a database file.
  # `omode' specifies the connection mode: `Villa::OWRITER' as a writer, `Villa::OREADER' as a
  # reader.  If the mode is `Villa::OWRITER', the following may be added by bitwise or:
  # `Villa::OCREAT', which means it creates a new database if not exist, `Villa::OTRUNC', which
  # means it creates a new database regardless if one exists.  Both of `Villa::OREADER' and
  # `Villa::OWRITER' can be added to by bitwise or: `Villa::ONOLCK', which means it opens a
  # database file without file locking.  If it is omitted, `Villa::OREADER' is specified.
  # `cmode' specifies the comparing function: `Villa::CMPLEX' comparing keys in lexical order,
  # `Villa::CMPDEC' comparing keys as decimal strings, `Villa::CMPOBJ' comparing keys as
  # serialized objects implementing the method `<=>'.  The comparing function should be kept
  # same in the life of a database.  If `Villa::CMPOBJ' is used, such methods as `put', `out',
  # `get' and so on can treat not only instances of `String' but also any serializable and
  # comparable object.
  # If so, objects being stored are serialized and objects being retrieved are deserialized.
  # The return value is the database handle.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # If a block parameter is given, this method works as an iterator.  A database handle is
  # opened and passed via the first argument of the block.  The database handle is surely
  # closed when the block is over.
  # While connecting as a writer, an exclusive lock is invoked to the database file.
  # While connecting as a reader, a shared lock is invoked to the database file.  The thread
  # blocks until the lock is achieved.  If `Villa::ONOLCK' is used, the application is
  # responsible for exclusion control.
  #
  #@ DEFINED IMPLICITLY
  ##
  # villa = Villa::open(name, omode, cmp)
  # Constructor: An alias of `new'.
  #
  #@ DEFINED OUTSIDE
  #----------------------------------------------------------------
  # private methods
  #----------------------------------------------------------------
  private
  #=
  # initialize(name, omode, cmode)
  # Method: Called implicitly by the constructor.
  #
  def initialize(name, omode = OREADER, cmode = CMPLEX)
    MyMutex.synchronize() do
      @index = mod_open(name, omode, cmode)
      @name = name
      @cmode = cmode
      @tranmutex = Mutex::new()
    end
    if(iterator?)
      begin
        yield(self)
      ensure
        close()
      end
    end
    self
  end
  #=
  # clone()
  # Method: Forbidden to use.
  #
  def clone
    raise(VillaError)
  end
  #=
  # dup()
  # Method: Forbidden to use.
  #
  alias dup clone
  #----------------------------------------------------------------
  # public methods
  #----------------------------------------------------------------
  public
  ##
  # bool = villa.close()
  # Method: Close the database handle.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # Because the region of a closed handle is released, it becomes impossible to use the handle.
  # Updating a database is assured to be written when the handle is closed.  If a writer opens
  # a database but does not close it appropriately, the database will be broken.  If the
  # transaction is activated and not committed, it is aborted.
  #
  def close()
    MyMutex.synchronize() do
      begin
        mod_close(@index)
      ensure
        @index = -1
      end
    end
  end
  ##
  # bool = villa.put(key, val, dmode)
  # Method: Store a record.
  # `key' specifies a key.  Although it must be an instance of String, binary data is okey.
  # `val' specifies a value.  Although it must be an instance of String, binary data is okey.
  # `dmode' specifies behavior when the key overlaps, by the following values: `Villa::DOVER',
  # which means the specified value overwrites the existing one, `Villa::DKEEP', which means
  # the existing value is kept, `Villa::DDUP', which means duplication of keys is allowed.  If
  # it is omitted `Villa::DOVER' is specified.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or replace
  # is cancelled.
  # A duplicated record is stored at the tail of the records of the same key.  The cursor
  # becomes unavailable due to updating database.
  #
  def put(key, val, dmode = DOVER)
    if(@cmode == CMPOBJ)
      key = Marshal.dump(key)
      val = Marshal.dump(val)
    end
    mod_put(@index, key, val, dmode)
  end
  ##
  # bool = villa.store(key, val)
  # Method: An alias of `put'.
  #
  alias store put
  ##
  # str = (villa[key] = val)
  # Method: An alias of `put'.
  #
  alias []= put
  ##
  # bool = villa.out(key)
  # Method: Delete a record.
  # `key' specifies a key.  Although it must be an instance of String, binary data is okey.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or no record
  # corresponds.
  # When the key of duplicated records is specified, the value of the first record of the same
  # key is selected.  The cursor becomes unavailable due to updating database.
  #
  def out(key)
    if(@cmode == CMPOBJ)
      key = Marshal.dump(key)
    end
    mod_out(@index, key)
  end
  ##
  # bool = villa.delete(key)
  # Method: An alias of `out'.
  #
  alias delete out
  ##
  # bool = villa.clear()
  # Method: Delete all records.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def clear
    MyMutex.synchronize() do
      begin
        while(rnum() > 0)
          curfirst();
          out(curkey())
        end
      rescue ENOITEM
      end
    end
    true
  end
  ##
  # str = villa.get(key)
  # Method: Retrieve a record.
  # `key' specifies a key.  Although it must be an instance of String, binary data is okey.
  # The return value is an instance of the value of the corresponding record.
  # When the key of duplicated records is specified, the first record of the same key is
  # deleted.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or no record
  # corresponds.
  #
  def get(key)
    if(@cmode == CMPOBJ)
      key = Marshal.dump(key)
    end
    val = mod_get(@index, key)
    if(@cmode == CMPOBJ)
      val = Marshal.load(val)
    end
    val
  end
  ##
  # str = villa.fetch(key)
  # Method: An alias of `get'.
  #
  alias fetch get
  ##
  # str = villa[key]
  # Method: An alias of `get'.
  #
  alias [] get
  ##
  # num = villa.vnum(key)
  # Method: Get the number of records corresponding a key.
  # `key' specifies a key.  Although it must be an instance of String, binary data is okey.
  # The return value is the size of the value of the corresponding record.  If no record
  # corresponds, 0 is returned.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def vnum(key)
    if(@cmode == CMPOBJ)
      key = Marshal.dump(key)
    end
    mod_vnum(@index, key)
  end
  ##
  # bool = villa.curfirst()
  # Method: Move the cursor to the first record.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or there is
  # no record in the database.
  #
  def curfirst()
    mod_curfirst(@index)
  end
  ##
  # bool = villa.curlast()
  # Method: Move the cursor to the last record.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or there is
  # no record in the database.
  #
  def curlast()
    mod_curlast(@index)
  end
  ##
  # bool = villa.curprev()
  # Method: Move the cursor to the previous record.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or there is
  # no previous record.
  #
  def curprev()
    mod_curprev(@index)
  end
  ##
  # bool = villa.curnext()
  # Method: Move the cursor to the next record.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or there is
  # no next record.
  #
  def curnext()
    mod_curnext(@index)
  end
  ##
  # bool = villa.curjump(key, jmode)
  # Method: Move the cursor to positon around a record.
  # `key' specifies a key.  Although it must be an instance of String, binary data is okey.
  # `jmode' specifies detail adjustment: `Villa::JFORWARD', which means that the cursor is set
  # to the first record of the same key and that the cursor is set to the next substitute if
  # completely matching record does not exist, `Villa::JBACKWARD', which means that the cursor
  # is set to the last record of the same key and that the cursor is set to the previous
  # substitute if completely matching record does not exist.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or there is
  # no record corresponding the condition.
  #
  def curjump(key, jmode = JFORWARD)
    if(@cmode == CMPOBJ)
      key = Marshal.dump(key)
    end
    mod_curjump(@index, key, jmode)
  end
  ##
  # str = villa.curkey()
  # Method: Get the key of the record where the cursor is.
  # The return value is the key of the corresponding record.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or no record
  # corresponds to the cursor.
  #
  def curkey()
    key = mod_curkey(@index)
    if(@cmode == CMPOBJ)
      key = Marshal.load(key)
    end
    key
  end
  ##
  # str = villa.curval()
  # Method: Get the value of the record where the cursor is.
  # The return value is the value of the corresponding record.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or no record
  # corresponds to the cursor.
  #
  def curval()
    val = mod_curval(@index)
    if(@cmode == CMPOBJ)
      val = Marshal.load(val)
    end
    val
  end
  ##
  # bool = villa.settuning(lrecmax, nidxmax, lcnum, ncnum)
  # Method: Set the tuning parameters for performance.
  # `lrecmax' specifies the max number of records in a leaf node of B+ tree.  If it is not more
  # than 0, the default value is specified.
  # `nidxmax' specifies the max number of indexes in a non-leaf node of B+ tree.  If it is not
  # more than 0, the default value is specified.
  # `lcnum' specifies the max number of caching leaf nodes.  If it is not more than 0, the
  # default value is specified.
  # `ncnum' specifies the max number of caching non-leaf nodes.  If it is not more than 0, the
  # default value is specified.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # The default setting is equivalent to `vlsettuning(49, 192, 1024, 512)'.  Because tuning
  # paremeters are not saved in a database, you should specify them every opening a database.
  #
  def settuning(lrecmax, nidxmax, lcnum, ncnum)
    mod_settuning(@index, lrecmax, nidxmax, lcnum, ncnum)
  end
  ##
  # bool = villa.sync()
  # Method: Synchronize updating contents with the file and the device.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # This method is useful when another process uses the connected database file.  This method
  # shuold not be used while the transaction is activated.
  #
  def sync()
    mod_sync(@index)
  end
  ##
  # bool = villa.optimize()
  # Method: Optimize the database.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # In an alternating succession of deleting and storing with overwrite or concatenate,
  # dispensable regions accumulate.  This method is useful to do away with them.  This method
  # shuold not be used while the transaction is activated.
  #
  def optimize()
    mod_optimize(@index)
  end
  ##
  # num = villa.fsiz()
  # Method: Get the size of the database file.
  # The return value is the size of the database file.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # Because of the I/O buffer, the return value may be less than the real size.
  #
  def fsiz()
    mod_fsiz(@index)
  end
  ##
  # num = villa.rnum()
  # Method: Get the number of the records stored in the database.
  # The return value is the number of the records stored in the database.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def rnum()
    mod_rnum(@index)
  end
  ##
  # num = villa.length()
  # Method: An alias of `rnum'.
  #
  alias length rnum
  ##
  # num = villa.size()
  # Method: An alias of `rnum'.
  #
  alias size rnum
  ##
  # bool = villa.writable()
  # Method: Check whether the database handle is a writer or not.
  # The return value is true if the handle is a writer, false if not.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def writable()
    mod_writable(@index)
  end
  ##
  # bool = villa.fatalerror()
  # Method: Check whether the database has a fatal error or not.
  # The return value is true if the database has a fatal error, false if not.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def fatalerror()
    mod_fatalerror(@index)
  end
  ##
  # bool = villa.tranbegin()
  # Method: Begin the transaction.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # If a thread is already in the transaction, the other threads block until the prius is out
  # of the transaction.  Only one transaction can be activated with a database handle at the
  # same time.
  #
  def tranbegin()
    @tranmutex.lock()
    MyMutex.synchronize() do
      begin
        mod_tranbegin(@index)
      rescue
        @tranmutex.unlock()
        raise()
      end
    end
  end
  ##
  # bool = villa.trancommit()
  # Method: Commit the transaction.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # Updating a database in the transaction is fixed when it is committed successfully.  Any
  # other thread except for the one which began the transaction should not call this method.
  #
  def trancommit()
    begin
      mod_trancommit(@index)
    ensure
      @tranmutex.unlock()
    end
  end
  ##
  # bool = villa.tranabort()
  # Method: Abort the transaction.
  # The return value is always `true'.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  # Updating a database in the transaction is discarded when it is aborted.  The state of the
  # database is rollbacked to before transaction.  Any other thread except for the one which
  # began the transaction should not call this method.
  #
  def tranabort()
    begin
      mod_tranabort(@index)
    ensure
      @tranmutex.unlock()
    end
  end
  ##
  # villa.transaction() do ... end
  # Iterator Method: Perform an iterator block in the transaction.
  # The specified iterator block is performed in the transaction.  If the block returns true,
  # the transaction is committed.  If the block returns false or raises any exception, the
  # transaction is aborted.
  #
  def transaction()
    tranbegin()
    begin
      cmt = yield()
    rescue
      cmt = false
      raise()
    ensure
      if(cmt)
        trancommit()
      else
        tranabort()
      end
    end
  end
  ##
  # villa.each() do |key, val| ... end
  # Iterator Method: Iterate a process with a pair of a key and a value of each record.
  #
  def each()
    MyMutex.synchronize() do
      curfirst()
      while(true)
        begin
          key = curkey()
          val = curval()
          yield(key, val)
          curnext()
        rescue ENOITEM
          break
        end
      end
      curfirst()
    end
    self
  end
  ##
  # villa.each_pair() do |key, val| ... end
  # Iterator Method: An alias of `each'.
  #
  alias each_pair each
  ##
  # villa.each_key() do |key| ... end
  # Iterator Method: Iterate a process with a key of each record.
  #
  def each_key()
    MyMutex.synchronize() do
      curfirst()
      while(true)
        begin
          key = curkey()
          curnext()
          yield(key)
        rescue ENOITEM
          break
        end
      end
      curfirst()
    end
    self
  end
  ##
  # villa.each_value() do |val| ... end
  # Iterator Method: Iterate a process with a value of each record.
  #
  def each_value()
    MyMutex.synchronize() do
      curfirst()
      while(true)
        begin
          val = curval()
          curnext()
          yield(val)
        rescue ENOITEM
          break
        end
      end
      curfirst()
    end
    self
  end
  ##
  # ary = villa.keys()
  # Method: Get an array of all keys.
  # The return value is an array of all keys.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def keys()
    ary = Array::new(rnum())
    MyMutex.synchronize() do
      begin
        curfirst()
        0.upto(ary.length - 1) do |i|
          ary[i] = curkey()
          curnext()
        end
        curfirst()
      rescue ENOITEM
      end
    end
    ary
  end
  ##
  # ary = villa.values()
  # Method: Get an array of all values.
  # The return value is an array of all values.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures.
  #
  def values()
    ary = Array::new(rnum())
    MyMutex.synchronize() do
      begin
        curfirst()
        0.upto(ary.length - 1) do |i|
          ary[i] = curval()
          curnext()
        end
        curfirst()
      rescue ENOITEM
      end
    end
    ary
  end
  ##
  # str = villa.index(val)
  # Method: Retrieve a record with a value.
  # `val' specifies a value.  Although it must be an instance of String, binary data is okey.
  # The return value is the key of the record with the specified value.
  # An exception of `Villa::EANY' or its sub classes is thrown if an error occures or no record
  # corresponds.
  # If two or more records correspond, the first found record is selected.
  #
  def index(val)
    key = nil
    MyMutex.synchronize() do
      curfirst()
      while(true)
        if(curval() == val)
          key = curkey()
          break
        end
        curnext()
      end
      curfirst()
    end
    key
  end
  ##
  # num = villa.to_int()
  # Method: An alias of `rnum'.
  #
  alias to_int rnum
  ##
  # num = villa.to_i()
  # Method: An alias of `to_int'.
  #
  alias to_i to_int
  ##
  # str = villa.to_str()
  # Method: Get string standing for the instance.
  #
  def to_str
    if(@index != -1)
      sprintf("#<Villa:%#x:name=%s:state=open:rnum=%d>",
              object_id(), @name, rnum())
    else
      sprintf("#<Villa:%#x:name=%s:state=closed>", object_id(), @name)
    end
  end
  ##
  # str = villa.to_s()
  # Method: An alias of `to_str'.
  #
  alias to_s to_str
  ##
  # ary = villa.to_ary()
  # Method: Get an array of alternation of each pair of a key and a value.
  #
  def to_ary
    ary = Array::new(rnum())
    i = 0
    each() do |key, val|
      ary[i] = [key, val]
      i += 1
    end
    ary
  end
  ##
  # ary = villa.to_a()
  # Method: An alias of `to_ary'.
  #
  alias to_a to_ary
  ##
  # hash = villa.to_hash()
  # Method: Get a hash storing all records.
  #
  def to_hash
    hash = Hash::new()
    each() do |key, val|
      hash[key] || hash[key] = val
    end
    hash
  end
  ##
  # hash = villa.to_h()
  # Method: An alias of `to_hash'.
  #
  alias to_h to_hash
  ##
  # str = villa.inspect()
  # Method: An alias of `to_str'.
  #
  alias inspect to_str
end


#----------------------------------------------------------------
# Alias definition of class methods
#----------------------------------------------------------------
class << Villa
  alias open new
end



# END OF FILE
