/**
  * Generates an NSDictionary representing primary key values, both simple and compound. If values
  * are encrypted we try to create the correct attribute value type. Supported types are: strings,
  * numbers, timestamps and custom attributes with a factory method using a string argument.
  *
  * @param value the primary key value, either a single value or a collection
  * @param entity the entity used to gather primary key information
  * @param isEncrypted yes/no
  * @return a dictionary with primary key values
  */
 private static NSDictionary processPrimaryKeyValue(
     String value, EOEntity entity, boolean isEncrypted) {
   NSArray pkAttributeNames = entity.primaryKeyAttributeNames();
   try {
     pkAttributeNames =
         pkAttributeNames.sortedArrayUsingComparator(NSComparator.AscendingStringComparator);
   } catch (NSComparator.ComparisonException ex) {
     log.error("Unable to sort attribute names: " + ex);
     throw new NSForwardException(ex);
   }
   NSArray values =
       isEncrypted
           ? NSArray.componentsSeparatedByString(
               ERXCrypto.blowfishDecode(value).trim(), AttributeValueSeparator)
           : NSArray.componentsSeparatedByString(value, AttributeValueSeparator);
   int attrCount = pkAttributeNames.count();
   NSMutableDictionary result = new NSMutableDictionary(attrCount);
   for (int i = 0; i < attrCount; i++) {
     String currentAttributeName = (String) pkAttributeNames.objectAtIndex(i);
     EOAttribute currentAttribute = entity.attributeNamed(currentAttributeName);
     Object currentValue = values.objectAtIndex(i);
     switch (currentAttribute.adaptorValueType()) {
       case 3:
         NSTimestampFormatter tsf = new NSTimestampFormatter();
         try {
           currentValue = tsf.parseObject((String) currentValue);
         } catch (java.text.ParseException ex) {
           log.error("Error while trying to parse: " + currentValue);
           throw new NSForwardException(ex);
         }
       case 1:
         if (currentAttribute.valueFactoryMethodName() != null) {
           currentValue = currentAttribute.newValueForString((String) currentValue);
         }
       case 0:
         currentValue = new java.math.BigDecimal((String) currentValue);
     }
     result.setObjectForKey(currentValue, currentAttributeName);
   }
   return result;
 }
  // ENHANCEME: Could also place a sha hash of the blowfish key in the form values so we can know if
  // we are using
  //		  the correct key for decryption.
  public static String encodeEnterpriseObjectsPrimaryKeyForUrl(
      NSArray eos, String separator, boolean encrypt) {
    // Array of objects to be encoded
    NSMutableArray encoded = new NSMutableArray();

    // If the separator is not specified, default to the one given at class init
    if (separator == null) separator = entityNameSeparator();

    // Add the separator if needed
    if (isSpecifySeparatorInURL()) encoded.addObject("sep=" + separator);

    int c = 1;
    // Iterate through the objects
    for (Enumeration e = eos.objectEnumerator(); e.hasMoreElements(); ) {
      EOEnterpriseObject eo = (EOEnterpriseObject) e.nextElement();

      // Get the primary key of the object
      NSArray pkValues = ERXEOControlUtilities.primaryKeyArrayForObject(eo);
      if (pkValues == null && eo instanceof ERXGeneratesPrimaryKeyInterface) {
        NSDictionary pkDict = ((ERXGeneratesPrimaryKeyInterface) eo).primaryKeyDictionary(false);
        if (pkDict != null) pkValues = pkDict.allValues();
      }
      if (pkValues == null) throw new RuntimeException("Primary key is null for object: " + eo);
      String pk = pkValues.componentsJoinedByString(AttributeValueSeparator);

      // Get the EncodedEntityName of the object
      String encodedEntityName = entityNameEncode(eo);

      // Add the result to the list of encoded objects
      encoded.addObject(
          encodedEntityName
              + separator
              + (encrypt ? "E" : "")
              + c++
              + "="
              + (encrypt ? ERXCrypto.blowfishEncode(pk) : pk));
    }

    // Return the result as an url-encoded string
    return encoded.componentsJoinedByString("&");
  }