// // NSDictionaryUtilities.java // Project vwdBussinessLogicJava // // Created by ak on Wed Jun 06 2001 // package er.extensions.foundation; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.util.Enumeration; import com.webobjects.appserver.WOMessage; import com.webobjects.eocontrol.EOKeyValueCoding; import com.webobjects.foundation.NSArray; import com.webobjects.foundation.NSBundle; import com.webobjects.foundation.NSComparator; import com.webobjects.foundation.NSDictionary; import com.webobjects.foundation.NSForwardException; import com.webobjects.foundation.NSKeyValueCoding; import com.webobjects.foundation.NSKeyValueCodingAdditions; import com.webobjects.foundation.NSMutableDictionary; import com.webobjects.foundation.NSPropertyListSerialization; /** * Collection of {@link com.webobjects.foundation.NSDictionary NSDictionary} utilities. */ public class ERXDictionaryUtilities { /** * Creates an immutable dictionary containing all of the keys and * objects from two dictionaries. * @param dict1 the first dictionary * @param dict2 the second dictionary * @return immutbale dictionary containing the union of the two dictionaries. */ public static <K, V> NSDictionary<K, V> dictionaryWithDictionaryAndDictionary(NSDictionary<? extends K, ? extends V> dict1, NSDictionary<? extends K, ? extends V> dict2) { if(dict1 == null || dict1.allKeys().count() == 0) return (NSDictionary<K, V>) dict2; if(dict2 == null || dict2.allKeys().count() == 0) return (NSDictionary<K, V>) dict1; NSMutableDictionary<K, V> result = new NSMutableDictionary<K, V>(dict2); result.addEntriesFromDictionary(dict1); return new NSDictionary<K, V>(result); } /** * Creates an NSDictionary from a resource associated with a given bundle * that is in property list format. * @param name name of the file or resource. * @param bundle NSBundle to which the resource belongs. * @return NSDictionary de-serialized from the property list. */ @SuppressWarnings("unchecked") public static NSDictionary dictionaryFromPropertyList(String name, NSBundle bundle) { String string = ERXStringUtilities.stringFromResource(name, "plist", bundle); return (NSDictionary<?,?>)NSPropertyListSerialization.propertyListFromString(string); } /** * Creates a dictionary from a list of alternating objects and keys * starting with an object. * @param objectsAndKeys alternating list of objects and keys * @return NSDictionary containing all of the object-key pairs. */ @SuppressWarnings("unchecked") public static NSDictionary dictionaryWithObjectsAndKeys(Object[] objectsAndKeys) { NSMutableDictionary<Object, Object> result = new NSMutableDictionary<Object, Object>(); Object object; String key; int length = objectsAndKeys.length; for(int i = 0; i < length; i+=2) { object = objectsAndKeys[i]; if(object == null) { break; } key = (String)objectsAndKeys[i+1]; result.setObjectForKey(object,key); } return new NSDictionary<Object, Object>(result); } /** * Removes an array of keys from a dictionary and * returns the result. * @param d dictionary to be pruned * @param a array of keys to be pruned * @return pruned dictionary */ public static <K, V> NSDictionary<K, V> dictionaryByRemovingFromDictionaryKeysInArray(NSDictionary<K, V> d, NSArray<K> a) { NSMutableDictionary<K, V> result=new NSMutableDictionary<K, V>(); if (d != null && a != null) { for (Enumeration<K> e = d.allKeys().objectEnumerator(); e.hasMoreElements();) { K key = e.nextElement(); if (!a.containsObject(key)) { result.setObjectForKey(d.objectForKey(key), key); } } } return result.immutableClone(); } /** * Creates a new dictionary with only the keys and objects in the array. The result is the objects for the * intersection of keys in the dictionary and the array. This is the opposite of dictionaryByRemovingFromDictionaryKeysInArray. * * @param d dictionary to be pruned * @param a array of keys to be included * @return pruned dictionary */ public static <K, V> NSDictionary<K, V> dictionaryByRemovingKeysNotInArray(NSDictionary<K, V> d, NSArray<K> a) { NSMutableDictionary<K, V> result=new NSMutableDictionary<K, V>(); if (d != null && a != null) { for (Enumeration<K> e = a.objectEnumerator(); e.hasMoreElements();) { K key = e.nextElement(); V value = d.objectForKey(key); if (value != null) { result.setObjectForKey(value, key); } } } return result.immutableClone(); } /** * */ public static <K, V> NSDictionary<K, V> removeNullValues(NSDictionary<K, V> dict) { NSMutableDictionary<K, V> d = new NSMutableDictionary<K, V>(); for (Enumeration<K> e = dict.keyEnumerator(); e.hasMoreElements();) { K key = e.nextElement(); V o = dict.objectForKey(key); if (!(o instanceof NSKeyValueCoding.Null)) { d.setObjectForKey(o, key); } } return d; } /** * Creates a dictionary from an objects and an array of key paths * @param object object to pull the values from * @param keys array of keys * @return NSDictionary containing all of the object-key pairs. */ public static NSDictionary<String, Object> dictionaryFromObjectWithKeys(Object object, NSArray<String> keys) { NSMutableDictionary<String, Object> result = new NSMutableDictionary<>(); if(object != null && keys != null) { for (Enumeration<String> e = keys.objectEnumerator(); e.hasMoreElements();) { String key = e.nextElement(); Object value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, key); if(value != null) { result.setObjectForKey(value, key); } } } return result.immutableClone(); } /** * Creates a dictionary from an object and a list of key paths * @param object object to pull the values from * @param keys list of keys * @return Returns a {@code NSDictionary} containing all of the object-key pairs. */ public static NSDictionary<String, Object> dictionaryFromObjectWithKeys(Object object, String... keys) { return ERXDictionaryUtilities.dictionaryFromObjectWithKeys(object, new NSArray<>(keys)); } // if you're keys are not all strings, this method will throw. public static NSArray<String> stringKeysSortedAscending(final NSDictionary<String, ?> d) { NSArray<String> result = null; if ( d != null && d.count() > 0 ) { final NSArray<String> keys = d.allKeys(); result = ERXArrayUtilities.sortedArrayUsingComparator(keys, NSComparator.AscendingStringComparator); } return result != null ? result : NSArray.EmptyArray; } /** * @param d dictionary to sort keys from * @return keys from d sorted by ascending value they are mapped to */ public static <T> NSArray<T> keysSortedByValueAscending(final NSDictionary<T, ?> d) { NSArray<T> result = null; if ( d != null && d.count() > 0 ) { final NSArray<T> keys = d.allKeys(); result = ERXArrayUtilities.sortedArrayUsingComparator(keys, new NSDictionaryKeyValueComparator(d)); } return result != null ? result : NSArray.EmptyArray; } /** * Removes entries from both dictionaries that match, leaving you with two dictionaries containing * only values that did NOT match. Note that this comparison considers null == EO/NSKeyValueCoding.NullValue. * * @param dict1 the first dictionary * @param dict2 the second dictionary */ public static <K, V> void removeMatchingEntries(NSMutableDictionary<? extends K, ? extends V> dict1, NSMutableDictionary<? extends K, ? extends V> dict2) { _removeMatchingEntries(dict1, dict2, true); } public static <K, V> void _removeMatchingEntries(NSMutableDictionary<? extends K, ? extends V> snapshot1, NSMutableDictionary<? extends K, ? extends V> snapshot2, boolean removeInverse) { Enumeration<? extends K> keys1Enum = snapshot1.allKeys().immutableClone().objectEnumerator(); while (keys1Enum.hasMoreElements()) { String key = (String)keys1Enum.nextElement(); Object value1 = snapshot1.objectForKey(key); Object value2 = snapshot2.objectForKey(key); boolean value1IsNull = (value1 == null || value1 == EOKeyValueCoding.NullValue || value1 == NSKeyValueCoding.NullValue); boolean value2IsNull = (value2 == null || value2 == EOKeyValueCoding.NullValue || value2 == NSKeyValueCoding.NullValue); if (value1IsNull && value2IsNull) { snapshot1.removeObjectForKey(key); snapshot2.removeObjectForKey(key); } else if (value1 != null && value1.equals(value2)) { snapshot1.removeObjectForKey(key); snapshot2.removeObjectForKey(key); } } // flip around the comparison and remove again if (removeInverse) { _removeMatchingEntries(snapshot2, snapshot1, false); } } /** * Sets the object for each of the keys in the array on a mutable dictionary. * * @param dictionary dictionary to mutate. a null dictionary is a no-op. * @param object object to set. an exception will be thrown if object is null. * @param keys array of keys to invoke <code>setObjectForKey()</code> for each key. a null * or empty array is a no-op. */ public static <K, V> void setObjectForKeys(final NSMutableDictionary<K, V> dictionary, final V object, final NSArray<K> keys) { // n.b.: we explicitly don't check for a null object to be consistent with the rest of // NSMutableDictionary's API if ( dictionary != null && keys != null && keys.count() > 0 ) { if ( keys.count() == 1 ) { dictionary.setObjectForKey(object, keys.objectAtIndex(0)); } else { final Enumeration<K> e = keys.objectEnumerator(); while ( e.hasMoreElements() ) { dictionary.setObjectForKey(object, e.nextElement()); } } } } /** * Compares dictionary keys based on the value they are associated with. Useful for getting a list * of keys in alphabetical order of their values. */ public static class NSDictionaryKeyValueComparator extends NSComparator { private NSDictionary<?,?> dictionary; public NSDictionaryKeyValueComparator(NSDictionary<?,?> aDictionary) { super(); dictionary = aDictionary; } @Override public int compare(Object key1, Object key2) throws ComparisonException { Object value1 = dictionary.objectForKey(key1); Object value2 = dictionary.objectForKey(key2); if ( ! (value1 instanceof Comparable && value2 instanceof Comparable)) { throw new ComparisonException("dictionary values are not comparable"); } return ((Comparable<Object>)value1).compareTo(value2); } } /** * Returns a deep clone of the given dictionary. A deep clone will attempt to * clone the keys and values (deeply) of this dictionary as well as the * dictionary itself. * * @param dict the dictionary to clone * @param onlyCollections if true, only collections in this dictionary will be cloned, not individual values * @return a deep clone of dict */ public static <K, V> NSDictionary<K, V> deepClone(NSDictionary<K, V> dict, boolean onlyCollections) { NSMutableDictionary<K, V> clonedDict = null; if (dict != null) { clonedDict = dict.mutableClone(); for (K key : dict.allKeys()) { V value = dict.objectForKey(key); K cloneKey = ERXUtilities.deepClone(key, onlyCollections); V cloneValue = ERXUtilities.deepClone(value, onlyCollections); if (cloneKey != key) { clonedDict.removeObjectForKey(key); if (cloneValue != null) { clonedDict.setObjectForKey(cloneValue, cloneKey); } } else if (cloneValue != null) { if (cloneValue != value) { clonedDict.setObjectForKey(cloneValue, cloneKey); } } else { clonedDict.removeObjectForKey(key); } } } return clonedDict; } /** * Encodes a dictionary into a string that can be used in a request uri. * @param dict dictionary with form values * @param separator optional value separator */ public static String queryStringForDictionary(NSDictionary<?, ?> dict, String separator) { return queryStringForDictionary(dict, separator, WOMessage.defaultURLEncoding()); } /** * Encodes a dictionary into a string that can be used in a request uri. * @param dict dictionary with form values * @param separator optional value separator */ public static String queryStringForDictionary(NSDictionary<?, ?> dict, String separator, String encoding) { if (separator == null) { separator = "&"; } StringBuilder sb = new StringBuilder(100); if (dict != null) { for (Enumeration<?> e = dict.allKeys().objectEnumerator(); e.hasMoreElements();) { Object key = e.nextElement(); try { sb.append(URLEncoder.encode(key.toString(), encoding)); sb.append('='); sb.append(URLEncoder.encode(dict.objectForKey(key).toString(), encoding)); if (e.hasMoreElements()) { sb.append(separator); } } catch (UnsupportedEncodingException ex) { // yeah right...like this will ever happen throw NSForwardException._runtimeExceptionForThrowable(ex); } } } return sb.toString(); } }