#<p>
# This file contains support for a Property service.
#</p>
#<p>
#  Author: Steve Wampler (sbw@tapestry.tucson.az.us)
#</p>
#<p>
#  <i>This file is in the public domain.</i>
#</p>

package lang

#<p>
# A <i>property</i> is a simple name/value pair that represents an
# item of information that is available to all components in an
# application.  The value of a property can be any Unicon entity,
# such as a string, set, table, class, etc. though a specific
# implementation of a property manager may impose some restrictions.
# </p>
# <p>
# This file provides two classes for
# managing properties: <tt>SProperty</tt> is simple property
# support that is local to a single program's execution instance,
# while <tt>Property</tt> provides <i>persistent</i> properties
# that survive program execution.
# The procedure <tt>useSimpleProperties()</tt> turns the
# <tt>Property</tt> references into <tt>SProperty</tt>
# references.  For example, the following program uses
# either the default Property class or SProperty class
# depending on the presence of the command-line option
# <tt>--simple</tt>:
#<pre>
#    import lang, util
#    link "ximage"
#
#    procedure main(A)
#        if Args(A).getOpt("simple") then useSimpleProperties()
#
#        Property("myDBname")
#        # Set a few properties...
#        Property().setP("A", 5)
#        t := table()
#        t["a"] := "hello"
#        t["b"] := "world"
#        Property().setP("B", t)
#
#        # Now, get the values
#        write("A is ",Property().getP("A"))
#        write("B is ",ximage(Property().getP("B")))
#    end
#</pre>
#</p>

import propertydb
link "ximage"

#<p>
#  Switch over to using the simple (transient) property service.
#</p>
procedure useSimpleProperties()
    SProperty()
    Property := SProperty
end

#<p>
# A singleton class that provides global access to a set of
# properties throughout a program's execution.  Note that,
# unlike the <tt>Property</tt> class, <tt>SProperty</tt> does
# not provide persistence outside of a single execution.
# Any Unicon entity can be the value of a property managed
# by <tt>SProperty</tt>.
#</p>
class SProperty : Object (pTable)

   #<p>
   # Produce the number of properties.
   # <[return the number of properties]>
   #</p>
   method count()
      return *pTable
   end

   #<p>
   # Produce a set of the names of all of the properties.
   # <[return set of property names]>
   #</p>
   method getAllNames()
      local k
      k := ::set()
      every ::insert(k, ::key(pTable))
      return k
   end

   #<p>
   #  Produce a table containing all of the properties.
   #  The keys are the property names.
   #  <[return table of all the properties]>
   #</p>
   method getAllProperties()
      return pTable
   end

   # <p>
   # Set a property's value.
   # <[param name name of the property]>
   # <[param value value for the named property]>
   # </p>
   method setP(name, value)
      /pTable := ::table()
      pTable[name] := value
   end

   #<p>
   # Remove all properties from the database
   #</p>
   method clearAll()
      pTable := ::table()
   end

   #<p>
   # Remove a property from the database
   # <[param pName name of property to remove]>
   #</p>
   method removeP(pName)
      ::delete(pTable, pName)
   end

   # <p>
   # Produce a property's value.
   # <[param pName name of the property]>
   # <[return the current value of the named property]>
   # </p>
   method getP(pName)
      /pTable := ::table()
      return pTable[pName]
   end

   #<p>
   # Produce a property's value as an <tt>ximage()</tt> string.
   # <[param pName name of the property]>
   # <[return the <tt>ximage()</tt> string value of the named property]>
   #</p>
   method getPString(pName)
      return ximage(pTable[pName])
   end

   #<p>
   # This is a NO-OP in the <tt>SProperty</tt> class.
   #</p>
   method switchPropertyTable(tableName)
      # NO-OP!
   end

#<p>
# Construct a singleton SProperty class.
#</p>
initially()
   SProperty := create |self
end

#<p>
# A singleton class that provides global access to a set of
# persistent properties.
# Persistent properties are stored in an SQL database and can be
# retrieved by other program executions.
# A persistent property is a simple name/value pair that represents an
# item of information that is available across Unicon applications.
# The value can be any Unicon type that can be converted to and from a
# JSON string representation by the <tt>json</tt> support.
# <b>Note that this should not include structures containing multiple
# references to the same structure (e.g. circular graphs, etc.) as
# the fetched property will not match the saved property.</b>
#</p>
#<p>
#<i>
# The base implementation works with PostgreSQL 9.6+ via ODBC.
# All of the database-specific code can be found in the class
# <tt>propertydb::PropertyUtil</tt>, found in the file
# <tt>$UNICON/src/lib/pdb_util.icn</tt> which can serve as a template
# for adapting to different SQL databases.  The <tt>Property</tt>
# constructor can be passed the string name of the required
# adaptation class, e.g. <tt>"MySQL_utils"</tt> (or whatever).
#</i>
#</p>
class Property : Object (pdb)

   #<p>
   # Produce the number of properties.
   # <[return the number of properties]>
   #</p>
   method count()
      # This is inefficient, but not horribly worse than alternatives...
      return *getAllNames()
   end

   #<p>
   # Produce a set of the names of all of the properties.
   # <[return set of property names]>
   #</p>
   method getAllNames()
      return pdb.fetchAllNames()
   end

   #<p>
   #  Produce a table containing all of the properties.
   #  The keys are the property names.
   #  <[return table of all the properties]>
   #</p>
   method getAllProperties()
      local t, k
      t := pdb.fetchAll()
      every k := ::key(t) do t[k] := Union().decode(t[k])
      return t
   end

   #<p>
   # Remove all properties from the database
   #</p>
   method clearAll()
      pdb.clearAll()
   end

   #<p>
   # Remove a property from the database
   # <[param pName name of property to remove]>
   #</p>
   method removeP(pName)
      pdb.removeProperty(pName)
   end

   #<p>
   # Set a property's value.
   # <[param pName name of the property]>
   # <[param value value for the named property]>
   #</p>
   method setP(pName, value)
      pdb.store(pName, Union().encode(value))
   end

   #<p>
   # Produce a property's value.
   # <[param pName name of the property]>
   # <[return the current value of the named property]>
   #</p>
   method getP(pName)
      return Union().decode(getPString(pName))
   end

   #<p>
   # Produce a property's value as a JSON string.
   # <[param pName name of the property]>
   # <[return the JSON string value of the named property]>
   #</p>
   method getPString(pName)
      return pdb.fetch(pName)
   end

   #<p>
   # Switch to a different property table in the database.
   # Reference properties in a different table.  The table
   # must be (and will be created there if not) in the same
   # SQL database as the original.
   # <[param tableName name of property table to use]>
   #</p>
   method switchPropertyTable(tableName)
      pdb.switchPropertyTable(tableName)
   end

#<p>
# Construct a singleton Property class.
   # <[param user name of user owning the database table]>
   # <[param passwd that user's database access password (defaults to "")]>
   # <[param dsn database name as known by ODBC].  The default is the
   #       same as the user name]>
   # <[param tabl name of the property table in that database
   #      (defaults to <tt>"property_table"</tt>)]>
   # <[param uClass name of the support class for a specific
   #     type of SQL database.  The default is
   #     <tt>"propertydb::PropertyUtil"</tt> which is suitable for
   #     PostgreSQL 9.6+ (and possibly others).  See the
   #     <tt>PropertyDB</tt> class for more details.]>
#</p>
initially(user, passwd:"", dsn, tabl, uClass)
   if pdb := PropertyDB(user,passwd,dsn,tabl,uClass) then
       Property := create |self
   else ::stop("Cannot open PropertyDB")
end
