/* **************************************************************************

  $Archive: /njcl/src/com/novell/service/file/nw/naming/ReferencePageImpl.java $
  $Revision: 1 $
  $Modtime: 3/03/98 10:57a $
 
  Copyright (c) 1998 Novell, Inc.  All Rights Reserved.

  THIS WORK IS  SUBJECT  TO  U.S.  AND  INTERNATIONAL  COPYRIGHT  LAWS  AND
  TREATIES.   NO  PART  OF  THIS  WORK MAY BE  USED,  PRACTICED,  PERFORMED
  COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED,  ABRIDGED, CONDENSED,
  EXPANDED,  COLLECTED,  COMPILED,  LINKED,  RECAST, TRANSFORMED OR ADAPTED
  WITHOUT THE PRIOR WRITTEN CONSENT OF NOVELL, INC. ANY USE OR EXPLOITATION
  OF THIS WORK WITHOUT AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO
  CRIMINAL AND CIVIL LIABILITY.

****************************************************************************/

package com.novell.service.file.nw.naming;

import java.io.EOFException;
import java.io.InputStream;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.Hashtable;

import javax.naming.Reference;

/** @internal
 * This class is a collection of named instances of the
 * javax.naming.Reference class.  This class implements the ReferencePage
 * interface using java.util.Hashtable.  This implementation
 * supports storing any serializeable object, including Reference,
 * BinaryRefAddr and StringRefAddr reference addresses.
 *
 * @see javax.naming.Reference
 * @see javax.naming.RefAddr
 */
public class ReferencePageImpl implements ReferencePage
{
   OutputStream dest;
   Hashtable hash;

   /**
    * Constructs an empty reference page.
    */
   public ReferencePageImpl ()
   {
      hash = new Hashtable ();
   }

   /**
    * Constructs a reference page and fills it by reading references from
    * the provided input stream.
    *
    * @param      src            The input stream from which to read
    *                            the references to put in this reference
    *                            page.
    * @exception  IOException    When an error occurs reading the stream.
    * @exception  ClassNotFoundException When a reference address is
    *                            encountered which is not supported.
    *                            See header for info on which reference
    *                            addresses are supported.
    * @see ReferencePageImpl
    */
   public ReferencePageImpl (InputStream src)
      throws IOException, ClassNotFoundException
   {
      hash = new Hashtable ();

      readFromStream (src);
   }

   /**
    * Returns number of references stored in this reference page.
    *
    * @return                    The number of references in this
    *                            reference page.
    */
   public int size ()
   {
      return (hash.size ());
   }

   /**
    * Tests whether this reference page is empty.
    *
    * @return                    true if this reference page is empty,
    *                            false otherwise.
    */
   public boolean isEmpty ()
   {
      return (hash.isEmpty ());
   }

   /**
    * Returns enumeration of the names of all references in this
    * reference page.
    *
    * @return                    StringEnumeration of names of all the
    *                            references.
    */
   public Enumeration names ()
   {
      return (new StringEnumerator (hash.keys ()));
   }

   /**
    * Returns enumeration of the references in this reference page.
    *
    * @return                    ReferenceEnumeration of all references.
    */
   public ReferenceEnumeration references ()
   {
      return (new ReferenceEnumerator (hash.elements ()));
   }

   /**
    * Returns a reference given a name.
    *
    * <p>Returns null if there is no reference for the given name.
    * </p>
    *
    * @param      name           The name of the reference to return.
    * @return                    Reference corresponding to the name.
    */
   public Reference getReference (String name)
   {
      return ((Reference) hash.get (name));
   }

   /**
    * Puts a Reference in this ReferencePage.
    *
    * @param      name           The name of the new reference.
    * @param      reference      The reference to store.
    * @return                    If no reference exists under the given
    *                            name, null is returned.  If a reference
    *                            already exists under the given name,
    *                            it is returned, and the new
    *                            reference takes its place.
    */
   public Reference putReference (String name, Reference reference)
   {
      return ((Reference) hash.put (name, reference));
   }

   /**
    * Removes a reference from this reference page.
    *
    * @param      name           The name of the reference to be removed.
    * @return                    Removed reference.
    */
   public Reference removeReference (String name)
   {
      return ((Reference) hash.remove (name));
   }

   /**
    * Reads references from an input stream.
    *
    * <p>This implementation only reads the BinaryRefAddr and StringRefAddr
    * classes.  If any other reference address classes are encountered,
    * an exception is thrown.
    * </p>
    *
    * @param      src            The input stream from which to read
    *                            references.
    * @exception  IOException    When an error occurs reading the stream.
    * @exception  ClassNotFoundException When a class stored on the stream
    *                            cannot be found or loaded.
    */
   public void readFromSource (InputStream src)
      throws IOException, ClassNotFoundException
   {
      readFromStream (src);
   }

   /**
    * Sets the destination stream for storing all references from this
    * reference page on a stream.
    *
    * @param      dest           The output stream on which to store all
    *                            references in this reference page.
    */
   public void setDestination (OutputStream dest)
   {
      this.dest = dest;
   }

   /**
    * Stores all references on the previously determined output stream.
    *
    * <p>The output stream that is used to store the references is set
    * by calling the setDestination() method.
    * </p>
    *
    * <p>This implementation only stores the BinaryRefAddr and StringRefAddr
    * classes.  If any other RefAddr classes are encountered, an exception
    * is thrown.
    * </p>
    *
    * @exception  IOException    When an error occurs reading the stream.
    * @see #setDestination
    */
   public void flush ()
      throws IOException
   {
      writeToStream (dest);

      dest.flush ();
   }

   /**
    * Does the work for readFromSource().
    *
    * @exception  IOException    When an error occurs reading the stream.
    * @exception  ClassNotFoundException When a class stored on the stream
    *                            cannot be found or loaded.
    */
   protected synchronized void readFromStream (InputStream src)
      throws IOException, ClassNotFoundException
   {
      ObjectInput i = new ObjectInputStream (src);

      int count = 0;
      
      // try to read the number of references stored on the stream
      try
      {
         count = i.readInt ();
      }
      catch (EOFException e)
      {
         // if the stream is empty, return, and don't read anything
         return;
      }

      // read as many references as are stored
      while (count-- > 0)
      {
         String refName = i.readUTF ();
         Reference ref = (Reference) i.readObject ();

         // put the reference into the Hashtable under the reference name
         hash.put (refName, ref);
      }
   }

   /**
    * Does the work for flush().
    *
    * @exception  IOException    When an error occurs reading the stream.
    */
   protected synchronized void writeToStream (OutputStream dest)
      throws IOException
   {
      if (dest == null)
         throw new NullPointerException ();

      ObjectOutput o = new ObjectOutputStream (dest);
      Enumeration keys = hash.keys ();

      // write number of references that will be stored
      o.writeInt (hash.size ());

      // rely on the keys enumeration to give us that many references
      while (keys.hasMoreElements ())
      {
         String refName = (String) keys.nextElement ();
         Reference ref = (Reference) hash.get (refName);

         // write name of reference and the reference itself
         o.writeUTF (refName);
         o.writeObject (ref);
      }
   }
}

class StringEnumerator implements Enumeration
{
   Enumeration e;

   public StringEnumerator (Enumeration e)
   {
      this.e = e;
   }

   public boolean hasMoreElements ()
   {
      return (e.hasMoreElements ());
   }

   public Object nextElement ()
   {
      return (next ());
   }

   public String next ()
   {
      Object o = e.nextElement ();

      if (o instanceof String)
         return ((String) o);
      else
         return (null);
   }
}

class ReferenceEnumerator implements ReferenceEnumeration
{
   Enumeration e;

   public ReferenceEnumerator (Enumeration e)
   {
      this.e = e;
   }

   public boolean hasMoreElements ()
   {
      return (e.hasMoreElements ());
   }

   public Object nextElement ()
   {
      return (next ());
   }

   public Reference next ()
   {
      Object o = e.nextElement ();

      if (o instanceof Reference)
         return ((Reference) o);
      else
         return (null);
   }
}
