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

  $Archive: /njcl_v2/src/com/novell/service/bindery/BinderyPropertyDataSegment.java $
  $Revision: 14 $
  $Modtime: 1/28/00 12:46p $
 
  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.bindery;

import java.util.*;
import com.novell.service.jncp.NSIException;

/**
 * Holds a 128-byte data segment containing the data of a Bindery
 * Property. It is used as an Attribute Value attached to the
 * Bindery Properties attribute.
 *
 * <p>This class is immutable, which means once it is created it
 * cannot be changed (except for adding more data segments). To
 * make modifications, extract the data not to be changed, create
 * a new object with the old object, and add the new data.
 * 
 */
public class BinderyPropertyDataSegment
{
  /**
   * The official length of a data segment in the bindery.
   *
   * <p>(SEGMENT_LENGTH = 128)
   */
   public static final int SEGMENT_LENGTH = 128;

   byte[]  segment = new byte[SEGMENT_LENGTH];
   int     segNum;
   int     segFlag;

  /**
   * Constructs a property data segment from a byte array.
   *
   * @param  seg     The byte array. If this array is less than 128,
   *                 the remainder of the segment is 0 padded.
   * @param  segNum  The number of this segment (1 based).
   * @param  segFlag The property flag (BF_ITEM, BF_SET), which should
   *                 match the property type.
   *
   * @exception NSIException If the size of the array is greater than
   *                         128 bytes.
   */
   public BinderyPropertyDataSegment (
         byte[] seg,
         int    segNum,
         int    segFlag)    
      throws NSIException
   {
      if (seg.length > SEGMENT_LENGTH)
         throw (new NSIException ());

      // Copy the users segment data into our own and pad with 0's if needed.
      int i = 0;
      for (; i < seg.length; i++)
         segment[i] = seg[i];

      this.segNum  = segNum;
      this.segFlag = segFlag;
      
   }  // BinderyPropertyDataSegment ()

  /**
   * Construct a property data segment from a vector of integers.
   *
   * @param  vseg    A vector of integers to be put into the data
   *                 segment. If the vector contains more than 32
   *                 entries, the remaining entries are ignored.
   *                 If the vector contains fewer than 32 entries
   *                 the remainder of the segment is 0 padded.
   * @param  segNum  The number of this segment (1 based).
   * @param  segFlag The property flag (BF_ITEM, BF_SET),which should
   *                 match the property type.
   */
   public BinderyPropertyDataSegment (
         Vector vseg,
         int    segNum,
         int    segFlag)    
   {
      int i=0;
      int j=0;
      int k;
      Integer x;

      for ( ; i < SEGMENT_LENGTH && j < vseg.size (); j++)
      {
         k = ((Integer) vseg.elementAt (j)).intValue ();

         segment [i++] = (byte) (k);
         segment [i++] = (byte) (k >>  8);
         segment [i++] = (byte) (k >> 16);
         segment [i++] = (byte) (k >> 24);
      }

      this.segNum  = segNum;
      this.segFlag = segFlag;
   }  // BinderyPropertyDataSegment ()

   /**
    * Returns a byte array from which a new property data segment can
    * be created. This is useful when the user wants to manipulate the
    * byte array in order to create a new BinderyPropertyDataSegment.
    *
    * @return A byte array.
    */
   public byte[] getByteData()
   {
      return( (byte[])segment.clone() ); // preserve immutability by cloning
   }

   /**
    * Returns the number of this data segment.
    *
    * @return The number of this data segment.
    */
   public int getSegNum()
   {
      return( segNum );
   }

   /**
    * Returns the data segment property flag.
    *
    * @return The property's flag.
    */
   public int getSegFlag()
   {
      return( segFlag );
   }

   /**
    * Determines if this data segment contains Set data.
    *
    * @return A boolean set to TRUE if this data segment contains
    *         Set data, otherwise set to FALSE.
    */
   public boolean isSet()
   {
      return( BinderyUtil.BF_SET == (BinderyUtil.BF_SET & segFlag) );
   }

   /**
    * Returns a set of bindery IDs. If data is not a set of IDs,
    * a null vector is returned because there is no obvious exception
    * to throw.
    *
    * @return The bindery IDs as a Vector.
    */
   public Vector getSetData()
   {
      Vector v = new Vector();

      if( isSet() )
      {
         // Set data are sets of bindery IDs, arranged as chunks of 4 bytes
         // in the byte array, lo-hi format since NW3 runs on Intel cpus.

         int i=0,k=0;

         do
         {
            k =  0x000000FF & segment[i];
            k += 0x0000FF00 & (segment[i+1] << 8);
            k += 0x00FF0000 & (segment[i+2] << 16);
            k += segment[i+3] << 24;

            if( k != 0)
            {
               Integer id = new Integer(k);
               v.addElement( id );
            }
            i += 4;
         }  while(k != 0  &&  i < SEGMENT_LENGTH);

      }  // end isSet

      v.trimToSize();
      return( v );

   }  // end getSetData

   /**
    * Compares the set data passed in the Object parameter with the
    * current segment.
    *
    * <p>If the 2 segments are Set data, then equality is determined
    * by finding all the IDs in the parameter that match the current
    * segment, regardless of order or extra IDs. This is done so that
    * you can search for a single ID in properties that have many.
    * If the two segments are not Set data, every byte in the
    * segment must be identical.
    *
    * @param obj The Object with which to compare the current segment.
    *
    * @return A boolean set to TRUE if the two segments match, otherwise
    *         set to FALSE.
    */
   public boolean equals( Object obj )
   {
      if( !(obj instanceof BinderyPropertyDataSegment) )
         return( false );

      BinderyPropertyDataSegment bpds = (BinderyPropertyDataSegment)obj;

      if( this.isSet() )
      {
         // Every ID in the data that is passed in must be found in this
         //    object's data set.

         Vector vthis = this.getSetData();
         Vector vbpds = bpds.getSetData();

         if( null == vbpds )
            return( false );

         for( int i=0;  i<vbpds.size(); i++ )
         {
            boolean found = false;
            for( int j=0;  j<vthis.size(); j++ )
            {
               if( vbpds.elementAt(i).equals( vthis.elementAt(j) ) )
               {
                  found = true;
                  break;
               }
            }
            if( !found )
               return( false );
         }

      }  // set data
      else
      {
         byte[] bray = bpds.getByteData();

         for( int i=0; i<SEGMENT_LENGTH; i++ )
            if( this.segment[i] != bray[i] )
               return(false);

      }  // not set data

      return( true );

   }  // end equals


   /** 
    * Returns a string representation of this bindery segment.
    *
    * <p>Bindery segment data does not lend itself to becoming a string
    * because its format is ill-defined. The only known definition is IF
    * it is Set data, in which each 4 bytes are the ID of another bindery
    * object. If it is not Set data the segment can be anything. This
    * toString() method checks to see if anything falls into the pattern
    * of a c-string and displays it that way if possible. If not, a
    * string of bytes in hex format is output.
    *
    * @ruetn A string representation of the bindery segment.
    */
   public String toString ()
   {
      if( isSet() )
      {
         Integer i;
         StringBuffer str = new StringBuffer();
         Vector         v = getSetData();
         int   j=0, k = v.size();

         for( ; j<k; j++ )
         {
            i = (Integer)v.elementAt(j) ;
            str.append( " " + Integer.toHexString(i.intValue()) ) ;
         }

         return( str.toString() );
      }

      boolean allAscii  = true;

      char SPACE  = ' ';
      char[] hex = { '0','1','2','3','4','5','6','7',
                     '8','9','A','B','C','D','E','F'  };

      /* Even tho it is odd to convert to both ascii and binary, it's the
       * only way to do it.  Otherwise, might get the first byte of a binary
       * sequence in the ascii range.                                   ***/

      char[] chrs  = new char[SEGMENT_LENGTH*3];   // max size if converted
      char[] achrs = new char[SEGMENT_LENGTH];     // max size if ascii

      int aj=0, cj=0, z=0;

      for (int i=0; i < SEGMENT_LENGTH; i++)
      {
         /* If we're past the first byte, and all bytes have been in the
          *   Ascii range, assume that a 0 is the null-terminator.  Since
          *   an initial 0 might be the start of a binary stream, we
          *   won't be able to detect a 0-byte string, ie initial null. ***/
      
         if( i>0  &&  segment[i]==0x0  &&  allAscii )
            break;

         // Assume that there won't be mixed ascii-binary, so if the AA flag
         //   has been set false, don't convert #s in ascii range to letters.

         if( allAscii  &&  0x20 <= segment[i]  &&  segment[i] < 0x7f )
         {
            achrs[aj++] = (char)segment[i];
         }
         else
         {
            /* If we have binary data, but the first several bytes are in
             * the ascii range, must go back to beginning so as to stuff
             * the entire segment into the array as binary data.        ***/

            if( allAscii && i>0 )
            {
               for( int j=0;  j<i;  j++)
               {
                  chrs[cj++] = SPACE;
                  chrs[cj++] = hex[ (segment[j] >>> 4) & 0x0F ];
                  chrs[cj++] = hex[ (segment[j] >>> 0) & 0x0F ];
               }
            }
            chrs[cj++] = SPACE;
            chrs[cj++] = hex[ (segment[i] >>> 4) & 0x0F ];
            chrs[cj++] = hex[ (segment[i] >>> 0) & 0x0F ];
            allAscii  = false;
            if( segment[i] == 0x0 )
            {
               z++ ;
               if( z==8 )           // 8 0s in a row is probably
                  break;            // the end of meaningful data
            }
            else
               z=0;
         }
      }

      if( allAscii )
         return( new String(achrs, 0, aj) );
      else
         return( new String(chrs, 0, cj) );

   }  // end toString
}

