/*
 * The contents of this file are subject to the Mozilla Public License Version 1.1
 * (the "License"); you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at <http://www.mozilla.org/MPL/>.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
 * language governing rights and limitations under the License.
 * 
 * The Original Code is the Venice Web Communities System.
 * 
 * The Initial Developer of the Original Code is Eric J. Bowersox <erbo@silcom.com>,
 * for Silverwrist Design Studios.  Portions created by Eric J. Bowersox are
 * Copyright (C) 2003 Eric J. Bowersox/Silverwrist Design Studios.  All Rights Reserved.
 * 
 * Contributor(s): 
 */
package com.silverwrist.dynamo.unistore;

import java.io.*;
import java.lang.ref.*;
import java.util.*;
import org.apache.commons.collections.*;
import com.silverwrist.dynamo.Namespaces;
import com.silverwrist.dynamo.db.NamespaceCache;
import com.silverwrist.dynamo.event.*;
import com.silverwrist.dynamo.except.*;
import com.silverwrist.dynamo.iface.*;
import com.silverwrist.dynamo.util.*;

class TextPartImpl implements UniStoreTextPart
{
  /*--------------------------------------------------------------------------------
   * Attributes
   *--------------------------------------------------------------------------------
   */

  private TextPartOps m_ops;
  private NamespaceCache m_nscache;
  private PostDynamicUpdate m_post;
  private MessageImpl m_parent;
  private int m_part;
  private QualifiedNameKey m_identity;
  private String m_mimetype;
  private int m_size;
  private int m_linecount;
  private int m_nread;
  private java.util.Date m_lastread;
  private ReferenceMap m_properties;
  private SoftReference m_text = null;

  /*--------------------------------------------------------------------------------
   * Constructors
   *--------------------------------------------------------------------------------
   */

  TextPartImpl(TextPartOps ops, NamespaceCache nscache, PostDynamicUpdate post, MessageImpl parent, Map params)
      throws DatabaseException
  {
    m_ops = ops;
    m_nscache = nscache;
    m_post = post;
    m_parent = parent;
    m_part = ((Integer)(params.get(MessageOps.PARAM_PART))).intValue();
    PropertyKey pk = (PropertyKey)(params.get(MessageOps.PARAM_IDENTITY));
    m_identity = new QualifiedNameKey(nscache.namespaceIdToName(pk.getNamespaceID()),pk.getName());
    m_mimetype = (String)(params.get(MessageOps.PARAM_MIMETYPE));
    Integer tmp = (Integer)(params.get(MessageOps.PARAM_SIZE));
    if (tmp!=null)
      m_size = tmp.intValue();
    tmp = (Integer)(params.get(MessageOps.PARAM_LINECOUNT));
    if (tmp!=null)
      m_linecount = tmp.intValue();
    m_nread = ((Integer)(params.get(MessageOps.PARAM_READS))).intValue();
    m_lastread = (java.util.Date)(params.get(MessageOps.PARAM_LASTREAD));
    m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

  } // end constructor

  TextPartImpl(PostDynamicUpdate post, MessageImpl parent, int part, QualifiedNameKey identity)
  {
    m_ops = null;
    m_nscache = null;
    m_post = post;
    m_parent = parent;
    m_part = part;
    m_identity = identity;
    m_mimetype = null;
    m_size = -1;
    m_linecount = -1;
    m_nread = 0;
    m_lastread = null;
    m_properties = new ReferenceMap(ReferenceMap.HARD,ReferenceMap.SOFT);

  } // end constructor

  /*--------------------------------------------------------------------------------
   * Overrides from class Object
   *--------------------------------------------------------------------------------
   */

  public String toString()
  {
    if (m_ops==null)
      return "(deleted text part)";
    return "message " + m_parent.getMessageID() + ", text part " + m_identity.toString();

  } // end toString

  /*--------------------------------------------------------------------------------
   * Implementations from interface ObjectProvider
   *--------------------------------------------------------------------------------
   */

  /**
   * Retrieves an object from this <CODE>ObjectProvider</CODE>.
   *
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be retrieved.
   * @return The object reference specified.
   */
  public Object getObject(String namespace, String name)
  {
    if (m_ops==null)
      throw new NoSuchObjectException(this.toString(),namespace,name);
    try
    { // convert the namespace name to an ID here
      PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
      Object rc = null;
      synchronized (this)
      { // start by looking in the properties map
	rc = m_properties.get(key);
	if (rc==null)
	{ // no use - need to try the database
	  rc = m_ops.getProperty(m_parent.getMessageID(),m_part,key);
	  if (rc!=null)
	    m_properties.put(key,rc);

	} // end if

      } // end synchronized block

      if (rc==null)
	throw new NoSuchObjectException(this.toString(),namespace,name);
      return rc;

    } // end try
    catch (DatabaseException e)
    { // translate into our NoSuchObjectException but retain the DatabaseException
      throw new NoSuchObjectException(this.toString(),namespace,name,e);

    } // end catch

  } // end getObject

  /*--------------------------------------------------------------------------------
   * Implementations from interface SecureObjectStore
   *--------------------------------------------------------------------------------
   */

  /**
   * Sets an object into this <CODE>SecureObjectStore</CODE>.
   *
   * @param caller The user performing the operation.
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be set.
   * @param value The object to set into the <CODE>SecureObjectStore</CODE>.
   * @return The previous object that was set into the <CODE>SecureObjectStore</CODE> under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error setting the object value.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            set this object value into this <CODE>SecureObjectStore</CODE>.
   */
  public Object setObject(DynamoUser caller, String namespace, String name, Object value)
      throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    m_parent.testPermission(caller,namespace,"set.property","no.setProperty");
    Object rc = null;
    // convert the namespace name to an ID here
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    synchronized (this)
    { // start by setting the database value
      rc = m_ops.setProperty(m_parent.getMessageID(),m_part,key,value);
      
      // and cache it, too
      m_properties.put(key,value);

    } // end synchronized block

    m_post.postUpdate(new MessagePartPropertyUpdateEvent(this,namespace,name));
    return rc;

  } // end setObject

  /**
   * Removes an object from this <CODE>SecureObjectStore</CODE>.
   *
   * @param caller The user performing the operation.
   * @param namespace The namespace to interpret the name relative to.
   * @param name The name of the object to be removed.
   * @return The previous object that was set into the <CODE>SecureObjectStore</CODE> under this namespace and name, or
   *         <CODE>null</CODE> if there was no such object.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error removing the object value.
   * @exception com.silverwrist.dynamo.except.DynamoSecurityException If the specified user is not permitted to
   *            remove this object value from this <CODE>SecureObjectStore</CODE>.
   */
  public Object removeObject(DynamoUser caller, String namespace, String name)
      throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    m_parent.testPermission(caller,namespace,"remove.property","no.removeProperty");
    Object rc = null;
    // convert the namespace name to an ID here
    PropertyKey key = new PropertyKey(m_nscache.namespaceNameToId(namespace),name);
    synchronized (this)
    { // start by killing the database value
      rc = m_ops.removeProperty(m_parent.getMessageID(),m_part,key);

      // and remove the cached value, too
      m_properties.remove(key);

    } // end synchronized block

    m_post.postUpdate(new MessagePartPropertyUpdateEvent(this,namespace,name));
    return rc;

  } // end removeObject

  /**
   * Returns a collection of all object namespaces that have been set into this <CODE>SecureObjectStore</CODE>.
   *
   * @return A {@link java.util.Collection Collection} containing {@link java.lang.String String} objects specifying
   *         all the object namespaces.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the namespace list.
   */
  public Collection getNamespaces() throws DatabaseException
  {
    if (m_ops==null)
      return Collections.EMPTY_LIST;

    // call through to the database to get the list of namespace IDs
    int[] ids = m_ops.getPropertyNamespaceIDs(m_parent.getMessageID(),m_part);

    ArrayList rc = new ArrayList(ids.length);
    for (int i=0; i<ids.length; i++)
      rc.add(m_nscache.namespaceIdToName(ids[i]));
    return Collections.unmodifiableList(rc);

  } // end getNamespaces

  /**
   * Returns a collection of all object names that have been set into this <CODE>SecureObjectStore</CODE> under
   * a given namespace.
   *
   * @param namespace The namespace to look for names under.
   * @return A {@link java.util.Collection Collection} containing {@link java.lang.String String} objects
   *         specifying all the object names for this namespace.
   * @exception com.silverwrist.dynamo.except.DatabaseException If there was an error getting the object name list.
   */
  public Collection getNamesForNamespace(String namespace) throws DatabaseException
  {
    if (m_ops==null)
      return Collections.EMPTY_LIST;

    // call through to the database to get the data for this namespace
    int nsid = m_nscache.namespaceNameToId(namespace);
    Map data = m_ops.getAllProperties(m_parent.getMessageID(),m_part,nsid);

    // we both create the return value and cache the data values
    ArrayList rc = new ArrayList(data.size());
    synchronized (this)
    { // do the transfer...
      Iterator it = data.entrySet().iterator();
      while (it.hasNext())
      { // copy one entry at a time
	Map.Entry ntry = (Map.Entry)(it.next());
	rc.add(ntry.getKey().toString());
	m_properties.put(new PropertyKey(nsid,ntry.getKey().toString()),ntry.getValue());

      } // end while

    } // end synchronized block

    return Collections.unmodifiableList(rc);

  } // end getNamesForNamespace

  /*--------------------------------------------------------------------------------
   * Implementations from interface UniStorePart
   *--------------------------------------------------------------------------------
   */

  public long getMessageID()
  {
    return (m_parent==null) ? -1L : m_parent.getMessageID();

  } // end getMessageID

  public UniStoreMessage getMessage()
  {
    return m_parent;

  } // end getMessage

  public int getPartIndex()
  {
    return m_part;

  } // end getPartIndex

  public QualifiedNameKey getPartIdentity()
  {
    return m_identity;

  } // end getPartIdentity

  public String getMimeType()
  {
    return m_mimetype;

  } // end getMimeType

  public int getSize()
  {
    return m_size;

  } // end getSize

  public int getNumReads()
  {
    return m_nread;

  } // end getNumReads

  public java.util.Date getLastReadDate()
  {
    return m_lastread;

  } // end getLastReadDate

  public void touchRead() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    synchronized (this)
    { // touch the database, then the local values
      java.util.Date tmp = m_ops.touchRead(m_parent.getMessageID(),m_part);
      m_nread++;
      m_lastread = tmp;

    } // end synchronized block

    m_post.postUpdate(new MessagePartRead(this));

  } // end touchRead

  public synchronized void delete(DynamoUser caller) throws DatabaseException, DynamoSecurityException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    m_parent.testPermission(caller,Namespaces.UNISTORE_PERMISSIONS_NAMESPACE,"delete.part","no.deletePart");

    // Cut this object loose from the parent.
    m_parent.deletedTextPart(m_part,m_identity);

    // Zap it from the database.
    m_ops.delete(m_parent.getMessageID(),m_part);

    baleeted();  // BALEETED!

  } // end delete

  /*--------------------------------------------------------------------------------
   * Implementations from interface UniStoreTextPart
   *--------------------------------------------------------------------------------
   */

  public int getLineCount()
  {
    return m_linecount;

  } // end getLineCount

  public synchronized String getText() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    String rc = null;
    if (m_text!=null)
      rc = (String)(m_text.get());
    if (rc==null)
    { // load text fom database and reload to reference
      rc = m_ops.getText(m_parent.getMessageID(),m_part);
      m_text = new SoftReference(rc);

    } // end if

    return rc;

  } // end getText

  public Reader getTextAsReader() throws DatabaseException
  {
    if (m_ops==null)
      throw new DatabaseException(TextPartImpl.class,"UniStoreMessages","part.deleted");
    return new StringReader(this.getText());

  } // end getTextAsReader

  /*--------------------------------------------------------------------------------
   * External operations
   *--------------------------------------------------------------------------------
   */

  void precacheText(String txt)
  {
    m_text = new SoftReference(txt);

  } // end txt

  void resetPartNumber(int new_num)
  {
    m_part = new_num;

  } // end resetPartNumber

  /**
   * Called after the part has been deleted, either alone or through the entire message being deleted.  This
   * method nulls out the internal data of the object and posts a "part-deleted" notification.<P>
   * See <A HREF="http://www.homestarrunner.com/sbemail50.html">this page</A> for the source of the method name.
   */
  synchronized void baleeted()
  {
    // Cut loose most of our data before we post an update event.
    m_ops = null;
    m_nscache = null;
    m_mimetype = null;
    m_size = -1;
    m_linecount = -1;
    m_nread = 0;
    m_lastread = null;
    m_properties.clear();
    if (m_text!=null)
      m_text.clear();
    m_text = null;

    // Post the "deleted" notification event.
    m_post.postUpdate(new MessagePartDeletedEvent(this));

    // Now cut loose the rest of our data.
    m_post = null;
    m_parent = null;
    m_part = -1;
    m_identity = null;

  } // end baleeted

} // end class TextPartImpl
