content: /*
   *  Licensed to the Apache Software Foundation (ASF) under one or more
   *  contributor license agreements.  See the NOTICE file distributed with
   *  this work for additional information regarding copyright ownership.
   *  The ASF licenses this file to You under the Apache License, Version 2.0
   *  (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.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   */
  /**
  * @author Vladimir N. Molotkov, Stepan M. Mishura
  */
  
  package android.org.apache.harmony.security.asn1;
  
  import android.org.apache.harmony.security.internal.nls.Messages;
  
  import java.io.IOException;
  import java.math.BigInteger;
  import java.util.Arrays;
  import java.util.Iterator;
  import java.util.Map;
  import java.util.TreeMap;
  
  
  /**
   * This abstract class represents ASN.1 Choice type.
   * 
   * To implement custom ASN.1 choice type an application class
   * must provide implementation for the following methods:
   *     getIndex()
   *     getObjectToEncode()
   * 
   * There are two ways to implement custom ASN.1 choice type:
   * with application class that represents ASN.1 custom choice type or without. 
   * The key point is how a value of choice type is stored by application classes.
   * 
   * For example, let's consider the following ASN.1 notations
   * (see http://www.ietf.org/rfc/rfc3280.txt)
   * 
   * Time ::= CHOICE {
   *       utcTime        UTCTime,
   *       generalTime    GeneralizedTime
   * }
   * 
   * Validity ::= SEQUENCE {
   *       notBefore      Time,
   *       notAfter       Time 
   *  }
   * 
   * 1)First approach:
   * No application class to represent ASN.1 Time notation
   * 
   * The Time notation is a choice of different time formats: UTC and Generalized.
   * Both of them are mapped to java.util.Date object, so an application
   * class that represents ASN.1 Validity notation may keep values
   * as Date objects.
   * 
   * So a custom ASN.1 Time choice type should map its notation to Date object.
   * 
   * class Time {
   * 
   *     // custom ASN.1 choice class: maps Time to is notation
   *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
   *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
   *
   *         public int getIndex(java.lang.Object object) {
   *             return 0; // always encode as ASN1GeneralizedTime
   *         }
   *
   *         public Object getObjectToEncode(Object object) {
   *         
   *             // A value to be encoded value is a Date object
   *             // pass it to custom time class  
   *             return object;
   *         }
   *     };
   * }
   * 
   * class Validity {
   * 
   *     private Date notBefore;    // choice as Date 
   *     private Date notAfter;     // choice as Date
   * 
   *     ... // constructors and other methods go here
   * 
   *     // custom ASN.1 sequence class: maps Validity class to is notation
   *     public static final ASN1Sequence ASN1
   *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
   * 
   *         protected Object getObject(Object[] values) {
   * 
   *             // ASN.1 Time choice passed Data object - use it 
   *             return new Validity((Date) values[0], (Date) values[1]);
   *         }
   *
   *         protected void getValues(Object object, Object[] values) {
   *
   *             Validity validity = (Validity) object;
   *
   *             // pass Date objects to ASN.1 Time choice
   *             values[0] = validity.notBefore;
   *             values[1] = validity.notAfter;
   *         }
   *     }
   * }
   * 
   * 2)Second approach:
   * There is an application class to represent ASN.1 Time notation
   * 
   * If it is a matter what time format should be used to decode/encode
   * Date objects a class to represent ASN.1 Time notation must be created.
   * 
   * For example,
   *  
   * class Time {
   * 
   *     private Date utcTime; 
   *     private Date gTime;
   * 
   *     ... // constructors and other methods go here
   *  
   *     // custom ASN.1 choice class: maps Time to is notation
   *     public static final ASN1Choice asn1 = new ASN1Choice(new ASN1Type[] {
   *         ASN1GeneralizedTime.asn1, ASN1UTCTime.asn1 }) {
   *
   *         public Object getDecodedObject(BerInputStream in) {
   *
   *             // create Time object to pass as decoded value
   *             Time time = new Time();
   * 
   *             if (in.choiceIndex==0) {
   *                 // we decoded GeneralizedTime
   *                 // store decoded Date value in corresponding field
   *                 time.gTime = in.content;
   *                 // return it
   *                 return time;
   *             } else {
   *                 // we decoded UTCTime
   *                 // store decoded Date value in corresponding field
   *                 time.utcTime = in.content;
   *                 // return it
   *                 return time;
   *             }
   *         }
   *
   *         public int getIndex(java.lang.Object object) {
   *             Time time = (Time)object;
   *             if(time.utcTime!=null){
   *                 // encode Date as UTCTime
   *                 return 1;
   *             } else {
   *                 // otherwise encode Date as GeneralizedTime
   *                 return 0;
   *             } 
   *         }
   *
   *         public Object getObjectToEncode(Object object) {
   *             Time time = (Time)object;
   *             if(time.utcTime!=null){
   *                 // encode Date as UTCTime
   *                 return 1;
   *             } else {
   *                 // otherwise encode Date as GeneralizedTime
   *                 return 0;
   *             } 
   *         }
   *     };
   * }
   *
   * So now Validity class must keep all values in Time object
   * and its custom ASN.1 sequence class must handle this class of objects
   *
   * class Validity {
   * 
   *     private Time notBefore;    // now it is a Time!!! 
   *     private Time notAfter;     // now it is a Time!!!
   * 
   *     ... // constructors and other methods go here
   * 
   *     // custom ASN.1 sequence class: maps Validity class to is notation
   *     public static final ASN1Sequence ASN1
   *         = new ASN1Sequence(new ASN1Type[] {Time.asn1, Time.asn1 }) {
   * 
   *         protected Object getObject(Object[] values) {
   * 
   *             // We've gotten Time objects here !!!
   *             return new Validity((Time) values[0], (Time) values[1]);
   *         }
   *
   *         protected void getValues(Object object, Object[] values) {
   *
   *             Validity validity = (Validity) object;
   *
   *             // pass Time objects to ASN.1 Time choice
   *             values[0] = validity.notBefore;
   *             values[1] = validity.notAfter;
   *         }
   *     }
   * }
   * 
   * @see http://asn1.elibel.tm.fr/en/standards/index.htm
   */
  
  public abstract class ASN1Choice extends ASN1Type {
  
      public final ASN1Type[] type;
  
      // identifiers table: [2][number of distinct identifiers]
      // identifiers[0]: stores identifiers (includes nested choices)
      // identifiers[1]: stores identifiers' indexes in array of types
      private final int[][] identifiers;
  
      /**
       * Constructs ASN.1 choice type.
       * 
       * @param type -
       *            an array of one or more ASN.1 type alternatives.
       * @throws IllegalArgumentException -
       *             type parameter is invalid
       */
      public ASN1Choice(ASN1Type[] type) {
          super(TAG_CHOICE); // has not tag number
  
          if (type.length == 0) {
              throw new IllegalArgumentException(Messages.getString("security.10E", //$NON-NLS-1$
                      getClass().getName()));
          }
  
          // create map of all identifiers
          TreeMap map = new TreeMap();
          for (int index = 0; index < type.length; index++) {
  
              ASN1Type t = type[index];
  
              if (t instanceof ASN1Any) {
                  // ASN.1 ANY is not allowed,
                  // even it is a single component (not good for nested choices)
                  throw new IllegalArgumentException(Messages.getString("security.10F", //$NON-NLS-1$
                          getClass().getName())); // FIXME name
              } else if (t instanceof ASN1Choice) {
  
                  // add all choice's identifiers
                  int[][] choiceToAdd = ((ASN1Choice) t).identifiers;
                  for (int j = 0; j < choiceToAdd[0].length; j++) {
                      addIdentifier(map, choiceToAdd[0][j], index);
                  }
                  continue;
              }
  
              // add primitive identifier
              if (t.checkTag(t.id)) {
                  addIdentifier(map, t.id, index);
              }
  
              // add constructed identifier
              if (t.checkTag(t.constrId)) {
                  addIdentifier(map, t.constrId, index);
              }
          }
  
          // fill identifiers array
          int size = map.size();
          identifiers = new int[2][size];
          Iterator it = map.entrySet().iterator();
  
          for (int i = 0; i < size; i++) {
          	Map.Entry entry = (Map.Entry) it.next();
              BigInteger identifier = (BigInteger) entry.getKey();
  
              identifiers[0][i] = identifier.intValue();
              identifiers[1][i] = ((BigInteger) entry.getValue()).intValue();
          }
  
          this.type = type;
      }
  
      private void addIdentifier(TreeMap map, int identifier, int index){
          if (map.put(BigInteger.valueOf(identifier), BigInteger.valueOf(index)) != null) {
              throw new IllegalArgumentException(Messages.getString("security.10F", //$NON-NLS-1$
                      getClass().getName())); // FIXME name
          }
      }
      
      //
      //
      // DECODE
      //
      //
  
      /**
       * Tests whether one of choice alternatives has the same identifier or not.
       * 
       * @param identifier -
       *            ASN.1 identifier to be verified
       * @return - true if one of choice alternatives has the same identifier,
       *         otherwise false;
       */
      public final boolean checkTag(int identifier) {
          return Arrays.binarySearch(identifiers[0], identifier) >= 0;
      }
  
      public Object decode(BerInputStream in) throws IOException {
  
          int index = Arrays.binarySearch(identifiers[0], in.tag);
          if (index < 0) {
              throw new ASN1Exception(Messages.getString("security.110", //$NON-NLS-1$
                      getClass().getName()));// FIXME message
          }
  
          index = identifiers[1][index];
  
          in.content = type[index].decode(in);
  
          // set index for getDecodedObject method
          in.choiceIndex = index;
  
          if (in.isVerify) {
              return null;
          }
          return getDecodedObject(in);
      }
      
      //
      //
      // ENCODE
      //
      //
  
      public void encodeASN(BerOutputStream out) {
          encodeContent(out);
      }
  
      public final void encodeContent(BerOutputStream out) {
          out.encodeChoice(this);
      }
  
      /**
       * TODO Put method description here
       *
       * @param object - an object to be encoded
       * @return
       */
      public abstract int getIndex(Object object);
  
      public abstract Object getObjectToEncode(Object object);
  
      public final void setEncodingContent(BerOutputStream out) {
          out.getChoiceLength(this);
      }
  }