From 2ab2242f6034e6b986b9c9de7e4d4d5876cf345f Mon Sep 17 00:00:00 2001 From: David Avendasora Date: Fri, 7 Feb 2014 12:30:33 -0500 Subject: [PATCH 1/4] Add additional optional ERXKey Types to be able to easily tell if a Key represents a Operator (@sum, @flatten, etc.) or a non-model "attribute" or "relationship". --- .../Sources/er/extensions/eof/ERXKey.java | 35 ++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java index cf890ba1d47..a02e0925f71 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java @@ -1505,7 +1505,7 @@ public ERXKey atCount() { * @author mschrag */ public static enum Type { - Attribute, ToOneRelationship, ToManyRelationship + Attribute, ToOneRelationship, ToManyRelationship, Operator, NonModelAttribute, NonModelToOneRelationshiop, NonModelToManyRelationship } public interface ValueCoding { @@ -1515,6 +1515,7 @@ public interface ValueCoding { } private String _key; + private Type _type; /** * Constructs an ERXKey. @@ -1526,6 +1527,18 @@ public ERXKey(String key) { _key = key; } + /** + * Constructs an ERXKey. + * + * @param key + * the underlying keypath + * @param type + */ + public ERXKey(String key, Type type) { + _key = key; + setType(type); + } + /** * Constructs a localized ERXKey. * @@ -2643,4 +2656,24 @@ public ERXSortOrdering dot(EOSortOrdering sortOrdering) { public ERXSortOrderings dot(NSArray sortOrderings) { return prefix(sortOrderings); } + + public Type type() { + return _type; + } + + public void setType(Type type) { + _type = type; + } + + public boolean isAttribute() { + return type() == ERXKey.Type.Attribute || type() == ERXKey.Type.NonModelAttribute; + } + + public boolean isToOneRelationship() { + return type() == ERXKey.Type.ToOneRelationship || type() == ERXKey.Type.NonModelToOneRelationshiop; + } + + public boolean isToManyRelationship() { + return type() == ERXKey.Type.ToManyRelationship || type() == ERXKey.Type.NonModelToManyRelationship; + } } From 40626ec45cd5865560806e0187457415363a41cf Mon Sep 17 00:00:00 2001 From: David Avendasora Date: Fri, 7 Feb 2014 13:13:13 -0500 Subject: [PATCH 2/4] Add valueForKey(ERXKey) to NSArray that returns an NSArray, with options for automatic flattening, distincting, and automatic null-value removal. Greatly simplifies making type-safe calls on to-many relationships and any other NSArray. --- .../com/webobjects/foundation/NSArray.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) diff --git a/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java b/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java index ab0cdd38f1f..7527f972f32 100644 --- a/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java +++ b/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java @@ -15,6 +15,9 @@ import java.util.ListIterator; import java.util.Vector; +import er.extensions.eof.ERXKey; +import er.extensions.foundation.ERXArrayUtilities; + /** * * NSArray re-implementation to support JDK 1.5 templates. Use with @@ -1075,4 +1078,95 @@ public static final NSArray emptyArray() { throw NSForwardException._runtimeExceptionForThrowable(e); } } + + /** + * A type-safe wrapper for {@link #valueForKeyPath(String)} that simply + * calls {@code valueForKeyPath(erxKey.key())} and attempts to cast the + * result to {@code NSArray}. If the value returned cannot be cast it + * will throw a {@link ClassCastException}. + * + * @param + * the Type of elements in the returned {@code NSArray} + * @param erxKey + * @return an {@code NSArray} of {@code T} objects. + * @author David Avendasora + */ + public NSArray valueForKeyPath(ERXKey erxKey) { + return (NSArray) valueForKeyPath(erxKey.key()); + } + + /** + * A type-safe wrapper for {@link #valueForKey(String)} that calls + * {@code valueForKey(erxKey, true, true, true)} + * + *

+ * This method will automatically + * {@link ERXArrayUtilities#removeNullValues(NSArray) remove} + * {@code NSKeyValueCoding.Null} elements, + * {@link ERXArrayUtilities#flatten(NSArray) flatten} all elements that are + * arrays and {@link ERXArrayUtilities#distinct(NSArray) remove} all + * duplicate objects. + *

+ * + * @param + * the Type of elements in the returned {@code NSArray} + * @param erxKey + * @return an {@code NSArray} of {@code T} objects. + * @author David Avendasora + */ + public NSArray valueForKey(ERXKey erxKey) { + return valueForKey(erxKey, true, true, true); + } + + /** + *

+ * A type-safe wrapper for {@link #valueForKeyPath(String)} that calls + * {@code valueForKeyPath(erxKey.key())} and attempts to cast the result to + * {@code NSArray}. + *

+ *

+ * Then, depending upon the parameters, removes + * {@link NSKeyValueCoding.Null} elements, flattens any {@link NSArray} + * elements and then filters out duplicate values. + *

+ *

+ * If the value cannot be cast it will throw a {@link ClassCastException} + * . + *

+ * + * @param + * the Type of elements in the returned {@code NSArray} + * @param erxKey + * @param removeNulls + * if {@code true} all {@link NSKeyValueCoding.Null} elements + * will be {@link ERXArrayUtilities#removeNullValues(NSArray) + * removed} + * @param distinct + * if {@code true} all duplicate elements will be + * {@link ERXArrayUtilities#distinct(NSArray) removed} + * @param flatten + * if {@code true} all {@link NSArray} elements will be + * {@link ERXArrayUtilities#flatten(NSArray) flattened} + * @return an {@code NSArray} of {@code T} objects. + * @author David Avendasora + */ + public NSArray valueForKey(ERXKey erxKey, boolean removeNulls, boolean distinct, boolean flatten) { + if (erxKey.type() == ERXKey.Type.Operator) { + final String message = "You cannot use an Opperator (@sum, @max, etc.) ERXKey with valueForKey(ERXKey) " + + "because the value returned by valueForKey(opperator) cannot be cast to NSArray. " + + "Call valueForKey(myERXKey.key()) instead."; + throw new IllegalArgumentException(message); + } + NSArray values = (NSArray) valueForKeyPath(erxKey.key()); + if (removeNulls) { + values = ERXArrayUtilities.removeNullValues(values); + } + if (flatten && erxKey.isToManyRelationship()) { + values = ERXArrayUtilities.flatten(values); + } + if (distinct) { + values = ERXArrayUtilities.distinct(values); + } + return values; + } } From 18acc0429c055a5d28f31a6aea8f61eec4a14fa2 Mon Sep 17 00:00:00 2001 From: David Avendasora Date: Fri, 7 Feb 2014 13:37:41 -0500 Subject: [PATCH 3/4] Fix typo before Marco (and everyone else) gives me loads of crap about it. --- .../Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java index a02e0925f71..af045731536 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java @@ -1505,7 +1505,7 @@ public ERXKey atCount() { * @author mschrag */ public static enum Type { - Attribute, ToOneRelationship, ToManyRelationship, Operator, NonModelAttribute, NonModelToOneRelationshiop, NonModelToManyRelationship + Attribute, ToOneRelationship, ToManyRelationship, Operator, NonModelAttribute, NonModelToOneRelationship, NonModelToManyRelationship } public interface ValueCoding { @@ -2670,7 +2670,7 @@ public boolean isAttribute() { } public boolean isToOneRelationship() { - return type() == ERXKey.Type.ToOneRelationship || type() == ERXKey.Type.NonModelToOneRelationshiop; + return type() == ERXKey.Type.ToOneRelationship || type() == ERXKey.Type.NonModelToOneRelationship; } public boolean isToManyRelationship() { From 446736041b7b6b667f143371d636a91953cf7c19 Mon Sep 17 00:00:00 2001 From: David Avendasora Date: Thu, 20 Feb 2014 13:08:52 -0500 Subject: [PATCH 4/4] Improve Javadoc, add ERXKey(String, String, Type) New constructor allows setting both the localization and the Type. I also added "todo" notes regarding evaluating how the new ERXKey.Types work with ERXKeyFilter, specifically in ERRest. --- .../com/webobjects/foundation/NSArray.java | 44 ++-- .../Sources/er/extensions/eof/ERXKey.java | 232 +++++++++++++++++- 2 files changed, 257 insertions(+), 19 deletions(-) diff --git a/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java b/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java index 7527f972f32..3d835fe5116 100644 --- a/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java +++ b/Frameworks/Core/ERExtensions/Sources/com/webobjects/foundation/NSArray.java @@ -1096,22 +1096,28 @@ public NSArray valueForKeyPath(ERXKey erxKey) { } /** - * A type-safe wrapper for {@link #valueForKey(String)} that calls - * {@code valueForKey(erxKey, true, true, true)} - * *

- * This method will automatically - * {@link ERXArrayUtilities#removeNullValues(NSArray) remove} - * {@code NSKeyValueCoding.Null} elements, - * {@link ERXArrayUtilities#flatten(NSArray) flatten} all elements that are - * arrays and {@link ERXArrayUtilities#distinct(NSArray) remove} all - * duplicate objects. + * A type-safe wrapper for {@link #valueForKey(String)} that automatically + * does the following (in order) to the resulting array prior to returning + * it: + *

    + *
  1. {@link ERXArrayUtilities#removeNullValues(NSArray) remove} + * {@code NSKeyValueCoding.Null} elements
  2. + *
  3. {@link ERXArrayUtilities#flatten(NSArray) flatten} all elements that + * are arrays (Only if {@link ERXKey#isToManyRelationship()} + * returns true, which can only possibly happen if + * {@link ERXKey#type()} has been set.)
  4. + *
  5. {@link ERXArrayUtilities#distinct(NSArray) remove} all duplicate + * objects
  6. + *
*

* * @param * the Type of elements in the returned {@code NSArray} * @param erxKey + * * @return an {@code NSArray} of {@code T} objects. + * * @author David Avendasora */ public NSArray valueForKey(ERXKey erxKey) { @@ -1125,9 +1131,17 @@ public NSArray valueForKey(ERXKey erxKey) { * {@code NSArray}. *

*

- * Then, depending upon the parameters, removes - * {@link NSKeyValueCoding.Null} elements, flattens any {@link NSArray} - * elements and then filters out duplicate values. + * Then, depending upon the parameters, + *

    + *
  1. {@link ERXArrayUtilities#removeNullValues(NSArray) remove} + * {@code NSKeyValueCoding.Null} elements
  2. + *
  3. {@link ERXArrayUtilities#flatten(NSArray) flatten} all elements that + * are arrays (Only if {@link ERXKey#isToManyRelationship()} + * returns true, which can only possibly happen if + * {@link ERXKey#type()} has been set.)
  4. + *
  5. {@link ERXArrayUtilities#distinct(NSArray) remove} all duplicate + * objects
  6. + *
*

*

* If the value cannot be cast it will throw a {@link ClassCastException} @@ -1147,14 +1161,16 @@ public NSArray valueForKey(ERXKey erxKey) { * @param flatten * if {@code true} all {@link NSArray} elements will be * {@link ERXArrayUtilities#flatten(NSArray) flattened} + * * @return an {@code NSArray} of {@code T} objects. + * * @author David Avendasora */ public NSArray valueForKey(ERXKey erxKey, boolean removeNulls, boolean distinct, boolean flatten) { if (erxKey.type() == ERXKey.Type.Operator) { - final String message = "You cannot use an Opperator (@sum, @max, etc.) ERXKey with valueForKey(ERXKey) " + final String message = "You cannot use an Operator (@sum, @max, etc.) ERXKey with valueForKey(ERXKey) " + "because the value returned by valueForKey(opperator) cannot be cast to NSArray. " - + "Call valueForKey(myERXKey.key()) instead."; + + "Call valueForKey(MY_OPPERATOR_ERXKEY.key()) instead."; throw new IllegalArgumentException(message); } NSArray values = (NSArray) valueForKeyPath(erxKey.key()); diff --git a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java index af045731536..06482aae231 100644 --- a/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java +++ b/Frameworks/Core/ERExtensions/Sources/er/extensions/eof/ERXKey.java @@ -3,6 +3,10 @@ import java.math.BigDecimal; import java.util.Locale; +import com.webobjects.eoaccess.EOAttribute; +import com.webobjects.eoaccess.EOModel; +import com.webobjects.eoaccess.EORelationship; +import com.webobjects.eocontrol.EOEnterpriseObject; import com.webobjects.eocontrol.EOQualifier; import com.webobjects.eocontrol.EOSortOrdering; import com.webobjects.foundation.NSArray; @@ -13,6 +17,7 @@ import er.extensions.eof.ERXSortOrdering.ERXSortOrderings; import er.extensions.eof.qualifiers.ERXExistsQualifier; +import er.extensions.foundation.ERXArrayUtilities; import er.extensions.qualifiers.ERXAndQualifier; import er.extensions.qualifiers.ERXKeyComparisonQualifier; import er.extensions.qualifiers.ERXKeyValueQualifier; @@ -1500,12 +1505,123 @@ public ERXKey atCount() { } /** - * Enums to desribe the type of key this represents. + * Enums to describe the type of key this represents. * * @author mschrag */ public static enum Type { - Attribute, ToOneRelationship, ToManyRelationship, Operator, NonModelAttribute, NonModelToOneRelationship, NonModelToManyRelationship + /** + *

+ * Indicates that this key represents an {@link EOAttribute} defined in + * the {@link EOModel}. Since it is defined in the model it can be used + * when instantiating Objects that impact SQL generation, e.g., + * {@link EOQualifier} and {@link EOSortOrdering}. + *

+ */ + Attribute, + /** + *

+ * Indicates that this key represents an {@link EORelationship} defined + * in the {@link EOModel} that will return false for + * {@link EORelationship#isToMany()}. Since it is defined in the model + * it can be used when instantiating Objects that impact SQL generation, + * e.g., {@link EOQualifier} and {@link EOSortOrdering}. + *

+ */ + ToOneRelationship, + /** + *

+ * Indicates that this key represents an {@link EORelationship} defined + * in the {@link EOModel} that will return true for + * {@link EORelationship#isToMany()}. Since it is defined in the model + * it can be used when instantiating Objects that impact SQL generation, + * e.g., {@link EOQualifier} and {@link EOSortOrdering}. + *

+ */ + ToManyRelationship, + /** + *

+ * Indicates that this key represents an {@link NSArray.Operator}, e.g., + * {@link NSArray._SumNumberOperator @sum}, + * {@link ERXArrayUtilities.FlattenOperator @flatten}, + * {@link ERXArrayUtilities.FlattenOperator @fetchSpec}, + * {@link NSArray._MinOperator @min} + *

+ *

+ * Note: this Type is not recognized by the + * {@link ERXKeyFilter#matches(ERXKey, Type)} and will not be included + * in {@link ERXKeyFilter#includeAll()}, + * {@link ERXKeyFilter#includeAttributes()} nor + * {@link ERXKeyFilter#includeAttributesAndToOneRelationships()} because + * {@link ERXKeyFilter} can only be used with {@link ERXKey}s that + * represent a single key {@link NSArray.Operator}s represent a keypath. + *

+ */ + Operator, + /** + *

+ * Indicates that this key represents a visible method or ivar that + * returns an object of type T, but does not have a corresponding + * relationship entry in the {@link EOModel} and therefore cannot be + * used to instantiate objects that will impact SQL generation. e.g., + * {@link EOQualifier} and {@link EOSortOrdering}. + *

+ *

+ * Note: this ERXKey.Type is not recognized by the + * {@link ERXKeyFilter#matches(ERXKey, Type)} and will not be included + * in {@link ERXKeyFilter#includeAll()}, + * {@link ERXKeyFilter#includeAttributes()} nor + * {@link ERXKeyFilter#includeAttributesAndToOneRelationships()}. + *

+ *

+ * TODO: Additional work needs to be done to validate that ERRest's use + * of ERXKeyFilter is compatible with this ERXKey.Type. + *

+ */ + NonModelAttribute, + /** + *

+ * Indicates that this key represents a visible instance member that + * returns an instance of {@link EOEnterpriseObject} of type T, but does + * not have a corresponding relationship entry in the {@link EOModel} + * and therefore cannot be used to instantiate objects that will impact + * SQL generation. e.g., {@link EOQualifier} and {@link EOSortOrdering}. + *

+ *

+ * Note: this ERXKey.Type is not recognized by the + * {@link ERXKeyFilter#matches(ERXKey, Type)} and will not be included + * in {@link ERXKeyFilter#includeAll()}, + * {@link ERXKeyFilter#includeAttributes()} nor + * {@link ERXKeyFilter#includeAttributesAndToOneRelationships()}. + *

+ *

+ * TODO: Additional work needs to be done to validate that ERRest's use + * of ERXKeyFilter is compatible with this ERXKey.Type. + *

+ */ + NonModelToOneRelationship, + /** + *

+ * Indicates that this key represents a visible instance member that + * returns an array of {@link EOEnterpriseObject} instances of type T, + * but does not have a corresponding relationship entry in the + * {@link EOModel} and therefore cannot be used to instantiate objects + * that will impact SQL generation. e.g., {@link EOQualifier} and + * {@link EOSortOrdering}. + *

+ *

+ * Note: this ERXKey.Type is not recognized by the + * {@link ERXKeyFilter#matches(ERXKey, Type)} and will not be included + * in {@link ERXKeyFilter#includeAll()}, + * {@link ERXKeyFilter#includeAttributes()} nor + * {@link ERXKeyFilter#includeAttributesAndToOneRelationships()}. + *

+ *

+ * TODO: Additional work needs to be done to validate that ERRest's use + * of ERXKeyFilter is compatible with this ERXKey.Type. + *

+ */ + NonModelToManyRelationship } public interface ValueCoding { @@ -1528,15 +1644,29 @@ public ERXKey(String key) { } /** - * Constructs an ERXKey. + * Constructs an ERXKey, specifying what {@link Type} it is. + * + * You can have EOGenerator use this constructor by using the following code + * in the EOGenerator template that generates your _Entity.java files. + * Replace the existing code that creates the ERXKeys declarations for the + * Entity's attributes, to-one relationships and to-many relationships. + * + *
+	 *     public static final ERXKey<$attribute.javaClassName> ${attribute.uppercaseUnderscoreName} = new ERXKey<$attribute.javaClassName>("$attribute.name", ERXKey.Type.Attribute);
+	 * 
+	 *     public static final ERXKey<$relationship.actualDestination.classNameWithDefault> ${relationship.uppercaseUnderscoreName} = new ERXKey<$relationship.actualDestination.classNameWithDefault>("$relationship.name", ERXKey.Type.ToOneRelationship);
+	 * 
+	 *     public static final ERXKey<$relationship.actualDestination.classNameWithDefault> ${relationship.uppercaseUnderscoreName} = new ERXKey<$relationship.actualDestination.classNameWithDefault>("$relationship.name", ERXKey.Type.ToManyRelationship);
+	 * 
* * @param key - * the underlying keypath + * the underlying key or keypath * @param type + * the {@link Type} */ public ERXKey(String key, Type type) { _key = key; - setType(type); + _type = type; } /** @@ -1551,6 +1681,23 @@ public ERXKey(String key, String locale) { _key = key + "_" + locale; } + /** + * Constructs a localized ERXKey, specifying what {@link Type} it is. + * + * @param key + * the underlying keypath + * @param locale + * the locale for the key + * @param type + * the {@link Type} + * + * @see #ERXKey(String, Type) + */ + public ERXKey(String key, String locale, Type type) { + _key = key + "_" + locale; + _type = type; + } + /** * Equivalent to ERXS.asc(key()) * @@ -2657,6 +2804,12 @@ public ERXSortOrderings dot(NSArray sortOrderings) { return prefix(sortOrderings); } + /** + * See {@link #ERXKey(String, Type)} for information on how to automatically + * set this. + * + * @return the {@link Type}, if specified, for this key. + */ public Type type() { return _type; } @@ -2665,14 +2818,83 @@ public void setType(Type type) { _type = type; } + /** + * Checks this key's {@link Type} to determine if it represents an + * {@link EOAttribute}. + *

+ * Note: if {@link #type()} has not been set, then this will return + * false. To set it, you have the following options: + *

    + *
  • Set it manually using {@link #setType(Type)}
  • + *
  • Set it using the {@link #ERXKey(String, Type)} constructor. If this + * key was declared in code generated by EOGenerator (i.e. in a _Entity.java + * class), you will need to modify your EOGenerator template. See + * {@link #ERXKey(String, Type)} for details.
  • + *
+ *

+ * + * @return true if {@link #type()} returns either + * {@link ERXKey.Type.Attribute} or + * {@link ERXKey.Type.NonModelAttribute}, false + * otherwise. + * + * @see isToOneRelationship + * @see isToManyRelationship + */ public boolean isAttribute() { return type() == ERXKey.Type.Attribute || type() == ERXKey.Type.NonModelAttribute; } + /** + * Checks this key's {@link ERXKey.Type} to determine if it represents a + * to-one {@link EORelationship}. + *

+ * Note: if {@link #type()} has not been set, then this will return + * false. To set it, you have the following options: + *

    + *
  • Set it manually using {@link #setType(Type)}
  • + *
  • Set it using the {@link #ERXKey(String, Type)} constructor. If this + * key was declared in code generated by EOGenerator (i.e. in a _Entity.java + * class), you will need to modify your EOGenerator template. See + * {@link #ERXKey(String, Type)} for details.
  • + *
+ *

+ * + * @return true if {@link #type()} returns either + * {@link ERXKey.Type.ToOneRelationship} or + * {@link ERXKey.Type.NonModelToOneRelationship}, false + * otherwise. + * + * @see isAttribute + * @see isToManyRelationship + */ public boolean isToOneRelationship() { return type() == ERXKey.Type.ToOneRelationship || type() == ERXKey.Type.NonModelToOneRelationship; } + /** + * Checks this key's {@link ERXKey.Type} to determine if it represents a + * to-many {@link EORelationship}. + *

+ * Note: if {@link #type()} has not been set, then this will return + * false. To set it, you have the following options: + *

    + *
  • Set it manually using {@link #setType(Type)}
  • + *
  • Set it using the {@link #ERXKey(String, Type)} constructor. If this + * key was declared in code generated by EOGenerator (i.e. in a _Entity.java + * class), you will need to modify your EOGenerator template. See + * {@link #ERXKey(String, Type)} for details.
  • + *
+ *

+ * + * @return true if {@link #type()} returns either + * {@link ERXKey.Type.ToOneRelationship} or + * {@link ERXKey.Type.NonModelToOneRelationship}, false + * otherwise. + * + * @see isAttribute + * @see isToOneRelationship + */ public boolean isToManyRelationship() { return type() == ERXKey.Type.ToManyRelationship || type() == ERXKey.Type.NonModelToManyRelationship; }