package er.extensions.foundation;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.Vector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.webobjects.eoaccess.EOEntity;
import com.webobjects.eoaccess.EOModelGroup;
import com.webobjects.eocontrol.EOEnterpriseObject;
import com.webobjects.eocontrol.EOFetchSpecification;
import com.webobjects.eocontrol.EOQualifier;
import com.webobjects.eocontrol.EOQualifierEvaluation;
import com.webobjects.eocontrol.EOSortOrdering;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSBundle;
import com.webobjects.foundation.NSComparator;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSKeyValueCoding;
import com.webobjects.foundation.NSKeyValueCodingAdditions;
import com.webobjects.foundation.NSMutableArray;
import com.webobjects.foundation.NSMutableDictionary;
import com.webobjects.foundation.NSMutableSet;
import com.webobjects.foundation.NSPropertyListSerialization;
import com.webobjects.foundation.NSRange;
import com.webobjects.foundation.NSSelector;
import com.webobjects.foundation.NSSet;
import er.extensions.eof.ERXConstant;
import er.extensions.eof.ERXGenericRecord;
import er.extensions.eof.ERXKey;
/**
* Collection of {@link com.webobjects.foundation.NSArray NSArray} utilities.
*/
public class ERXArrayUtilities {
private static final Logger log = LoggerFactory.getLogger(ERXArrayUtilities.class);
/**
* Holds the null grouping key for use when grouping objects
* based on a key that might return null and nulls are allowed
*/
public static final String NULL_GROUPING_KEY="**** NULL GROUPING KEY ****";
/** caches if array utilities have been initialized */
private static volatile boolean initialized = false;
/** Caches sort orderings for given keys */
private final static NSDictionary<String, NSSelector> _selectorsByKey = new NSDictionary<>(new NSSelector[] {
EOSortOrdering.CompareAscending,
EOSortOrdering.CompareCaseInsensitiveAscending,
EOSortOrdering.CompareCaseInsensitiveDescending,
EOSortOrdering.CompareDescending,
}, new String[] {
"compareAscending",
"compareCaseInsensitiveAscending",
"compareCaseInsensitiveDescending",
"compareDescending",
});
/**
* Simply utility method to create a concrete set object from an array.
*
* @param array of elements
* @return set created from given array
* @deprecated use {@link ERXSetUtilities#setFromArray(Collection)} instead
*/
// CHECKME: Is this a value add?
@Deprecated
public static <T> NSSet<T> setFromArray(NSArray<T> array) {
if (array == null || array.count() == 0) {
return NSSet.EmptySet;
}
return new NSSet<T>(array);
}
/**
* The qualifiers EOSortOrdering.CompareAscending and friends are
* actually 'special' and processed in a different/faster way when
* sorting than a selector that would be created by:
* <code>new NSSelector("compareAscending", ObjectClassArray)</code>
* This method eases the pain on creating those selectors from a string.
*
* @param key sort key
* @return selector for the given sort ordering key or <code>null</code>
*/
public static NSSelector sortSelectorWithKey(String key) {
NSSelector result = null;
if (key != null && !key.equals("")) {
result = _selectorsByKey.objectForKey(key);
if (result == null) {
result = new NSSelector(key, ERXConstant.ObjectClassArray);
}
}
return result;
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned. This is a typesafe variant of
* arrayGroupedByKeyPath(NSArray<V> objects, String keyPath).
* <p>
* See <code>arrayGroupedByKeyPath(NSArray<V> objects, String keyPath)</code> for examples.
* <p>
* This method calls
* <code>arrayGroupedByKeyPath(NSArray objects, String keyPath, Object nullGroupingKey, String valueKeyPath)</code>
* with includeNulls set to true and valueKeyPath set to null.
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByKeyPath(Collection<V> objects, ERXKey<K> keyPath) {
return arrayGroupedByKeyPath(objects, (keyPath == null) ? null : keyPath.key());
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned.
* <p>
* If one starts with:
<pre><code>( { lastName = "Barker"; firstName = "Bob"; favoriteColor = "blue"; },
{ firstName = "Bob"; favoriteColor = "red"; },
{ lastName = "Further"; firstName = "Frank"; favoriteColor = "green"; } )</code></pre>
* and one calls <code>arrayGroupedByKeyPath(objects, "firstName")</code>, one gets:
<pre><code>{ "Bob" = ( { lastName = "Barker"; firstName = "Bob"; favoriteColor = "blue"; }, { firstName = "Bob"; favoriteColor = "red"; } );
"Frank" = ( { lastName = "Further"; firstName = "Frank"; favoriteColor = "green"; } ); }</code></pre>
* If one calls <code>arrayGroupedByKeyPath(objects, "lastName")</code>, one gets:
<pre><code>{ "Bob" = ( { lastName = "Barker"; firstName = "Bob"; favoriteColor = "blue"; } );
"Frank" = ( { lastName = "Further"; firstName = "Frank"; favoriteColor = "green"; } );
"**** NULL GROUPING KEY ****" = ( { firstName = "Bob"; favoriteColor = "red"; } ); }</code></pre>
* <p>
* This method calls <code>arrayGroupedByKeyPath(objects, keyPath, includeNulls, valueKeyPath)</code> with
* includeNulls set to true and valueKeyPath set to null.
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByKeyPath(Collection<V> objects, String keyPath) {
return arrayGroupedByKeyPath(objects,keyPath,true,null);
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned. If valueKeyPath is not null, then
* the grouped arrays each have valueForKey called with valueKeyPath and the
* grouped arrays are replaced with the results of those calls. This is a
* typesafe variant of
* <code>arrayGroupedByKeyPath(NSArray<T> objects, String keyPath, boolean includeNulls, String valueKeyPath).</code>
* <p>
* See <code>arrayGroupedByKeyPath(NSArray<T> objects, String keyPath, boolean includeNulls, String valueKeyPath)</code>
* for examples.
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @param includeNulls determines if keyPaths that resolve to null
* are included in the resulting dictionary
* @param valueKeyPath used to call valueForKey on the arrays in
* the results dictionary, with the results of those calls each
* replacing the corresponding array in the results dictionary.
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
public static <T, K, V> NSDictionary<K, NSArray<V>> arrayGroupedByKeyPath(Collection<T> objects, ERXKey<K> keyPath, boolean includeNulls, ERXKey<V> valueKeyPath) {
return arrayGroupedByKeyPath(objects, (keyPath == null) ? null : keyPath.key(), includeNulls, (valueKeyPath == null) ? null : valueKeyPath.key());
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned. If valueKeyPath is not null, then
* the grouped arrays each have valueForKey called with valueKeyPath and the
* grouped arrays are replaced with the results of those calls.
* <p>
* If one starts with:
<pre><code>( { lastName = "Barker"; firstName = "Bob"; favoriteColor = "blue"; },
{ firstName = "Bob"; favoriteColor = "red"; },
{ lastName = "Further"; firstName = "Frank"; favoriteColor = "green"; } )</code></pre>
* and one calls <code>arrayGroupedByKeyPath(objects, "firstName", true, "favoriteColor")</code>, one gets:
<pre><code>{Frank = ("green"); Bob = ("blue", "red");</code></pre>
* If one calls <code>arrayGroupedByKeyPath(objects, "lastName", false, "favoriteColor")</code>, one gets:
<pre><code>{Further = ("green"); Barker = ("blue"); }</code></pre>
* If one calls <code>arrayGroupedByKeyPath(objects, "lastName", true, "favoriteColor")</code>, one gets:
<pre><code>{Further = ("green"); Barker = ("blue"); "**** NULL GROUPING KEY ****" = ("red"); }</code></pre>
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @param includeNulls determines if keyPaths that resolve to null
* are included in the resulting dictionary
* @param valueKeyPath used to call valueForKey on the arrays in
* the results dictionary, with the results of those calls each
* replacing the corresponding array in the results dictionary.
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
public static <T, K, V> NSDictionary<K, NSArray<V>> arrayGroupedByKeyPath(Collection<T> objects, String keyPath, boolean includeNulls, String valueKeyPath) {
return arrayGroupedByKeyPath(objects, keyPath, (includeNulls) ? (K)NULL_GROUPING_KEY : null, valueKeyPath);
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned. If valueKeyPath is not null, then
* the grouped arrays each have valueForKey called with valueKeyPath and the
* grouped arrays are replaced with the results of those calls. This is a
* typesafe variant of
* <code>arrayGroupedByKeyPath(NSArray objects, String keyPath, Object nullGroupingKey, String valueKeyPath)</code>.
* <p>
* See <code>arrayGroupedByKeyPath(NSArray objects, String keyPath, Object nullGroupingKey, String valueKeyPath)</code>
* for examples.
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @param nullGroupingKey used as the key in the results dictionary
* for the array of objects for which the valueForKey with keyPath
* result is null.
* @param valueKeyPath used to call valueForKey on the arrays in
* the results dictionary, with the results of those calls each
* replacing the corresponding array in the results dictionary.
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
public static <T, K, V> NSDictionary<K, NSArray<V>> arrayGroupedByKeyPath(Collection<T> objects, ERXKey<K> keyPath, K nullGroupingKey, ERXKey<V> valueKeyPath) {
return arrayGroupedByKeyPath(objects, (keyPath == null) ? null : keyPath.key(), nullGroupingKey, (valueKeyPath == null) ? null : valueKeyPath.key());
}
/**
* Starting with an array of KeyValueCoding-compliant objects and a keyPath,
* this method calls valueForKey on each object in the array and groups the
* contents of the array, using the result of the valueForKey call as a key
* in a dictionary. If passed a null array, null is returned. If passed a null
* keyPath, an empty dictionary is returned. If valueKeyPath is not null, then
* the grouped arrays each have valueForKey called with valueKeyPath and the
* grouped arrays are replaced with the results of that call.
* <p>
* If one starts with:
<pre><code>( { lastName = "Barker"; firstName = "Bob"; favoriteColor = "blue"; },
{ firstName = "Bob"; favoriteColor = "red"; },
{ lastName = "Further"; firstName = "Frank"; favoriteColor = "green"; } )</code></pre>
* and one calls <code>arrayGroupedByKeyPath(objects, "firstName", null, "favoriteColor")</code>, one gets:
<pre><code>{Frank = ("green"); Bob = ("blue", "red");</code></pre>
* If one calls <code>arrayGroupedByKeyPath(objects, "lastName", "extra", "favoriteColor")</code>, one gets:
<pre><code>{Further = ("green"); Barker = ("blue"); "extra" = ("red"); }</code></pre>
* If one calls <code>arrayGroupedByKeyPath(objects, "lastName", null, "favoriteColor")</code>, one gets:
<pre><code>{Further = ("green"); Barker = ("blue"); "**** NULL GROUPING KEY ****" = ("red"); }</code></pre>
*
* @param objects array of objects to be grouped
* @param keyPath path into objects used to group the objects
* @param nullGroupingKey used as the key in the results dictionary
* for the array of objects for which the valueForKey with keyPath
* result is null.
* @param valueKeyPath used to call valueForKey on the arrays in
* the results dictionary, with the results of those calls each
* replacing the corresponding array in the results dictionary.
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have that value.
* Objects for which the key path returns null will be grouped
* with the key {@link #NULL_GROUPING_KEY}
*/
@SuppressWarnings("unchecked")
public static <T> NSDictionary arrayGroupedByKeyPath(Collection<T> objects, String keyPath, Object nullGroupingKey, String valueKeyPath) {
if (objects == null)return null;
NSMutableDictionary result=new NSMutableDictionary();
for (T eo : objects) {
Object key = NSKeyValueCodingAdditions.Utility.valueForKeyPath(eo,keyPath);
boolean isNullKey = key==null || key instanceof NSKeyValueCoding.Null;
if (!isNullKey || nullGroupingKey != null) {
if (isNullKey) key=nullGroupingKey;
NSMutableArray existingGroup=(NSMutableArray)result.objectForKey(key);
if (existingGroup==null) {
existingGroup=new NSMutableArray();
result.setObjectForKey(existingGroup,key);
}
if (valueKeyPath!=null) {
Object value=NSKeyValueCodingAdditions.Utility.valueForKeyPath(eo,valueKeyPath);
if (value!=null) existingGroup.addObject(value);
} else {
existingGroup.addObject(eo);
}
}
}
return result;
}
/**
* Typesafe variant of arrayGroupedByToManyKeyPath.
*
* @param objects the objects to be grouped
* @param keyPath the key to group by
* @param includeNulls determines if the key paths that resolve to null should be allowed in the group
* @return the resulting dictionary
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByToManyKeyPath(Collection<V> objects, ERXKey<K> keyPath, boolean includeNulls) {
return arrayGroupedByToManyKeyPath(objects, (keyPath == null) ? null : keyPath.key(), includeNulls);
}
/**
* Groups an array of objects by a given to-many key path, where every
* single item in the to-many will put the object in the corresponding group.
* A typical example is an array of users with a roles relationship. The result to
* calling <code>arrayGroupedByToManyKeyPath(users, "roles.name")</code> would be
* <code>"admin" = (user1, user2); "editor" = (user3);...</code>.
* The dictionary that is returned contains keys that correspond to the grouped
* keys values. This means that the object pointed to by the key
* path must be a cloneable object. For instance using the key path
* 'users' would not work because enterprise objects are not
* cloneable. Instead you might choose to use the key path 'users.name'
* of 'users.primaryKey', if your enterprise objects support this
* see {@link ERXGenericRecord} if interested.
* @param objects array of objects to be grouped
* @param keyPath path used to group the objects.
* @param includeNulls determines if keyPaths that resolve to null
* should be allowed into the group.
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have the grouped
* characteristic. Note that if the key path returns null
* then one of the keys will be the static ivar NULL_GROUPING_KEY
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByToManyKeyPath(Collection<V> objects,
String keyPath,
boolean includeNulls) {
return arrayGroupedByToManyKeyPath(objects, keyPath, includeNulls ? (K) NULL_GROUPING_KEY : null);
}
/**
* Typesafe variant of arrayGroupedByToManyKeyPath.
*
* @param objects array of objects to be grouped
* @param keyPath path used to group the objects.
* @param nullGroupingKey if not-null, determines if keyPaths that resolve to null
* should be allowed into the group; if so, this key is used for them
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have the grouped
* characteristic. Note that if the key path returns null
* then one of the keys will be the static ivar NULL_GROUPING_KEY
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByToManyKeyPath(Collection<V> objects, ERXKey<K> keyPath, K nullGroupingKey) {
return arrayGroupedByToManyKeyPath(objects, (keyPath == null) ? null : keyPath.key(), nullGroupingKey);
}
/**
* Groups an array of objects by a given to-many key path, where every
* single item in the to-many will put the object in the corresponding group.
* The dictionary that is returned contains keys that correspond to the grouped
* keys values. This means that the object pointed to by the key
* path must be a cloneable object. For instance using the key path
* 'users' would not work because enterprise objects are not
* cloneable. Instead you might choose to use the key path 'users.name'
* of 'users.primaryKey', if your enterprise objects support this
* see {@link ERXGenericRecord} if interested.
* @param objects array of objects to be grouped
* @param keyPath path used to group the objects.
* @param nullGroupingKey if not-null, determines if keyPaths that resolve to null
* should be allowed into the group; if so, this key is used for them
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have the grouped
* characteristic. Note that if the key path returns null
* then one of the keys will be the static ivar NULL_GROUPING_KEY
*/
public static <K, V> NSDictionary<K, NSArray<V>> arrayGroupedByToManyKeyPath(Collection<V> objects,
String keyPath,
K nullGroupingKey) {
return arrayGroupedByToManyKeyPath(objects, keyPath, nullGroupingKey, null);
}
/**
* Typesafe variant of arrayGroupedByToManyKeyPath.
*
* @param objects array of objects to be grouped
* @param keyPath path used to group the objects.
* @param nullGroupingKey if not-null, determines if keyPaths that resolve to null
* should be allowed into the group; if so, this key is used for them
* @param valueKeyPath allows the grouped objects in the result to be
* derived from objects (by evaluating valueKeyPath), instead
* of being members of the objects collection. Objects that
* evaluate valueKeyPath to null have no value included in the
* result
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have the grouped
* characteristic. Note that if the key path returns null
* then one of the keys will be the static ivar NULL_GROUPING_KEY
*/
public static <T, K, V> NSDictionary<K, NSArray<V>> arrayGroupedByToManyKeyPath(Collection<T> objects, ERXKey<K> keyPath, K nullGroupingKey, ERXKey<V> valueKeyPath) {
return arrayGroupedByToManyKeyPath(objects, (keyPath == null) ? null : keyPath.key(), nullGroupingKey, (valueKeyPath == null) ? null : valueKeyPath.key());
}
/**
* Groups an array of objects by a given to-many key path, where every
* single item in the to-many will put the object in the corresponding group.
* The dictionary that is returned contains keys that correspond to the grouped
* keys values. This means that the object pointed to by the key
* path must be a cloneable object. For instance using the key path
* 'users' would not work because enterprise objects are not
* cloneable. Instead you might choose to use the key path 'users.name'
* of 'users.primaryKey', if your enterprise objects support this
* see {@link ERXGenericRecord} if interested.
* @param objects array of objects to be grouped
* @param keyPath path used to group the objects.
* @param nullGroupingKey if not-null, determines if keyPaths that resolve to null
* should be allowed into the group; if so, this key is used for them
* @param valueKeyPath allows the grouped objects in the result to be
* derived from objects (by evaluating valueKeyPath), instead
* of being members of the objects collection. Objects that
* evaluate valueKeyPath to null have no value included in the
* result
* @return a dictionary where the keys are the grouped values and the
* objects are arrays of the objects that have the grouped
* characteristic. Note that if the key path returns null
* then one of the keys will be the static ivar NULL_GROUPING_KEY
*/
@SuppressWarnings("unchecked")
public static <T> NSDictionary arrayGroupedByToManyKeyPath(Collection<T> objects,
String keyPath,
Object nullGroupingKey,
String valueKeyPath) {
NSMutableDictionary result=new NSMutableDictionary();
for (T object : objects) {
Object key = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object,keyPath);
boolean isNullKey = key==null || key instanceof NSKeyValueCoding.Null;
if (!isNullKey || nullGroupingKey != null) {
if (isNullKey) key=nullGroupingKey;
NSArray array = (NSArray)key;
for(@SuppressWarnings("null") Enumeration keys = array.objectEnumerator(); keys.hasMoreElements(); ) {
key = keys.nextElement();
NSMutableArray existingGroup=(NSMutableArray)result.objectForKey(key);
if (existingGroup==null) {
existingGroup=new NSMutableArray();
result.setObjectForKey(existingGroup,key);
}
if (valueKeyPath!=null) {
Object value=NSKeyValueCodingAdditions.Utility.valueForKeyPath(object,valueKeyPath);
if (value!=null) existingGroup.addObject(value);
} else {
existingGroup.addObject(object);
}
}
}
}
return result;
}
/**
* Simple comparison method to see if two array
* objects are identical sets.
*
* @param array1 first array
* @param array2 second array
* @return result of comparison
*/
public static <T> boolean arraysAreIdenticalSets(Collection<? super T> array1, Collection<? super T> array2) {
if (array1 == null || array2 == null) {
return array1 == array2;
}
for (Object item : array1) {
if (!array2.contains(item)) {
return false;
}
}
for (Object item : array2) {
if (!array1.contains(item)) {
return false;
}
}
return true;
}
/**
* Filters any kinds of collections that implements {@link Enumeration}
* interface such as {@link com.webobjects.foundation.NSArray NSArray}, {@link com.webobjects.foundation.NSSet NSSet}, {@link Vector}
* and {@link Hashtable} using the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation} interface.
*
* @param enumeration to be filtered; to obtain an enumeration,
* use objectEnumerator() for the collections in
* com.webobjects.foundation package
* and use elements() for the Vector and Hashtable
* @param qualifier to do the filtering
* @return array of filtered results.
* @deprecated use {@link #filteredArrayWithQualifierEvaluation(Iterable, EOQualifierEvaluation)} instead
*/
@Deprecated
public static <T> NSArray<T> filteredArrayWithQualifierEvaluation(Enumeration<T> enumeration, EOQualifierEvaluation qualifier) {
NSMutableArray<T> result = new NSMutableArray<>();
while (enumeration.hasMoreElements()) {
T object = enumeration.nextElement();
if (qualifier.evaluateWithObject(object))
result.addObject(object);
}
return result;
}
/**
* Filters any kinds of collections that implement {@link Iterable}
* interface such as {@link NSArray} or {@link NSSet} using the
* {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation} interface.
*
* @param <T> class of array items
* @param iterable to be filtered
* @param qualifier to do the filtering
* @return array of filtered results
*/
public static <T> NSArray<T> filteredArrayWithQualifierEvaluation(Iterable<T> iterable, EOQualifierEvaluation qualifier) {
if (iterable == null) {
return NSArray.emptyArray();
}
if (iterable instanceof Collection) {
if (((Collection)iterable).isEmpty()) {
return NSArray.emptyArray();
}
}
NSMutableArray<T> result = new NSMutableArray<>();
for (T object : iterable) {
if (qualifier.evaluateWithObject(object)) {
result.add(object);
}
}
return result;
}
/**
* Checks if the given enumeration contains at least one match defined by the given object
* implementing the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation} interface.
*
* @param enumeration to be tested
* @param qualifier to do the filtering
* @return true if there is at least one match
*/
public static boolean enumerationHasMatchWithQualifierEvaluation(Enumeration<?> enumeration, EOQualifierEvaluation qualifier) {
while (enumeration.hasMoreElements()) {
Object object = enumeration.nextElement();
if (qualifier.evaluateWithObject(object)) {
return true;
}
}
return false;
}
/**
* Checks if the given iterator contains at least one match defined by the given object
* implementing the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation} interface.
*
* @param iterator to be tested
* @param qualifier to do the filtering
* @return true if there is at least one match
*/
public static boolean iteratorHasMatchWithQualifierEvaluation(Iterator<?> iterator, EOQualifierEvaluation qualifier) {
while (iterator.hasNext()) {
Object object = iterator.next();
if (qualifier.evaluateWithObject(object)) {
return true;
}
}
return false;
}
/**
* Filters any kind of collections that implements {@link Iterator}
* interface such as {@link ArrayList}, {@link HashMap}, {@link SortedSet}
* and {@link TreeSet} using the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation} interface.
*
* @param iterator to be filtered; use iterator() to obtain
* an iterator from the collections
* @param qualifier to do the filtering
* @return array of filtered results.
* @deprecated use {@link #filteredArrayWithQualifierEvaluation(Iterable, EOQualifierEvaluation)} instead
*/
@Deprecated
public static <T> NSArray<T> filteredArrayWithQualifierEvaluation(Iterator<T> iterator, EOQualifierEvaluation qualifier) {
NSMutableArray<T> result = new NSMutableArray<>();
while (iterator.hasNext()) {
T object = iterator.next();
if (qualifier.evaluateWithObject(object))
result.addObject(object);
}
return result;
}
/**
* Filters out duplicates of an array of objects
* based on the value of the given key path off of those objects.
* Objects with a null value will be skipped, too.
*
* @param <T> class of array items
* @param objects array of objects
* @param keyPath key path to be evaluated off of every object
* @return filter array of objects based on the value of a key path
*/
public static <T> NSArray<T> arrayWithoutDuplicateKeyValue(Iterable<T> objects, ERXKey<?> keyPath) {
return arrayWithoutDuplicateKeyValue(objects, (keyPath == null) ? null : keyPath.key());
}
/**
* Filters out duplicates of an array of objects
* based on the value of the given key path off of those objects.
* Objects with a null value will be skipped, too.
*
* @param <T> class of array items
* @param objects array of objects
* @param keyPath key path to be evaluated off of every object
* @return filter array of objects based on the value of a key path
*/
public static <T> NSArray<T> arrayWithoutDuplicateKeyValue(Iterable<T> objects, String keyPath) {
if (objects == null || keyPath == null) {
return NSArray.emptyArray();
}
if (objects instanceof Collection) {
if (((Collection)objects).isEmpty()) {
return NSArray.emptyArray();
}
}
Set<Object> present = new HashSet<>();
NSMutableArray<T> result = new NSMutableArray<>();
for (T object : objects){
Object value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, keyPath);
if (value != null && !present.contains(value)) {
present.add(value);
result.add(object);
}
}
return result;
}
/**
* Subtracts the contents of one array from another.
* The order of the array should be preserved.
*
* @param <T> class of array items
* @param array array to have values removed from it
* @param minus array of values to remove from the main array
* @return array after performing subtraction
*/
public static <T> NSArray<T> arrayMinusArray(Collection<T> array, Collection<?> minus) {
if (array.isEmpty() || minus == null || minus.isEmpty()) {
if (array instanceof NSArray) {
return ((NSArray)array).immutableClone();
}
return new NSArray<>(array);
}
Collection<T> arrayList = new ArrayList<>(array);
arrayList.removeAll(minus);
return new NSArray<>(arrayList);
}
/**
* Subtracts a single object from an array.
*
* @param <T> class of array items
* @param array array to have value removed from it
* @param object to be removed
* @return array after performing subtraction
*/
public static <T> NSArray<T> arrayMinusObject(Collection<T> array, T object) {
if (object == null) {
return new NSArray<>(array);
}
NSMutableArray<T> result = new NSMutableArray<>(array);
boolean removed = true;
while (removed) {
removed = result.remove(object);
}
return result.immutableClone();
}
/**
* Creates an array preserving order by adding all of the
* non-duplicate values from the second array to the first.
*
* @param <T> class of array items
* @param array1 first array
* @param array2 second array
* @return array containing all of the elements of the first
* array and all of the non-duplicate elements of
* the second array.
*/
public static <T> NSArray<T> arrayByAddingObjectsFromArrayWithoutDuplicates(Collection<? extends T> array1, Collection<? extends T> array2) {
if (array2 == null || array2.isEmpty()) {
if (array1 == null || array1.isEmpty()) {
return NSArray.emptyArray();
} else if (array1 instanceof NSArray) {
return ((NSArray)array1).immutableClone();
}
return new NSArray<>(array1);
}
NSMutableArray<T> result = new NSMutableArray<>(array1);
addObjectsFromArrayWithoutDuplicates(result, array2);
return result;
}
/**
* Creates an array that has all of the objects of the parameter array
* without the first object.
*
* @param <T> class of array items
* @param array the array to use to create the result
* @return an array containing all objects but the first of the
* parameter array. if null is passed, null is returned.
* if the parameter array is empty, an empty array is returned.
*/
public static <T> NSArray<T> arrayByRemovingFirstObject(NSArray<T> array) {
if (array == null) {
return null;
}
if (array.isEmpty()) {
return NSArray.emptyArray();
}
return array.subarrayWithRange(new NSRange(1, array.size() - 1));
}
/**
* Adds the object to the mutable array if the object is not null.
*
* @param <T> class of array items
* @param array mutable array where non-null object will be added
* @param object to be added to array
*/
public static <T> void safeAddObject(NSMutableArray<T> array, T object) {
if (array != null && object != null) {
array.add(object);
}
}
/**
* Adds all of the non-duplicate elements from the second
* array to the mutable array.
*
* @param <T> class of array items
* @param array1 mutable array where non-duplicate objects are
* added
* @param array2 array to be added to a1
*/
public static <T> void addObjectsFromArrayWithoutDuplicates(NSMutableArray<T> array1, Collection<? extends T> array2) {
if (array2 == null || array2.isEmpty()) {
return;
}
Set<T> present = new HashSet<>(array1);
for (T object : array2) {
if (!present.contains(object)) {
array1.add(object);
present.add(object);
}
}
}
/**
* Recursively flattens an array of arrays and individual
* objects into a single array of elements.
* <p>
* For example:
* <pre><code>NSArray foos; //Assume exists
* NSArray bars = (NSArray)foos.valueForKey("toBars");
* </code></pre>
* In this case if <code>foos</code> contained five elements
* then the array <code>bars</code> will contain five arrays
* each corresponding to what <code>aFoo.toBars</code> would
* return. To have the entire collection of <code>bars</code>
* in one single array you would call:
* <code>NSArray allBars = flatten(bars)</code>
*
* @param array array to be flattened
* @param filterDuplicates determines if the duplicate values
* should be filtered
* @return an array containing all of the elements from
* all of the arrays contained within the array
* passed in. (Optionally, with duplicate elements filtered out)
*/
@SuppressWarnings("unchecked")
public static NSArray flatten(NSArray<?> array, boolean filterDuplicates) {
NSArray<?> result = flatten(array);
if (filterDuplicates) {
result = arrayWithoutDuplicates(result);
}
return result;
}
/**
* Recursively flattens an array of arrays and individual
* objects into a single array of elements.
* <p>
* For example:
* <pre><code>NSArray foos; //Assume exists
* NSArray bars = (NSArray)foos.valueForKey("toBars");
* </code></pre>
* In this case if <code>foos</code> contained five elements
* then the array <code>bars</code> will contain five arrays
* each corresponding to what <code>aFoo.toBars</code> would
* return. To have the entire collection of <code>bars</code>
* in one single array you would call:
* <code>NSArray allBars = flatten(bars)</code>
*
* @param array array to be flattened
* @return an array containing all of the elements from
* all of the arrays contained within the array
* passed in.
*/
@SuppressWarnings("unchecked")
public static NSArray flatten(NSArray<?> array) {
if (array == null || array.isEmpty()) {
return array;
}
NSMutableArray<Object> result = null; // Not gonna create a new array if we don't actually need to flatten
for (int i = 0; i < array.size(); i++) {
Object element = array.get(i);
if (element instanceof NSArray) {
if (result == null) {
// Turns out we actually need to flatten
result = new NSMutableArray<>();
for (int backfillIndex = 0; backfillIndex < i; backfillIndex++) {
// backfill any singles we put off copying
result.add(array.get(backfillIndex));
}
}
NSArray<?> flattenedChildArray = flatten((NSArray<?>)element);
result.addAll(flattenedChildArray);
} else if (result != null) {
result.add(element);
} // Otherwise let's put off copying the element, the backfill section above will take care of it.
}
// CLEANUP: Arguably safer to return the immutable array we are declared as returning
return result != null ? result : array;
}
/**
* Creates an NSArray 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 NSArray deserialized from the property list.
*/
@SuppressWarnings("unchecked")
public static NSArray arrayFromPropertyList(String name, NSBundle bundle) {
return (NSArray<?>)NSPropertyListSerialization.propertyListFromString(ERXStringUtilities.stringFromResource(name, "plist", bundle));
}
/**
* Performs multiple key-value coding calls against an array or an object.
*
* @param source collection or object to be acted upon.
* @param keyPaths array of key paths.
* @return for collections, returns an array containing an array of values for every key path.
* For objects, returns an array containing a value for every key path.
*/
public static NSArray valuesForKeyPaths(Object source, Collection<String> keyPaths) {
if (keyPaths == null || keyPaths.isEmpty()) {
return NSArray.emptyArray();
}
NSMutableArray<Object> result = new NSMutableArray<>();
for (String keyPath : keyPaths) {
Object value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(source, keyPath);
result.add(value != null ? value : NSKeyValueCoding.NullValue);
}
return result;
}
/**
* Returns the first object of the array. If the array is null or empty, null is returned.
*
* @param <T> class of array items
* @param array the array to search
* @return the first object in array. null if array is empty or null.
*/
public static <T> T firstObject(List<T> array) {
T result = null;
if (array != null && !array.isEmpty()) {
result = array.get(0);
}
return result;
}
/**
* Finds the index of the first object in the array with a given value for a given key path.
* Assumes that all objects in the array either are NSKeyValueCoding.NullValue or have the given key path.
*
* @param <T> class of array items
* @param <V> class of value from key path
* @param array the array to search
* @param value the value to look for
* @param keyPath the key path to use to compare to value
* @return index of the first object with the qualification. -1 if none matches.
*/
public static <T, V> int indexOfFirstObjectWithValueForKeyPath(Collection<T> array, V value, ERXKey<V> keyPath) {
return indexOfFirstObjectWithValueForKeyPath(array, value, (keyPath == null) ? null : keyPath.key());
}
/**
* Finds the index of the first object in the array with a given value for a given key path.
* Assumes that all objects in the array either are NSKeyValueCoding.NullValue or have the given key path.
*
* @param <T> class of array items
* @param array the array to search
* @param value the value to look for
* @param keyPath the key path to use to compare to value
* @return index of the first object with the qualification. -1 if none matches.
*/
public static <T> int indexOfFirstObjectWithValueForKeyPath(Collection<T> array, Object value, String keyPath) {
int i = 0;
for (T object : array) {
if (object != NSKeyValueCoding.NullValue) {
Object currentValue = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, keyPath);
currentValue = (currentValue == NSKeyValueCoding.NullValue ? null : currentValue);
if (Objects.equals(currentValue, value)) {
return i;
}
}
i++;
}
return -1;
}
/**
* Finds the first object in the array with a given value for a given key path.
*
* @param <T> class of array items
* @param <V> class of value from key path
* @param array the array to search
* @param value the value to look for
* @param keyPath the key path to use to compare to value
* @return first object in the array with the qualification. null if none matches.
*/
public static <T, V> T firstObjectWithValueForKeyPath(List<T> array, V value, ERXKey<V> keyPath) {
return firstObjectWithValueForKeyPath(array, value, (keyPath == null) ? null : keyPath.key());
}
/**
* Finds the first object in the array with a given value for a given key path.
*
* @param <T> class of array items
* @param array the array to search
* @param value the value to look for
* @param keyPath the key path to use to compare to value
* @return first object in the array with the qualification. null if none matches.
*/
public static <T> T firstObjectWithValueForKeyPath(List<T> array, Object value, String keyPath) {
int index = indexOfFirstObjectWithValueForKeyPath(array, value, keyPath);
return index >= 0 ? array.get(index) : null;
}
/**
* Walks over an array and returns an array of objects from that array that have a particular
* value for a particular key path. Treats null and NSKeyValueCoding.NullValue equivalently.
* Any NSKeyValueCoding.NullValue objects in the array are skipped. If array is null or empty,
* an empty array is returned.
*
* @param <T> class of array items
* @param <V> class of value from key path
* @param array array to search
* @param value value to look for
* @param keyPath key path to apply on each object on the array to compare against valueToLookFor
* @return an array of matching objects
*/
public static <T, V> NSArray<T> objectsWithValueForKeyPath(Collection<T> array, V value, ERXKey<V> keyPath) {
return objectsWithValueForKeyPath(array, value, (keyPath == null) ? null : keyPath.key());
}
/**
* Walks over an array and returns an array of objects from that array that have a particular
* value for a particular key path. Treats null and NSKeyValueCoding.NullValue equivalently.
* Any NSKeyValueCoding.NullValue objects in the array are skipped. If array is null or empty,
* an empty array is returned.
*
* @param <T> class of array items
* @param array array to search
* @param value value to look for
* @param keyPath key path to apply on each object on the array to compare against valueToLookFor
* @return an array of matching objects
*/
public static <T> NSArray<T> objectsWithValueForKeyPath(Collection<T> array, Object value, String keyPath) {
if (array == null || array.isEmpty()) {
return NSArray.emptyArray();
}
boolean valueToLookForIsNull = value == null || value == NSKeyValueCoding.NullValue;
NSMutableArray<T> result = new NSMutableArray<>();
for (T object : array) {
if (object != NSKeyValueCoding.NullValue) {
Object keyPathValue = NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, keyPath);
boolean theValueIsNull = keyPathValue == null || keyPathValue == NSKeyValueCoding.NullValue;
if ( (theValueIsNull && valueToLookForIsNull) || Objects.equals(value, keyPathValue) ) {
result.add(object);
}
}
}
return result.immutableClone();
}
/**
* Locates an object within an array using a custom equality check provided as an ERXEqualator. This
* is useful if you have an array of EOs and want to find a particular EO in it without regard to editing
* contexts.
*
* @param <T> class of array items
* @param array the array to search
* @param object the object to look for
* @param equalator the equalator to use for performing the equality check between object and each object
* in the array
* @return index of first occurring object in the array that is defined as equal by the equalator. -1
* if no such object is found.
*/
public static <T> int indexOfObjectUsingEqualator(Collection<T> array, T object, ERXEqualator equalator) {
int i = 0;
for (T item : array) {
if (equalator.objectIsEqualToObject(item, object)) {
return i;
}
i++;
}
return -1;
}
/**
* Sorts a given array with a key in ascending fashion and returns a mutable clone of the result.
*
* @param array array to be sorted.
* @param key sort key.
* @return mutable clone of sorted array.
*/
// CHECKME ak: I probably wrote this, but do we really need it?
public static <T> NSMutableArray<T> sortedMutableArraySortedWithKey(NSArray<T> array, String key) {
return sortedArraySortedWithKey(array, key).mutableClone();
}
/**
* Sorts a given array with a key in ascending fashion.
*
* @param array array to be sorted.
* @param key sort key.
* @return sorted array.
*/
public static <T> NSArray<T> sortedArraySortedWithKey(NSArray<T> array, String key) {
return sortedArraySortedWithKey(array, key, null);
}
/**
* Sorts a given array with a key in ascending fashion.
* @param array array to be sorted.
* @param key sort key.
* @param selector sort order selector to use, if null, then sort will be case insensitive ascending.
* @return sorted array.
*/
public static <T> NSArray<T> sortedArraySortedWithKey(NSArray<T> array, String key, NSSelector selector) {
ERXAssert.PRE.notNull("Attempting to sort null array of objects.", array);
ERXAssert.PRE.notNull("Attepting to sort array of objects with null key.", key);
NSArray<EOSortOrdering> order=new NSArray<>(new EOSortOrdering[] {EOSortOrdering.sortOrderingWithKey(key, selector == null ? EOSortOrdering.CompareCaseInsensitiveAscending : selector)});
return EOSortOrdering.sortedArrayUsingKeyOrderArray(array, order);
}
/**
* Sorts a given array with a set of keys according to the given selector.
* @param array array to be sorted.
* @param keys sort keys
* @param selector sort order selector to use, if null, then sort will be case insensitive ascending.
a.addObject(theObject);
a.addObject(theObject);
* @return sorted array.
*/
public static <T> NSArray<T> sortedArraySortedWithKeys(NSArray<T> array, NSArray<String> keys, NSSelector selector) {
ERXAssert.PRE.notNull("Attempting to sort null array of objects.", array);
ERXAssert.PRE.notNull("Attepting to sort an array with null keys.", keys);
if (keys.count() < 2)
return sortedArraySortedWithKey(array, keys.lastObject(), selector == null ? EOSortOrdering.CompareCaseInsensitiveAscending : selector);
NSMutableArray<EOSortOrdering> order = new NSMutableArray<>(keys.count());
for (Enumeration<String> keyEnumerator = keys.objectEnumerator(); keyEnumerator.hasMoreElements();) {
String key = keyEnumerator.nextElement();
order.addObject(EOSortOrdering.sortOrderingWithKey(key, selector == null ? EOSortOrdering.CompareCaseInsensitiveAscending : selector));
}
return EOSortOrdering.sortedArrayUsingKeyOrderArray(array, order);
}
/**
* Sorts a given mutable array with a key in place.
* @param array array to be sorted.
* @param key sort key.
*/
public static void sortArrayWithKey(NSMutableArray<?> array, String key) {
sortArrayWithKey(array, key, null);
}
/**
* Sorts a given mutable array with a key in place.
* @param array array to be sorted.
* @param key sort key.
* @param selector sort order selector to use, if null, then sort will be ascending.
*/
public static void sortArrayWithKey(NSMutableArray<?> array, String key, NSSelector selector) {
ERXAssert.PRE.notNull("Attempting to sort null array of eos.", array);
ERXAssert.PRE.notNull("Attempting to sort array of eos with null key.", key);
NSArray<EOSortOrdering> order=new NSArray<>(new EOSortOrdering[] {EOSortOrdering.sortOrderingWithKey(key, selector == null ? EOSortOrdering.CompareCaseInsensitiveAscending : selector)});
EOSortOrdering.sortArrayUsingKeyOrderArray(array, order);
}
/**
* The BaseOperator is Wonder's core class of
* {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}.
* This class adds support for chaining multiple array operators in a single
* keypath via its
* {@link er.extensions.foundation.ERXArrayUtilities.BaseOperator#contents(NSArray, String) contents}
* method.
*/
static abstract class BaseOperator implements NSArray.Operator {
/**
* Rather than iterating through the array argument calling
* {@link com.webobjects.foundation.NSKeyValueCodingAdditions.Utility#valueForKeyPath(Object, String) valueForKeyPath}
* on each array object, this method operates by calling
* {@link com.webobjects.foundation.NSArray#valueForKeyPath(String) valueForKeyPath}
* on the array argument instead. This method is used by Wonder operators to chain
* multiple array operators in a single key path.
*
* @param array the array value for the operator
* @param keypath the keypath to call on the array argument
* @return the object value produced by valueForKeyPath, or the array itself
* if the keypath is empty
*/
public Object contents(NSArray<?> array, String keypath) {
if(array != null && array.count() > 0 && keypath != null && keypath.length() > 0) {
return NSKeyValueCodingAdditions.Utility.valueForKeyPath(array, keypath);
}
return array;
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>sort</b>.
* <p>
* This allows for key value paths like:
* <ol>
* <li><code>myArray.valueForKey("@sort.firstName");</code></li>
* <li><code>myArray.valueForKey("@sort.lastName,firstName.length");</code></li>
* </ol>
* Which in the first case would return myArray sorted ascending by first name
* and the second case by lastName and then by the length() of the firstName. Due
* to the way the sort key arguments are written, this key cannot occur anywhere
* except at the very end of the keypath.
*/
public static class SortOperator implements NSArray.Operator {
private NSSelector selector;
public SortOperator(NSSelector selector) {
this.selector = selector;
}
/**
* Sorts the given array by the keypath.
* @param array array to be sorted.
* @param keypath sort key.
* @return immutable sorted array.
*/
public Object compute(NSArray array, String keypath) {
if (array.count() < 2)
return array;
if (keypath != null && keypath.indexOf(",") != -1) {
return sortedArraySortedWithKeys(array,
NSArray.componentsSeparatedByString(keypath, ","),
selector);
}
return sortedArraySortedWithKey(array, keypath, selector);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>fetchSpec</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKey("@fetchSpec.fetchUsers");</code></pre>
* Which in this case would return myArray filtered and sorted by the
* EOFetchSpecification named "fetchUsers" which must be a model-based fetchspec
* in the first object's entity.
*
* @see BaseOperator
*/
public static class FetchSpecOperator extends BaseOperator {
public FetchSpecOperator() {
}
/**
* Filters and sorts the given array by the named fetchspecification.
* @param array array to be filtered.
* @param keypath name of fetch specification.
* @return immutable filtered array.
*/
public Object compute(NSArray array, String keypath) {
if(array.count() == 0) {
return array;
}
EOEnterpriseObject eo = (EOEnterpriseObject)array.objectAtIndex(0);
String fetchSpec = ERXStringUtilities.firstPropertyKeyInKeyPath(keypath);
keypath = ERXStringUtilities.keyPathWithoutFirstProperty(keypath);
if(keypath == null) {
return filteredArrayWithEntityFetchSpecification(array, eo.entityName(), fetchSpec);
}
array = filteredArrayWithEntityFetchSpecification(array, eo.entityName(), fetchSpec);
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>flatten</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKey("@flatten.someOtherPath");</code></pre>
* Which in this case would return myArray flattened if myArray is an NSArray
* of NSArrays (of NSArrays etc) before continuing to process someOtherPath.
*
* @see BaseOperator
*/
public static class FlattenOperator extends BaseOperator {
public FlattenOperator() {
}
/**
* Flattens the given array.
* @param array array to be flattened.
* @param keypath additional keypath
* @return value following keypath for flattened array
*/
public Object compute(NSArray array, String keypath) {
array = flatten(array);
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>isEmpty</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKey("@isEmpty");</code></pre>
* Which in this case would return {@link java.lang.Boolean#TRUE true} if the
* myArray.count() == 0, or {@link java.lang.Boolean#FALSE false} if it is not.
* This operator always ends computation. Any keypath following the isEmpty
* operator is simply ignored.
*
*/
public static class IsEmptyOperator implements NSArray.Operator {
public IsEmptyOperator() {
}
/**
* returns true if the given array is empty, useful for WOHyperlink disabled binding.
* @param array array to be checked.
* @param keypath the keypath. This value is ignored.
* @return <code>Boolean.TRUE</code> if array is empty, <code>Boolean.FALSE</code> otherwise.
*/
public Object compute(NSArray array, String keypath) {
return array.count() == 0 ? Boolean.TRUE : Boolean.FALSE;
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>subarrayWithRange</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKeyPath("@subarrayWithRange.20-3.someOtherPath");</code></pre>
* Which in this case would return the three objects from <code>myArray</code>, starting
* at the index of 20, before continuing to process <code>someOtherPath</code>.
* <p>
* Note that the syntax for the range argument is <b>not</b> startIndex-endIndex. The API
* matches that of NSRange. You must provide a start index and an array length.
*
* @see BaseOperator
*/
public static class SubarrayWithRangeOperator extends BaseOperator {
public SubarrayWithRangeOperator() {
}
/**
* @param array array to truncate
* @param keypath the key path to follow after truncation
* @return the value produced by the keypath after truncating the array
*/
public Object compute(NSArray array, String keypath) {
if(ERXStringUtilities.stringIsNullOrEmpty(keypath)) {
throw new IllegalArgumentException("subarrayWithRange must be used " +
"like '@subarrayWithRange.start-length'");
}
String rangeString = ERXStringUtilities.firstPropertyKeyInKeyPath(keypath);
keypath = ERXStringUtilities.keyPathWithoutFirstProperty(keypath);
int index = rangeString.indexOf('-');
if(index < 1 || index >= rangeString.length()) {
throw new IllegalArgumentException("subarrayWithRange must be used " +
"like '@subarrayWithRange.start-length' current key path: " +
"\"@subarrayWithRange." + rangeString + "\"");
}
int start = Integer.valueOf(rangeString.substring(0, index));
int length = Integer.valueOf(rangeString.substring(++index));
array = array.subarrayWithRange(new NSRange(start, length));
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>limit</b>, which is similar to subarrayWithRange except it is
* always from 0 to the limit value. If the limit specified is larger than the
* size of the array, the entire array will be returned.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKeyPath("@limit.10.someOtherPath");</code></pre>
* Which in this case would return the first 10 objects in <code>myArray</code>
* before continuing to process <code>someOtherPath</code>.
*
* @see BaseOperator
*/
public static class LimitOperator extends BaseOperator {
public LimitOperator() {
}
/**
* Computes the subarray of the given array.
*
* @param array array to be truncated.
* @param keypath the key path to follow after truncation.
* @return the value produced by following the keypath after truncation.
*/
public Object compute(NSArray array, String keypath) {
int dotIndex = keypath.indexOf(".");
String limitStr;
String rest;
if (dotIndex == -1) {
limitStr = keypath;
rest = null;
}
else {
limitStr = keypath.substring(0, dotIndex);
rest = keypath.substring(dotIndex + 1);
}
int length = limitStr.length() == 0 ? 0 : Integer.parseInt(limitStr);
length = Math.min(length, array.count());
NSArray<?> objects = array.subarrayWithRange(new NSRange(0, length));
return contents(objects, rest);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>unique</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKeyPath("@unique.someOtherPath");</code></pre>
* Which in this case would return only those objects which are unique in myArray
* before continuing to process someOtherPath.
*
* @see BaseOperator
*/
public static class UniqueOperator extends BaseOperator {
public UniqueOperator() {
}
/**
* Removes duplicates.
*
* @param array
* array to be uniqued.
* @param keypath
* the key path after removing duplicates from the array
* @return the value produced by following the keypath after removing duplicates
*/
public Object compute(NSArray array, String keypath) {
if (array != null) array = arrayWithoutDuplicates(array);
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>removeNullValues</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKeyPath("@removeNullValues.someOtherPath");</code></pre>
* Which in this case would remove the occurrences of NSKeyValueCoding.Null from myArray
* before continuing to process someOtherPath.
*
* @see BaseOperator
*/
public static class RemoveNullValuesOperator extends BaseOperator {
public RemoveNullValuesOperator() {
}
/**
* Removes null values from the given array.
*
* @param array
* array to be filtered.
* @param keypath
* the key path to follow after filtering
* @return the value produced by following keypath after filtering nulls from the array
*/
public Object compute(NSArray array, String keypath) {
array = removeNullValues(array);
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator} for the key <b>objectAtIndex</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKey("@objectAtIndex.3.firstName");</code></pre>
*/
public static class ObjectAtIndexOperator implements NSArray.Operator {
public ObjectAtIndexOperator() {
}
/**
* returns the keypath value for n-ths object.
* @param array array to be checked.
* @param keypath integer value of index (zero based).
* @return <code>null</code> if array is empty or value is not in index, <code>keypath</code> value for the object at index otherwise.
*/
public Object compute(NSArray array, String keypath) {
int end = keypath.indexOf(".");
int index = Integer.parseInt(keypath.substring(0, end == -1 ? keypath.length() : end));
Object value = null;
if(index < array.count() )
value = array.objectAtIndex(index);
if(end != -1 && value != null) {
value = NSKeyValueCodingAdditions.Utility.valueForKeyPath(value, keypath.substring(end+1));
}
return value;
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>avgNonNull</b>.
* <p>
* This allows for key value paths like:
* <ul>
* <li><code>myArray.valueForKey("@avgNonNull.payment.amount");</code></li>
* <li><code>myArray.valueForKey("payment.@avgNonNull.amount");</code></li>
* <li><code>myArray.valueForKey("payment.amount.@avgNonNull");</code></li>
* </ul>
* which will sum up all values for the key amount and divide by the number
* of nun-null entries. @avgNonNull applies to the array of objects to its
* left if it is the last key in the path. Otherwise it applies to the end
* of the keypath to its right. It should not be followed by an array or
* any other array operators. This is because it does not call
* {@link com.webobjects.foundation.NSArray#valueForKeyPath(String) valueForKeyPath} on
* the array to its left, but instead loops through the values of the array
* to its left, calling
* {@link com.webobjects.foundation.NSKeyValueCodingAdditions.Utility#valueForKeyPath(Object, String) valueForKeyPath}
* on the individual array values instead. This behavior is consistent with
* Apple's standard NSArray operators.
*/
public static class AvgNonNullOperator implements NSArray.Operator {
public AvgNonNullOperator() {
}
/**
* returns the average value for over all non-null values.
* @param array array to be checked.
* @param keypath path to numeric values
* @return computed average as BigDecimal or <code>NULL</code>.
*/
public Object compute(NSArray array, String keypath) {
BigDecimal sum = new BigDecimal(0L);
int count = 0;
Object obj, tmp;
BigDecimal val;
final boolean noKeypath = keypath == null || keypath.length() <= 0;
for(Enumeration<?> e = array.objectEnumerator(); e.hasMoreElements();) {
tmp = e.nextElement();
obj = noKeypath?tmp:NSKeyValueCodingAdditions.Utility.valueForKeyPath(tmp, keypath);
if(!ERXValueUtilities.isNull(obj)) {
count += 1;
val = ERXValueUtilities.bigDecimalValue(obj);
sum = sum.add(val);
}
}
if(count == 0) {
return null;
}
return sum.divide(BigDecimal.valueOf(count), sum.scale() + 4, BigDecimal.ROUND_HALF_EVEN);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>reverse</b>.
* <p>
* This allows for key value paths like:
* <pre><code>myArray.valueForKey("@reverse.someOtherPath");</code></pre>
* which would reverse the order of the array myArray before continuing to
* process someOtherPath.
*
* @see BaseOperator
*/
public static class ReverseOperator extends BaseOperator {
public ReverseOperator() {
}
/**
* returns the reverse value for the values of the keypath.
* @param array array to be checked.
* @param keypath additional keypath
* @return value produced following keypath after array is reversed
*/
public Object compute(NSArray array, String keypath) {
array = reverse(array);
return contents(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>median</b>.
* <p>
* This allows for key value paths like:
* <ul>
* <li><code>myArray.valueForKey("@median.payment.amount");</code></li>
* <li><code>myArray.valueForKey("payment.@median.amount");</code></li>
* <li><code>myArray.valueForKey("payment.amount.@median");</code></li>
* </ul>
* which return the median of the array elements at the given key path. The
* median is the value for which half of the elements are above and half the
* elements are below. As such, an array sort is needed and this might be
* very costly depending of the size of the array.
* <p>
* The @median operator applies to the array of objects to its
* left if it is the last key in the path. Otherwise it applies to the end
* of the keypath to its right. It should not be followed by an array or
* any other array operators. This is because it does not call
* {@link com.webobjects.foundation.NSArray#valueForKeyPath(String) valueForKeyPath} on
* the array to its left, but instead loops through the values of the array
* to its left, calling
* {@link com.webobjects.foundation.NSKeyValueCodingAdditions.Utility#valueForKeyPath(Object, String) valueForKeyPath}
* on the individual array values instead. This behavior is consistent with
* Apple's standard NSArray operators.
*/
public static class MedianOperator implements NSArray.Operator {
public MedianOperator() {
}
/**
* returns the median value for the values of the keypath.
* @param array array to be checked.
* @param keypath path to numeric values
* @return median value
*/
public Object compute(NSArray array, String keypath) {
return median(array, keypath);
}
}
/**
* Define an {@link com.webobjects.foundation.NSArray.Operator NSArray.Operator}
* for the key <b>stdDev</b> and <b>popStdDev</b>.
* <p>
* This allows for key value paths like:
* <ul>
* <li><code>myArray.valueForKey("@stdDev.payment.amount");</code></li>
* <li><code>myArray.valueForKey("payment.@stdDev.amount");</code></li>
* <li><code>myArray.valueForKey("payment.amount.@stdDev");</code></li>
* </ul>
* All three of these examples will return the same value, which in this case
* is the standard deviation of the amounts. The standard deviation is a
* measure of the dispersion of a sample of numbers. The population standard
* deviation is used if you have the values for an entire population.
* <p>
* The standard deviation operator applies to the array of objects to its
* left if it is the last key in the path. Otherwise it applies to the end
* of the keypath to its right. It should not be followed by an array or
* any other array operators. This is because it does not call
* {@link com.webobjects.foundation.NSArray#valueForKeyPath(String) valueForKeyPath} on
* the array to its left, but instead loops through the values of the array
* to its left, calling
* {@link com.webobjects.foundation.NSKeyValueCodingAdditions.Utility#valueForKeyPath(Object, String) valueForKeyPath}
* on the individual array values instead. This behavior is consistent with
* Apple's standard NSArray operators.
*/
public static class StandardDeviationOperator implements NSArray.Operator {
private boolean isPop;
public StandardDeviationOperator(boolean isPopulation) {
isPop = isPopulation;
}
/**
* returns the standard deviation value for the values of the keypath.
* @param array array to be checked.
* @param keypath path to numeric values
* @return standard deviation value
*/
public Object compute(NSArray array, String keypath) {
return stdDev(array, keypath, isPop);
}
}
/**
* Will register new NSArray operators
* <b>sort</b>, <b>sortAsc</b>, <b>sortDesc</b>, <b>sortInsensitiveAsc</b>,
* <b>sortInsensitiveDesc</b>, <b>unique</b>, <b>flatten</b>, <b>reverse</b>,
* <b>limit</b>, and <b>fetchSpec</b>
*/
public static void initialize() {
if (initialized) {
return;
}
initialized = true;
if (ERXProperties.booleanForKeyWithDefault("er.extensions.ERXArrayUtilities.ShouldRegisterOperators", true)) {
NSArray.setOperatorForKey("sort", new SortOperator(EOSortOrdering.CompareAscending));
NSArray.setOperatorForKey("sortAsc", new SortOperator(EOSortOrdering.CompareAscending));
NSArray.setOperatorForKey("sortDesc", new SortOperator(EOSortOrdering.CompareDescending));
NSArray.setOperatorForKey("sortInsensitiveAsc", new SortOperator(EOSortOrdering.CompareCaseInsensitiveAscending));
NSArray.setOperatorForKey("sortInsensitiveDesc", new SortOperator(EOSortOrdering.CompareCaseInsensitiveDescending));
NSArray.setOperatorForKey("flatten", new FlattenOperator());
NSArray.setOperatorForKey("fetchSpec", new FetchSpecOperator());
NSArray.setOperatorForKey("unique", new UniqueOperator());
NSArray.setOperatorForKey("isEmpty", new IsEmptyOperator());
NSArray.setOperatorForKey("subarrayWithRange", new SubarrayWithRangeOperator());
NSArray.setOperatorForKey("objectAtIndex", new ObjectAtIndexOperator());
NSArray.setOperatorForKey("avgNonNull", new AvgNonNullOperator());
NSArray.setOperatorForKey("reverse", new ReverseOperator());
NSArray.setOperatorForKey("removeNullValues", new RemoveNullValuesOperator());
NSArray.setOperatorForKey("median", new MedianOperator());
NSArray.setOperatorForKey("limit", new LimitOperator());
NSArray.setOperatorForKey("stdDev", new StandardDeviationOperator(false));
NSArray.setOperatorForKey("popStdDev", new StandardDeviationOperator(true));
}
}
/**
* Calculates the median value of an array.
* The median is the value for which half of the elements are above and half the elements are below.
* As such, an array sort is needed and this might be very costly depending of the size of the array.
* @param array array of objects
* @param keypath key path for the median
*
* @return the median value
*/
public static Number median(NSArray<?> array, String keypath) {
final int count = array.count();
final boolean noKeypath = keypath == null || keypath.length() <= 0;
Object obj, tmp;
Number value;
if(count == 0) {
value = null;
} else if(count == 1) {
obj = noKeypath?array.objectAtIndex(0):array.valueForKeyPath(keypath);
value = ERXValueUtilities.bigDecimalValue(obj);
} else {
//Sort the array
NSArray sortedArray;
if(noKeypath) {
NSMutableArray sortlist = array.mutableClone();
Collections.sort(sortlist);
sortedArray = sortlist;
} else {
sortedArray = sortedArraySortedWithKey(array, keypath);
}
//Find the midpoint
int mid = count / 2;
obj = sortedArray.objectAtIndex(mid);
//If the count is even, average the two midpoints
if(count % 2 == 0) {
tmp = noKeypath?obj:NSKeyValueCodingAdditions.Utility.valueForKeyPath(obj, keypath);
BigDecimal a = ERXValueUtilities.bigDecimalValue(tmp);
obj = sortedArray.objectAtIndex(mid - 1);
tmp = noKeypath?obj:NSKeyValueCodingAdditions.Utility.valueForKeyPath(obj, keypath);
BigDecimal b = ERXValueUtilities.bigDecimalValue(tmp);
BigDecimal sum = a.add(b);
value = sum.divide(BigDecimal.valueOf(2), sum.scale() + 4, BigDecimal.ROUND_HALF_EVEN);
} else {
tmp = noKeypath?obj:NSKeyValueCodingAdditions.Utility.valueForKeyPath(obj, keypath);
value = ERXValueUtilities.bigDecimalValue(tmp);
}
}
return value;
}
/**
* Finds the standard deviation of the numeric values found in the array at the
* specified keypath. If the keypath is null or empty, then the array values are
* used instead. If the array has fewer than two objects, null is returned. If
* isPopulation is true, the population standard deviation is calculated. If
* isPopulation is false, the sample standard deviation is calculated. Use a
* true value for isPopulation if you know the values for an entire population
* and false if you are dealing with a sample.
*
* @param array an array of objects
* @param keypath a key path to a numeric value on each object
* @param isPopulation
* @return the standard deviation for the numeric values
*/
public static Number stdDev(NSArray<?> array, String keypath, boolean isPopulation) {
final int count = array.count();
if(count < 2) {return null;}
final boolean noKeypath = keypath == null || keypath.length() <= 0;
Object val = noKeypath?array.valueForKey("@avg"):array.valueForKeyPath(keypath + ".@avg");
BigDecimal mean = ERXValueUtilities.bigDecimalValue(val);
BigDecimal sum = BigDecimal.valueOf(0);
BigDecimal divisor = BigDecimal.valueOf(isPopulation?count:count-1);
BigDecimal diff;
Object obj;
for(Object tmp: array) {
obj = noKeypath?tmp:NSKeyValueCodingAdditions.Utility.valueForKeyPath(tmp, keypath);
diff = ERXValueUtilities.bigDecimalValue(obj).subtract(mean);
diff = diff.multiply(diff);
sum = sum.add(diff);
}
sum = sum.divide(divisor, sum.scale() + 4, BigDecimal.ROUND_HALF_EVEN);
return BigDecimal.valueOf(Math.sqrt(sum.doubleValue()));
}
/**
* Shorter name for arrayWithoutDuplicates, which I always forget the name of.
*
* @param <T> class of array items
* @param array the array to return distinct values from
* @return an array of distinct elements from the input array
*/
public static <T> NSArray<T> distinct(Collection<T> array) {
return arrayWithoutDuplicates(array);
}
/**
* Filters out all of the duplicate objects in
* a given array. Preserves the order now.
*
* @param <T> class of array items
* @param array to be filtered
* @return filtered array
*/
public static <T> NSArray<T> arrayWithoutDuplicates(Collection<T> array) {
NSMutableArray<T> result = new NSMutableArray<>();
Set<T> present = new HashSet<>();
for (T object : array) {
if (!present.contains(object)){
present.add(object);
result.add(object);
}
}
return result;
}
/**
* Batches an NSArray into sub-arrays of the given size.
*
* @param <T> class of array items
* @param array array to batch
* @param batchSize number of items in each batch
* @return NSArray of NSArrays, each with at most batchSize items
*/
public static <T> NSArray<NSArray<T>> batchedArrayWithSize(NSArray<T> array, int batchSize) {
if (array == null || array.isEmpty()) {
return NSArray.emptyArray();
}
if (batchSize < 1) {
throw new IllegalArgumentException("batchSize is " + batchSize + " but must be at least 1");
}
NSMutableArray<NSArray<T>> batchedArray = new NSMutableArray<>();
int count = array.size();
for (int i = 0; i < count; i += batchSize) {
int length = batchSize;
if (i + length > count) {
length = count - i;
}
batchedArray.add(array.subarrayWithRange(new NSRange(i, length)));
}
return batchedArray;
}
/**
* Filters a given array with a named fetch specification and bindings.
*
* @param array array to be filtered.
* @param fetchSpec name of the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation}.
* @param entity name of the {@link com.webobjects.eoaccess.EOEntity EOEntity}
* to which the fetch specification is associated.
* @param bindings bindings dictionary for qualifier variable substitution.
* @return array filtered and sorted by the named fetch specification.
*/
public static <T> NSArray<T> filteredArrayWithEntityFetchSpecification(NSArray<T> array, String entity, String fetchSpec, NSDictionary<String, ?> bindings) {
EOEntity wrongParamEntity = EOModelGroup.defaultGroup().entityNamed(fetchSpec);
if (wrongParamEntity != null) {
fetchSpec = entity;
entity = wrongParamEntity.name();
log.error("filteredArrayWithEntityFetchSpecification Calling conventions have changed from fetchSpec, entity to entity, fetchSpec");
}
EOFetchSpecification spec = EOFetchSpecification.fetchSpecificationNamed(fetchSpec, entity);
NSArray<EOSortOrdering> sortOrderings;
NSArray<T> result;
EOQualifier qualifier;
if (bindings != null) {
spec = spec.fetchSpecificationWithQualifierBindings(bindings);
}
result = new NSArray<>(array);
if ((qualifier = spec.qualifier()) != null) {
result = EOQualifier.filteredArrayWithQualifier(result, qualifier);
}
if ((sortOrderings = spec.sortOrderings()) != null) {
result = EOSortOrdering.sortedArrayUsingKeyOrderArray(result,sortOrderings);
}
return result;
}
/**
* Filters a given array with a named fetch specification.
*
* @param array array to be filtered.
* @param fetchSpec name of the {@link com.webobjects.eocontrol.EOQualifierEvaluation EOQualifierEvaluation}.
* @param entity name of the {@link com.webobjects.eoaccess.EOEntity EOEntity}
* to which the fetch specification is associated.
* @return array filtered and sorted by the named fetch specification.
*/
public static <T> NSArray<T> filteredArrayWithEntityFetchSpecification(NSArray<T> array, String entity, String fetchSpec) {
return filteredArrayWithEntityFetchSpecification(array, entity, fetchSpec, null);
}
/**
* Shifts a given object in an array one value to the left (index--).
*
* @param <T> class of array items
* @param array array to be modified.
* @param object the object that should be moved
*/
public static <T> void shiftObjectLeft(NSMutableArray<T> array, T object) {
int index = array.indexOfObject(object);
if (index == -1) return;
if (index > 0) {
array.insertObjectAtIndex(object, index -1);
array.removeObjectAtIndex(index + 1);
}
}
/**
* Shifts a given object in an array one value to the right (index++).
*
* @param <T> class of array items
* @param array array to be modified
* @param object the object that should be moved
*/
public static <T> void shiftObjectRight(NSMutableArray<T> array, T object) {
int index = array.indexOfObject(object);
if (index == -1) return;
if (index < array.count() - 1) {
array.insertObjectAtIndex(object, index + 2);
array.removeObjectAtIndex(index);
}
}
/**
* Function to determine if an array contains any of
* the elements of another array.
*
* @param <T> class of array items
* @param array1 to test if it contains any of the objects
* @param array2 array of objects to test if the first array
* contains any of
* @return if the first array contains any elements from the second
* array
*/
public static <T> boolean arrayContainsAnyObjectFromArray(Collection<? extends T> array1, Collection<? extends T> array2) {
if (array1 != null && array2 != null && !array1.isEmpty() && !array2.isEmpty()) {
Collection<? extends T> smaller, larger;
if (array1.size() > array2.size()) {
smaller = array2;
larger = array1;
} else {
smaller = array1;
larger = array2;
}
for (Object object : smaller) {
if (larger.contains(object)) {
return true;
}
}
}
return false;
}
/**
* Function to determine if an array contains all of
* the elements of another array.
*
* @param <T> class of array items
* @param array1 to test if it contains all of the objects of another array
* @param array2 array of objects to test if the first array
* contains all of
* @return if the first array contains all of the elements from the second
* array
*/
public static <T> boolean arrayContainsArray(Collection<? extends T> array1, Collection<? extends T> array2) {
if (array1 == null || array1.isEmpty()) {
return false;
}
if (array2 != null && !array2.isEmpty()) {
for (Object object : array2) {
if (!array1.contains(object)) {
return false;
}
}
}
return true;
}
/**
* Intersects the elements of two arrays. This has the effect of
* stripping out duplicates.
*
* @param <T> class of array items
* @param array1 the first array
* @param array2 the second array
* @return the intersecting elements
*/
public static <T> NSArray<T> intersectingElements(Collection<? extends T> array1, Collection<? extends T> array2) {
if (array1 == null || array1.isEmpty() || array2 == null || array2.isEmpty()) {
return NSArray.emptyArray();
}
Collection<? extends T> smaller, larger;
if (array1.size() > array2.size()) {
smaller = array2;
larger = array1;
} else {
smaller = array1;
larger = array2;
}
Set<T> set = new HashSet<>(smaller);
NSMutableArray<T> intersectingElements = new NSMutableArray<>();
for (T object : larger) {
if (set.contains(object)) {
intersectingElements.add(object);
set.remove(object);
}
}
return !intersectingElements.isEmpty() ? intersectingElements : NSArray.emptyArray();
}
/**
* Reverses the elements of an array.
*
* @param <T> class of array items
* @param array to be reversed
* @return reverse ordered array
*/
public static <T> NSArray<T> reverse(List<T> array) {
if (array == null || array.isEmpty()) {
return NSArray.emptyArray();
}
List<T> reverse = new ArrayList<>(array);
Collections.reverse(reverse);
return new NSArray<>(reverse);
}
/**
* Displays a list of attributes off of
* objects in a 'friendly' manner.
* <p>
* For example, given an array containing three user
* objects and the attribute key "firstName", the
* result of calling this method would be the string:
* "Max, Anjo and Patrice".
* @param list of objects to be displayed in a friendly
* manner
* @param attribute key to be called on each object in
* the list
* @param nullArrayDisplay string to be returned if the
* list is null or empty
* @param separator string to be used for the first items
* @param finalSeparator used between the last items
* @return friendly display string
*/
@SuppressWarnings("null")
public static String friendlyDisplayForKeyPath(NSArray<?> list, String attribute, String nullArrayDisplay, String separator, String finalSeparator) {
Object result = null;
int count = list!=null ? list.count() : 0;
if (count==0) {
result=nullArrayDisplay;
} else if (count == 1) {
result= (attribute!= null ? NSKeyValueCodingAdditions.Utility.valueForKeyPath(list.objectAtIndex(0), attribute) : list.objectAtIndex(0));
} else if (count > 1) {
StringBuilder buffer = new StringBuilder();
for(int i = 0; i < count; i++) {
Object attributeValue = (attribute!= null ? NSKeyValueCodingAdditions.Utility.valueForKeyPath(list.objectAtIndex(i), attribute) : list.objectAtIndex(i));
if (i>0) buffer.append(i == (count - 1) ? finalSeparator : separator);
buffer.append(attributeValue);
}
result=buffer.toString();
}
return (result == null ? null : result.toString());
}
/**
* Returns an array of dictionaries containing the key/value pairs for the given paths.
* @param array array of objects
* @param keys array of keys
* @return array of dictionaries containing values for the key paths
*/
public static NSArray<?> arrayForKeysPath(NSArray<?> array, NSArray<String> keys) {
NSMutableArray<Object> result=new NSMutableArray<>();
if (array != null && keys != null) {
for (Enumeration<?> e = array.objectEnumerator(); e.hasMoreElements();) {
Object object = e.nextElement();
result.addObject(ERXDictionaryUtilities.dictionaryFromObjectWithKeys(object, keys));
}
}
return result.immutableClone();
}
/** Removes all occurrences of NSKeyValueCoding.NullValue in the provided array
* @param array the array from which the NullValue should be removed
* @return a new NSArray with the same order than the original array but
* without NSKeyValueCoding.NullValue objects
*/
public static <T> NSArray<T> removeNullValues(NSArray<T> array) {
return removeNullValues(array, array);
}
/** Removes all occurrences of NSKeyValueCoding.NullValue in the provided array
* @param target array to remove objects from
* @param array array of values
* @return a new NSArray with the same order than the original array but
* without NSKeyValueCoding.NullValue objects
*/
public static <T> NSArray<T> removeNullValues(NSArray<T> target, NSArray<T> array) {
if (target == null) return null;
if (array == null) return target;
NSMutableArray<T> result = new NSMutableArray<>();
int i = 0;
for (T object : array) {
if (!(object instanceof NSKeyValueCoding.Null)) {
result.add(target.objectAtIndex(i));
}
i++;
}
return result;
}
/** Converts an Object array to a String array by casting each element.
* This is analogous to <code>String[] myStringArray = (String[])myObjectArray;</code>
* except that it creates a clone of the array.
* @param o an Object array containing String elements
* @return a String array containing the same elements
*/
public static String[] objectArrayCastToStringArray(Object[] o) {
String[] s = new String[o.length];
for (int i = 0; i < o.length; i++) {
s[i] = (String)o[i];
}
return s;
}
/** pretty prints an Object array which is ugly when using toString
* @param o the object which one wants to print as a String
* @return the String which can be used in lets say
* <code>log.info("my array = "+ERXArrayUtilities.objectArrayToString(myArray));</code>
*/
public static String objectArrayToString(Object[] o) {
return new NSArray<Object>(o).toString();
}
/** pretty prints a two dimensional Object array which is ugly when using toString
* @param array the object which one wants to print as a String
* @return the String which can be used in lets say
* <code>log.info("my array = "+ERXArrayUtilities.objectArrayToString(myArray));</code>
*/
public static String objectArrayToString(Object[][] array) {
NSMutableArray<Object> result = new NSMutableArray<>();
for (Object[] oa : array) {
result.add(objectArrayToString(oa));
}
return result.toString();
}
/** pretty prints a NSArray of two dimensional Object array which is ugly when using toString
* @param array the object which one wants to print as a String
* @return the String which can be used in lets say
* <code>log.info("my array = "+ERXArrayUtilities.objectArrayToString(myArray));</code>
*/
public static String objectArraysToString(NSArray<Object[][]> array) {
NSMutableArray<Object> aa = new NSMutableArray<>();
for (Object[][] oa : array) {
aa.add(objectArrayToString(oa));
}
return aa.toString();
}
/** removes all occurrences of NSKeyValueCoding.Null from the end of the array
* @param array the array from which the values should be removed
* @return a new NSArray which does not have NSKeyValueCoding.Null instances at the end
*/
public static <T> NSArray<T> removeNullValuesFromEnd(NSArray<T> array) {
if (array == null) return null;
NSMutableArray<T> a = array.mutableClone();
while (a.lastObject() instanceof NSKeyValueCoding.Null) {
a.removeLastObject();
}
return a;
}
public static String[] toStringArray(List<?> array) {
int size = array.size();
String[] b = new String[size];
for (int i = size; i > 0; i--) {
b[i - 1] = array.get(i - 1).toString();
}
return b;
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, the new object will replace the previous one in the dictionary.
* <p>
* This is a typesafe variant of dictionaryOfObjectsIndexedByKeyPath(NSArray<V> objects, String keyPath).
* <p>
* Calls <code>dictionaryOfObjectsIndexedByKeyPathThrowOnCollision()</code> passing <code>false</code> for throwOnCollision.
*
* @param <K> class of key path value
* @param <T> class of array items
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
*/
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPath(NSArray<T> array, ERXKey<K> keyPath) {
return dictionaryOfObjectsIndexedByKeyPath(array, keyPath, false);
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, the new object will replace the previous one in the dictionary.
* <p>
* Calls <code>dictionaryOfObjectsIndexedByKeyPathThrowOnCollision()</code> passing <code>false</code> for throwOnCollision.
*
* @param <K> class of key path value
* @param <T> class of array items
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
*/
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPath(NSArray<T> array, String keyPath) {
return dictionaryOfObjectsIndexedByKeyPath(array, keyPath, false);
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, if throwOnCollision is true, an exception is thrown, otherwise, the
* the new object will replace the previous one in the dictionary.
* <p>
* This is a typesafe variant of dictionaryOfObjectsIndexedByKeyPath(NSArray<V> objects, String keyPath, boolean throwOnCollision).
*
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @param throwOnCollision if <code>true</code> and two objects in the array have the same non-null (or non-NullValue) value for keyPath,
* an exception is thrown. If <code>false</code>, the last object in the array wins.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
* @deprecated use {@link #dictionaryOfObjectsIndexedByKeyPath(NSArray, ERXKey, boolean)} instead
*/
@Deprecated
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPathThrowOnCollision(final NSArray<T> array, final ERXKey<K> keyPath, final boolean throwOnCollision) {
return dictionaryOfObjectsIndexedByKeyPath(array, keyPath, throwOnCollision);
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, if throwOnCollision is true, an exception is thrown, otherwise, the
* the new object will replace the previous one in the dictionary.
*
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @param throwOnCollision if <code>true</code> and two objects in the array have the same non-null (or non-NullValue) value for keyPath,
* an exception is thrown. If <code>false</code>, the last object in the array wins.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
* @deprecated use {@link #dictionaryOfObjectsIndexedByKeyPath(NSArray, String, boolean)} instead
*/
@Deprecated
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPathThrowOnCollision(final NSArray<T> array, final String keyPath, final boolean throwOnCollision) {
return dictionaryOfObjectsIndexedByKeyPath(array, keyPath, throwOnCollision);
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, if throwOnCollision is true, an exception is thrown, otherwise, the
* the new object will replace the previous one in the dictionary.
* <p>
* This is a typesafe variant of dictionaryOfObjectsIndexedByKeyPath(NSArray<V> objects, String keyPath, boolean throwOnCollision).
*
* @param <K> class of key path value
* @param <T> class of array items
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @param throwOnCollision if <code>true</code> and two objects in the array have the same non-null (or non-NullValue) value for keyPath,
* an exception is thrown. If <code>false</code>, the last object in the array wins.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
*/
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPath(NSArray<T> array, ERXKey<K> keyPath, boolean throwOnCollision) {
return dictionaryOfObjectsIndexedByKeyPath(array, (keyPath == null) ? null : keyPath.key(), throwOnCollision);
}
/**
* Given an array of objects, returns a dictionary mapping the value by performing valueForKeyPath on each object in
* the array to the object in the array. This method assume that the value returned for the keyPath attribute will be unique for
* all the objects in the array. In case of duplicate entry, if throwOnCollision is true, an exception is thrown, otherwise, the
* the new object will replace the previous one in the dictionary.
*
* @param <K> class of key path value
* @param <T> class of array items
* @param array array to index
* @param keyPath keyPath to index. If any object returns <code>null</code> or NSKeyValueCoding.NullValue for this keyPath, the
* object is not put into the resulting dictionary.
* @param throwOnCollision if <code>true</code> and two objects in the array have the same non-null (or non-NullValue) value for keyPath,
* an exception is thrown. If <code>false</code>, the last object in the array wins.
* @return a dictionary indexing the given array. If array is <code>null</code>, an empty dictionary is returned.
*/
public static <K, T> NSDictionary<K, T> dictionaryOfObjectsIndexedByKeyPath(NSArray<T> array, String keyPath, boolean throwOnCollision) {
if (array == null || array.isEmpty()) {
return NSDictionary.emptyDictionary();
}
NSMutableDictionary<K, T> result = new NSMutableDictionary<>(array.size());
for (T object : array) {
K key = (K) NSKeyValueCodingAdditions.Utility.valueForKeyPath(object, keyPath);
if (key != null && key != NSKeyValueCoding.NullValue) {
if (throwOnCollision && result.containsKey(key)) {
throw new RuntimeException("Collision with value ('" + key + "') for keyPath '" + keyPath + "'. Initial object: '" +
result.get(key) + ", subsequent object: " + object);
}
result.put(key, object);
}
}
return result.immutableClone();
}
/**
* Prunes an array for only instances of the given class.
*
* @param <T> class to extract
* @param array array to process
* @param aClass class to use. null results in the result being a copy of the <code>array</code>.
* @return an array which is a subset of the <code>array</code> where each object in the result is
* an instance of <code>aClass</code>.
*/
public static <T> NSArray<T> arrayBySelectingInstancesOfClass(Collection<?> array, Class<T> aClass) {
if (array == null || array.isEmpty()) {
return NSArray.emptyArray();
}
if (aClass == null) {
return new NSArray<>((Collection) array);
}
NSMutableArray<T> result = new NSMutableArray<>();
for (Object object : array) {
if (aClass.isInstance(object)) {
result.add((T)object);
}
}
return result.isEmpty() ? NSArray.emptyArray() : result.immutableClone();
}
/**
* Just like the method {@link com.webobjects.foundation.NSArray#sortedArrayUsingComparator(NSComparator)},
* except it catches the NSComparator.ComparisonException and, if thrown,
* it wraps it in a runtime exception. Returns null when passed null for array.
*
* @param <T> class of array items
* @param array the array to sort
* @param comparator the comparator
* @return the sorted array
*/
public static <T> NSArray<T> sortedArrayUsingComparator(NSArray<T> array, NSComparator comparator) {
if (array == null || array.size() < 2) {
return array;
}
NSArray<T> result = array;
try {
result = array.sortedArrayUsingComparator(comparator);
}
catch (NSComparator.ComparisonException e) {
throw new RuntimeException(e);
}
return result;
}
/**
* Swaps the two given {@link Object}s in the given {@link NSArray} and
* returns a new {@link NSArray}. If one of the {@link Object}s is not
* element of the {@link NSArray} a {@link RuntimeException} will be thrown.
*
* @author edgar - Jan 7, 2008
* @param <T>
* @param array
* in that the two given {@link Object}s have to be swapped
* @param object1
* one object in the {@link NSArray} that will be swapped
* @param object2
* the other object in the {@link NSArray} that will be swapped
*
* @return the new {@link NSArray} with the swapped elements
*
* @throws RuntimeException
* if one of the {@link Object}s is not in the {@link NSArray}
* @deprecated use {@link #swapObjects(NSArray, Object, Object)} instead
*/
@Deprecated
public static <T> NSArray<T> arrayWithObjectsSwapped(final NSArray<T> array, final Object object1, final Object object2) {
int indexOfObject1 = array.indexOf(object1);
int indexOfObject2 = array.indexOf(object2);
if (indexOfObject1 >= 0 && indexOfObject2 >= 0) {
return arrayWithObjectsAtIndexesSwapped(array, indexOfObject1, indexOfObject2);
}
throw new RuntimeException("At least one of the given objects is not element of the array!");
}
/**
* Swaps the two objects at the given indexes in the given {@link NSArray} and
* returns a new {@link NSArray}.
*
* @author edgar - Jan 7, 2008
* @param array in that the two {@link Object}s at the given indexes have to be swapped
* @param indexOfObject1 index of one object in the {@link NSArray} that will be swapped
* @param indexOfObject2 index of the other object in the {@link NSArray} that will be swapped
*
* @return the new {@link NSArray} with the swapped elements
*
* @throws RuntimeException if one of the indexes is out of bound
* @deprecated use {@link #swapObjects(NSArray, int, int)} instead
*/
@Deprecated
public static <T> NSArray<T> arrayWithObjectsAtIndexesSwapped(final NSArray<T> array, final int indexOfObject1, final int indexOfObject2) {
if (array == null || array.count() < 2) {
throw new RuntimeException ("Array is either null or does not have enough elements.");
}
NSMutableArray<T> tmpArray = array.mutableClone();
try {
T tmpObject = array.objectAtIndex(indexOfObject1);
tmpArray.set(indexOfObject1, array.objectAtIndex(indexOfObject2));
tmpArray.set(indexOfObject2, tmpObject);
}
catch (Exception e) {
throw new RuntimeException(e);
}
return tmpArray.immutableClone();
}
/**
* Swaps two objects a and b in an array inplace
*
* @author cug - Jan 7, 2008
*
* @param array the array
* @param a - first object
* @param b - second object
*
* @throws RuntimeException if one or both indexes are out of bounds
* @deprecated use {@link #swapObjects(NSMutableArray, Object, Object)} instead
*/
@Deprecated
public static <T> void swapObjectsInArray (NSMutableArray<T> array, T a, T b) {
if (array == null || array.count() < 2) {
throw new RuntimeException ("Array is either null or does not have enough elements.");
}
int indexOfA = array.indexOf(a);
int indexOfB = array.indexOf(b);
if (indexOfA >= 0 && indexOfB >= 0) {
swapObjectsAtIndexesInArray(array, indexOfA, indexOfB);
}
else {
throw new RuntimeException ("At least one of the objects is not element of the array!");
}
}
/**
* Swaps two objects at the given indexes in an array inplace
*
* @author cug - Jan 7, 2008
*
* @param array the array
* @param indexOfA - index of the first object
* @param indexOfB - index of the second object
*
* @throws RuntimeException if one or both indexes are out of bounds
* @deprecated use {@link #swapObjects(NSMutableArray, int, int)} instead
*/
@Deprecated
public static <T> void swapObjectsAtIndexesInArray (NSMutableArray<T> array, int indexOfA, int indexOfB) {
try {
T tmp = array.replaceObjectAtIndex(array.objectAtIndex(indexOfA), indexOfB);
array.replaceObjectAtIndex(tmp, indexOfA);
}
catch (Exception e) {
throw new RuntimeException();
}
}
/**
* Swaps the object a with the object at the given index
*
* @author edgar - Apr 14, 2008
* @param array the array
* @param a - first object
* @param indexOfB - index of second object
* @deprecated use {@link #swapObjects(NSMutableArray, Object, int)} instead
*/
@Deprecated
public static <T> void swapObjectWithObjectAtIndexInArray(NSMutableArray<T> array, T a, int indexOfB) {
if (array == null || array.count() < 2) {
throw new RuntimeException ("Array is either null or does not have enough elements.");
}
int indexOfA = array.indexOf(a);
if (indexOfA >= 0 && indexOfB >= 0) {
if (indexOfA != indexOfB) {
swapObjectsAtIndexesInArray(array, indexOfA, indexOfB);
}
}
else {
throw new RuntimeException ("At least one of the objects is not element of the array!");
}
}
/**
* Swaps two objects at the given indexes in an array inplace.
*
* @param <T> class of array elements
* @param array an array
* @param indexA index of the first object
* @param indexB index of the second object
*/
public static <T> void swapObjects(NSMutableArray<T> array, int indexA, int indexB) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
if (array.isEmpty()) {
throw new IllegalArgumentException("array is empty");
}
int maxIndex = array.size() - 1;
if (indexA < 0 || indexA > maxIndex) {
throw new IllegalArgumentException("indexA = " + indexA + " is out of bounds [0.." + maxIndex + "]");
}
if (indexB < 0 || indexB > maxIndex) {
throw new IllegalArgumentException("indexB = " + indexB + " is out of bounds [0.." + maxIndex + "]");
}
if (indexA == indexB) {
// nothing to do
return;
}
try {
T tmp = array.replaceObjectAtIndex(array.objectAtIndex(indexA), indexB);
array.replaceObjectAtIndex(tmp, indexA);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Swaps the object a with the object at the given index in an array inplace.
*
* @param <T> class of array elements
* @param array an array
* @param a the first object
* @param indexB index of the second object
*/
public static <T> void swapObjects(NSMutableArray<T> array, T a, int indexB) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
int indexA = array.indexOf(a);
swapObjects(array, indexA, indexB);
}
/**
* Swaps the given objects in an array inplace.
*
* @param <T> class of array elements
* @param array an array
* @param a the first object
* @param b the second object
*/
public static <T> void swapObjects(NSMutableArray<T> array, T a, T b) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
int indexA = array.indexOf(a);
int indexB = array.indexOf(b);
swapObjects(array, indexA, indexB);
}
/**
* Swaps two objects at the given indexes in an array and returns a new
* modified array.
*
* @param <T> class of array elements
* @param array an array
* @param indexA index of the first object
* @param indexB index of the second object
* @return array with swapped objects
*/
public static <T> NSArray<T> swapObjects(NSArray<T> array, int indexA, int indexB) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
if (indexA == indexB && indexA >= 0) {
int maxIndex = array.size() - 1;
if (indexA <= maxIndex) {
// no swapping necessary
return array.immutableClone();
}
}
NSMutableArray<T> tmpArray = array.mutableClone();
swapObjects(tmpArray, indexA, indexB);
return tmpArray.immutableClone();
}
/**
* Swaps the object a with the object at the given index in an array and returns
* a new modified array.
*
* @param <T> class of array elements
* @param array an array
* @param a the first object
* @param indexB index of the second object
* @return array with swapped objects
*/
public static <T> NSArray<T> swapObjects(NSArray<T> array, T a, int indexB) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
int indexA = array.indexOf(a);
return swapObjects(array, indexA, indexB);
}
/**
* Swaps the objects in an array and returns a new modified array.
*
* @param <T> class of array elements
* @param array an array
* @param a the first object
* @param b the second object
* @return array with swapped objects
*/
public static <T> NSArray<T> swapObjects(NSArray<T> array, T a, T b) {
if (array == null) {
throw new IllegalArgumentException("array is null");
}
int indexA = array.indexOf(a);
int indexB = array.indexOf(b);
return swapObjects(array, indexA, indexB);
}
/**
* Returns a deep clone of the given array. A deep clone will attempt
* to clone the values of this array as well as the array itself.
*
* @param <T> class of array elements
* @param array the array to clone
* @param onlyCollections if true, only collections in this array will be cloned, not individual values
* @return a deep clone of array
*/
public static <T> NSArray<T> deepClone(NSArray<T> array, boolean onlyCollections) {
if (array == null) {
return null;
}
NSMutableArray<T> clonedArray = array.mutableClone();
for (int i = array.size() - 1; i >= 0; i--) {
T value = array.get(i);
T clonedValue = ERXUtilities.deepClone(value, onlyCollections);
if (clonedValue != null) {
if (clonedValue != value) {
clonedArray.set(i, clonedValue);
}
} else {
clonedArray.remove(i);
}
}
return clonedArray;
}
/**
* Returns a deep clone of the given set. A deep clone will attempt
* to clone the values of this set as well as the set itself.
*
* @param set the set to clone
* @param onlyCollections if true, only collections in this array will be cloned, not individual values
* @return a deep clone of set
* @deprecated user {@link ERXSetUtilities#deepClone(NSSet, boolean)} instead
*/
@Deprecated
public static <T> NSSet<T> deepClone(NSSet<T> set, boolean onlyCollections) {
NSMutableSet<T> clonedSet = null;
if (set != null) {
clonedSet = set.mutableClone();
for (T value : set) {
T clonedValue = ERXUtilities.deepClone(value, onlyCollections);
if (clonedValue != null) {
if (clonedValue != value) {
clonedSet.removeObject(value);
clonedSet.addObject(clonedValue);
}
}
else {
clonedSet.removeObject(value);
}
}
}
return clonedSet;
}
/**
* <div class="en">
* Check if an array is null or empty.
* </div>
*
* <div class="ja">
* 配列が null か空かをチェックします
* </div>
*
* @param array <div class="en">an array</div>
* <div class="ja">文字列配列</div>
* @return <div class="en">true if array is either null or empty</div>
* <div class="ja">配列が null か空の場合は <code>true</code> が戻ります</div>
*/
public static boolean arrayIsNullOrEmpty(Collection<?> array) {
return array == null || array.isEmpty();
}
/**
* <div class="en">
* To create oneLine Log for an NSArray<String>
* </div>
*
* <div class="ja">
* NSArray 配列をログとして出力する時に複数行に渡らないで、一行で収まるように
* </div>
*
* @param array <div class="en">an array</div>
* <div class="ja">文字列配列</div>
* @return <div class="en">change a NSArray to String</div>
* <div class="ja">NSArray を String に変換した行</div>
*/
public static String arrayToLogstring(Collection<String> array) {
if (array == null) {
return "()";
}
StringBuilder result = new StringBuilder();
result.append("( ");
for (String obj : array) {
result.append(obj);
result.append(", ");
}
result.setLength(result.length() - 2);
result.append(" )");
return result.toString();
}
}