content: package local.org.bouncycastle.asn1;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.util.Enumeration;
  import java.util.Vector;
  
  abstract public class ASN1Set
      extends ASN1Primitive
  {
      private Vector set = new Vector();
      private boolean isSorted = false;
  
      /**
       * return an ASN1Set from the given object.
       *
       * @param obj the object we want converted.
       * @exception IllegalArgumentException if the object cannot be converted.
       */
      public static ASN1Set getInstance(
          Object  obj)
      {
          if (obj == null || obj instanceof ASN1Set)
          {
              return (ASN1Set)obj;
          }
          else if (obj instanceof ASN1SetParser)
          {
              return ASN1Set.getInstance(((ASN1SetParser)obj).toASN1Primitive());
          }
          else if (obj instanceof byte[])
          {
              try
              {
                  return ASN1Set.getInstance(ASN1Primitive.fromByteArray((byte[])obj));
              }
              catch (IOException e)
              {
                  throw new IllegalArgumentException("failed to construct set from byte[]: " + e.getMessage());
              }
          }
          else if (obj instanceof ASN1Encodable)
          {
              ASN1Primitive primitive = ((ASN1Encodable)obj).toASN1Primitive();
  
              if (primitive instanceof ASN1Set)
              {
                  return (ASN1Set)primitive;
              }
          }
  
          throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
      }
  
      /**
       * Return an ASN1 set from a tagged object. There is a special
       * case here, if an object appears to have been explicitly tagged on 
       * reading but we were expecting it to be implicitly tagged in the 
       * normal course of events it indicates that we lost the surrounding
       * set - so we need to add it back (this will happen if the tagged
       * object is a sequence that contains other sequences). If you are
       * dealing with implicitly tagged sets you really <b>should</b>
       * be using this method.
       *
       * @param obj the tagged object.
       * @param explicit true if the object is meant to be explicitly tagged
       *          false otherwise.
       * @exception IllegalArgumentException if the tagged object cannot
       *          be converted.
       */
      public static ASN1Set getInstance(
          ASN1TaggedObject    obj,
          boolean             explicit)
      {
          if (explicit)
          {
              if (!obj.isExplicit())
              {
                  throw new IllegalArgumentException("object implicit - explicit expected.");
              }
  
              return (ASN1Set)obj.getObject();
          }
          else
          {
              //
              // constructed object which appears to be explicitly tagged
              // and it's really implicit means we have to add the
              // surrounding set.
              //
              if (obj.isExplicit())
              {
                  if (obj instanceof BERTaggedObject)
                  {
                      return new BERSet(obj.getObject());
                  }
                  else
                  {
                      return new DLSet(obj.getObject());
                  }
              }
              else
              {
                  if (obj.getObject() instanceof ASN1Set)
                  {
                      return (ASN1Set)obj.getObject();
                  }
  
                  //
                  // in this case the parser returns a sequence, convert it
                  // into a set.
                  //
                  if (obj.getObject() instanceof ASN1Sequence)
                  {
                      ASN1Sequence s = (ASN1Sequence)obj.getObject();
  
                      if (obj instanceof BERTaggedObject)
                      {
                          return new BERSet(s.toArray());
                      }
                      else
                      {
                          return new DLSet(s.toArray());
                      }
                  }
              }
          }
  
          throw new IllegalArgumentException("unknown object in getInstance: " + obj.getClass().getName());
      }
  
      protected ASN1Set()
      {
      }
  
      /**
       * create a sequence containing one object
       */
      protected ASN1Set(
          ASN1Encodable obj)
      {
          set.addElement(obj);
      }
  
      /**
       * create a sequence containing a vector of objects.
       */
      protected ASN1Set(
          ASN1EncodableVector v,
          boolean                  doSort)
      {
          for (int i = 0; i != v.size(); i++)
          {
              set.addElement(v.get(i));
          }
  
          if (doSort)
          {
              this.sort();
          }
      }
  
      /**
       * create a sequence containing a vector of objects.
       */
      protected ASN1Set(
          ASN1Encodable[]   array,
          boolean doSort)
      {
          for (int i = 0; i != array.length; i++)
          {
              set.addElement(array[i]);
          }
  
          if (doSort)
          {
              this.sort();
          }
      }
  
      public Enumeration getObjects()
      {
          return set.elements();
      }
  
      /**
       * return the object at the set position indicated by index.
       *
       * @param index the set number (starting at zero) of the object
       * @return the object at the set position indicated by index.
       */
      public ASN1Encodable getObjectAt(
          int index)
      {
          return (ASN1Encodable)set.elementAt(index);
      }
  
      /**
       * return the number of objects in this set.
       *
       * @return the number of objects in this set.
       */
      public int size()
      {
          return set.size();
      }
  
      public ASN1Encodable[] toArray()
      {
          ASN1Encodable[] values = new ASN1Encodable[this.size()];
  
          for (int i = 0; i != this.size(); i++)
          {
              values[i] = this.getObjectAt(i);
          }
  
          return values;
      }
  
      public ASN1SetParser parser()
      {
          final ASN1Set outer = this;
  
          return new ASN1SetParser()
          {
              private final int max = size();
  
              private int index;
  
              public ASN1Encodable readObject() throws IOException
              {
                  if (index == max)
                  {
                      return null;
                  }
  
                  ASN1Encodable obj = getObjectAt(index++);
                  if (obj instanceof ASN1Sequence)
                  {
                      return ((ASN1Sequence)obj).parser();
                  }
                  if (obj instanceof ASN1Set)
                  {
                      return ((ASN1Set)obj).parser();
                  }
  
                  return obj;
              }
  
              public ASN1Primitive getLoadedObject()
              {
                  return outer;
              }
  
              public ASN1Primitive toASN1Primitive()
              {
                  return outer;
              }
          };
      }
  
      public int hashCode()
      {
          Enumeration             e = this.getObjects();
          int                     hashCode = size();
  
          while (e.hasMoreElements())
          {
              Object o = getNext(e);
              hashCode *= 17;
  
              hashCode ^= o.hashCode();
          }
  
          return hashCode;
      }
  
      ASN1Primitive toDERObject()
      {
          if (isSorted)
          {
              ASN1Set derSet = new DERSet();
  
              derSet.set = this.set;
  
              return derSet;
          }
          else
          {
              Vector v = new Vector();
  
              for (int i = 0; i != set.size(); i++)
              {
                  v.addElement(set.elementAt(i));
              }
  
              ASN1Set derSet = new DERSet();
  
              derSet.set = v;
  
              derSet.sort();
  
              return derSet;
          }
      }
  
      ASN1Primitive toDLObject()
      {
          ASN1Set derSet = new DLSet();
  
          derSet.set = this.set;
  
          return derSet;
      }
  
      boolean asn1Equals(
          ASN1Primitive o)
      {
          if (!(o instanceof ASN1Set))
          {
              return false;
          }
  
          ASN1Set   other = (ASN1Set)o;
  
          if (this.size() != other.size())
          {
              return false;
          }
  
          Enumeration s1 = this.getObjects();
          Enumeration s2 = other.getObjects();
  
          while (s1.hasMoreElements())
          {
              ASN1Encodable obj1 = getNext(s1);
              ASN1Encodable obj2 = getNext(s2);
  
              ASN1Primitive o1 = obj1.toASN1Primitive();
              ASN1Primitive o2 = obj2.toASN1Primitive();
  
              if (o1 == o2 || o1.equals(o2))
              {
                  continue;
              }
  
              return false;
          }
  
          return true;
      }
  
      private ASN1Encodable getNext(Enumeration e)
      {
          ASN1Encodable encObj = (ASN1Encodable)e.nextElement();
  
          // unfortunately null was allowed as a substitute for DER null
          if (encObj == null)
          {
              return DERNull.INSTANCE;
          }
  
          return encObj;
      }
  
      /**
       * return true if a <= b (arrays are assumed padded with zeros).
       */
      private boolean lessThanOrEqual(
           byte[] a,
           byte[] b)
      {
          int len = Math.min(a.length, b.length);
          for (int i = 0; i != len; ++i)
          {
              if (a[i] != b[i])
              {
                  return (a[i] & 0xff) < (b[i] & 0xff);
              }
          }
          return len == a.length;
      }
  
      private byte[] getEncoded(
          ASN1Encodable obj)
      {
          ByteArrayOutputStream   bOut = new ByteArrayOutputStream();
          ASN1OutputStream        aOut = new ASN1OutputStream(bOut);
  
          try
          {
              aOut.writeObject(obj);
          }
          catch (IOException e)
          {
              throw new IllegalArgumentException("cannot encode object added to SET");
          }
  
          return bOut.toByteArray();
      }
  
      protected void sort()
      {
          if (!isSorted)
          {
              isSorted = true;
              if (set.size() > 1)
              {
                  boolean    swapped = true;
                  int        lastSwap = set.size() - 1;
  
                  while (swapped)
                  {
                      int    index = 0;
                      int    swapIndex = 0;
                      byte[] a = getEncoded((ASN1Encodable)set.elementAt(0));
  
                      swapped = false;
  
                      while (index != lastSwap)
                      {
                          byte[] b = getEncoded((ASN1Encodable)set.elementAt(index + 1));
  
                          if (lessThanOrEqual(a, b))
                          {
                              a = b;
                          }
                          else
                          {
                              Object  o = set.elementAt(index);
  
                              set.setElementAt(set.elementAt(index + 1), index);
                              set.setElementAt(o, index + 1);
  
                              swapped = true;
                              swapIndex = index;
                          }
  
                          index++;
                      }
  
                      lastSwap = swapIndex;
                  }
              }
          }
      }
  
      boolean isConstructed()
      {
          return true;
      }
  
      abstract void encode(ASN1OutputStream out)
              throws IOException;
  
      public String toString() 
      {
          return set.toString();
      }
  }
  