FileDocCategorySizeDatePackage
ASN1Collection.javaAPI DocAndroid 1.5 API8017Wed May 06 22:41:06 BST 2009org.bouncycastle.asn1

ASN1Collection.java

package org.bouncycastle.asn1;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.ConcurrentModificationException;

// BEGIN android-note
/*
 * This is a new class that was synthesized from ASN1Sequence and
 * ASN1Set, but with extra smarts about efficiently storing its
 * elements.
 */
// END android-note

/**
 * Base class for collection-like <code>DERObject</code>s. Instances
 * of this class will keep up to four elements directly, resorting to
 * an external collection only if more elements than that need to be
 * stored.
 */
public abstract class ASN1Collection
    extends DERObject
{
    /** >= 0; size of the collection */
    private int size;

    /** null-ok; element #0 */
    private DEREncodable obj0;

    /** null-ok; element #1 */
    private DEREncodable obj1;

    /** null-ok; element #2 */
    private DEREncodable obj2;

    /** null-ok; element #3 */
    private DEREncodable obj3;

    /** null-ok; elements #4 and higher */
    private DEREncodable[] rest;

    /**
     * Returns the object at the postion indicated by index.
     *
     * @param index the index (starting at zero) of the object
     * @return the object at the postion indicated by index
     */
    public final DEREncodable getObjectAt(int index) {
        if ((index < 0) || (index >= size)) {
            throw new IndexOutOfBoundsException(Integer.toString(index));
        }
                    
        switch (index) {
            case 0: return obj0;
            case 1: return obj1;
            case 2: return obj2;
            case 3: return obj3;
            default: return rest[index - 4];
        }
    }

    /**
     * Returns the number of objects in this instance.
     *
     * @return the number of objects in this instance
     */
    public final int size() {
        return size;
    }

    /** {@inheritDoc} */
    public final int hashCode() {
        Enumeration e = this.getObjects();
        int hashCode = 0;

        while (e.hasMoreElements()) {
            Object o = e.nextElement();
            
            if (o != null) {
                hashCode ^= o.hashCode();
            }
        }

        return hashCode;
    }

    /**
     * Adds a new element to this instance.
     * 
     * @param obj non-null; the instance to add
     */
    protected void addObject(DEREncodable obj) {
        if (obj == null) {
            throw new NullPointerException("obj == null");
        }

        int sz = size;
        
        switch (sz) {
            case 0: obj0 = obj; break;
            case 1: obj1 = obj; break;
            case 2: obj2 = obj; break;
            case 3: obj3 = obj; break;
            case 4: {
                // Initial allocation of rest.
                rest = new DEREncodable[5];
                rest[0] = obj;
                break;
            }
            default: {
                int index = sz - 4;
                if (index >= rest.length) {
                    // Grow rest.
                    DEREncodable[] newRest = new DEREncodable[index * 2 + 10];
                    System.arraycopy(rest, 0, newRest, 0, rest.length);
                    rest = newRest;
                }
                rest[index] = obj;
                break;
            }
        }

        size++;
    }

    /**
     * Sets the element at a given index (used by {@link #sort}).
     * 
     * @param obj non-null; the object to set
     * @param index >= 0; the index
     */
    private void setObjectAt(DEREncodable obj, int index) {
        switch (index) {
            case 0: obj0 = obj; break;
            case 1: obj1 = obj; break;
            case 2: obj2 = obj; break;
            case 3: obj3 = obj; break;
            default: {
                rest[index - 4] = obj;
                break;
            }
        }
    }

    /**
     * Encodes this instance to the given stream.
     * 
     * @param out non-null; stream to encode to
     */
    /*package*/ abstract void encode(DEROutputStream out) throws IOException;

    /**
     * Gets an enumeration of all the objects in this collection.
     * 
     * @return non-null; the enumeration
     */
    public final Enumeration getObjects() {
        return new ASN1CollectionEnumeration();
    }

    /**
     * Associated enumeration class.
     */
    private class ASN1CollectionEnumeration implements Enumeration {
        /** original size; used for modification detection */
        private final int origSize = size;

        /** >= 0; current cursor */
        private int at = 0;

        /** {@inheritDoc} */
        public boolean hasMoreElements() {
            if (size != origSize) {
                throw new ConcurrentModificationException();
            }

            return at < origSize;
        }

        /** {@inheritDoc} */
        public Object nextElement() {
            if (size != origSize) {
                throw new ConcurrentModificationException();
            }

            switch (at++) {
                case 0: return obj0;
                case 1: return obj1;
                case 2: return obj2;
                case 3: return obj3;
                default: return rest[at - 5];
            }
        }
    }

    /**
     * Sorts the elements in this instance.
     */
    protected void sort() {
        if (size <= 1) {
            return;
        }

        boolean swapped = true;

        // TODO: This is bubble sort. Probably not the best choice.
        while (swapped) {
            int index = 0;
            byte[] a = getEncoded(getObjectAt(0));
                
            swapped = false;
                
            while (index != size - 1) {
                int nextIndex = index + 1;
                byte[] b = getEncoded(getObjectAt(nextIndex));

                if (lessThanOrEqual(a, b)) {
                    a = b;
                } else {
                    DEREncodable o = getObjectAt(index);
                    
                    setObjectAt(getObjectAt(nextIndex), index);
                    setObjectAt(o, nextIndex);

                    swapped = true;
                }

                index++;
            }
        }
    }
    
    /**
     * Returns true if a <= b (arrays are assumed padded with zeros).
     */
    private static boolean lessThanOrEqual(byte[] a, byte[] b) {
        if (a.length <= b.length) {
            for (int i = 0; i != a.length; i++) {
                int l = a[i] & 0xff;
                int r = b[i] & 0xff;
                 
                if (r > l) {
                    return true;
                } else if (l > r) {
                    return false;
                }
            }

            return true;
        } else {
            for (int i = 0; i != b.length; i++) {
                 int l = a[i] & 0xff;
                 int r = b[i] & 0xff;
                 
                 if (r > l) {
                     return true;
                 } else if (l > r) {
                     return false;
                 }
             }

             return false;
         }
    }

    /**
     * Gets the encoded form of an object.
     * 
     * @param obj non-null; object to encode
     * @return non-null; the encoded form
     */
    private static byte[] getEncoded(DEREncodable 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 collection");
        }

        return bOut.toByteArray();
    }

    /** {@inheritDoc} */
    public final String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append('[');
        for (int i = 0; i < size; i++) {
            if (i != 0) sb.append(", ");
            sb.append(getObjectAt(i));
        }
        sb.append(']');
        return sb.toString();
    }
}