Repository: jakartaee/data
Pull request #1091 (2025-05-02 20:18:52)

Javadoc for Expression
Covers item 2 from #1086 - adding JavaDoc for the Expression class before proceeding to do likewise for the subtypes.


Patch:
diff --git a/api/src/main/java/jakarta/data/expression/Expression.java b/api/src/main/java/jakarta/data/expression/Expression.java
index d4df0b7b..e116a83d 100644
--- a/api/src/main/java/jakarta/data/expression/Expression.java
+++ b/api/src/main/java/jakarta/data/expression/Expression.java
@@ -24,50 +24,269 @@ import jakarta.data.constraint.NotEqualTo;
 import jakarta.data.constraint.NotIn;
 import jakarta.data.constraint.NotNull;
 import jakarta.data.constraint.Null;
+import jakarta.data.metamodel.Attribute;
+import jakarta.data.metamodel.StaticMetamodel;
 import jakarta.data.restrict.BasicRestriction;
 import jakarta.data.restrict.Restriction;
 
 import java.util.Collection;
 
+/**
+ * <p>An expression represents an {@linkplain Attribute entity attribute},
+ * {@linkplain jakarta.data.expression.literal literal value}, or
+ * {@linkplain jakarta.data.expression.function function}.</p>
+ *
+ * <p>Expressions are used to compose {@linkplain Restriction restrictions}
+ * that allow applications to supply query criteria at runtime. For example,
+ * </p>
+ * <pre>
+ * List&lt;Car&gt; affordableVehicles =
+ *         cars.search(make,
+ *                     model,
+ *                     _Car.price.plus(fees).lessThan(25000));
+ * </pre>
+ *
+ * <p>The {@linkplain Attribute entity and static metamodel} for the code
+ * examples within this class are shown in the {@link Attribute} Javadoc.
+ * </p>
+ *
+ * <p>Expressions are immutable and do not change after they are created.</p>
+ *
+ * <h2>Attribute expressions</h2>
+ *
+ * <p>All subtypes of {@link Attribute} obtained from the
+ * {@linkplain StaticMetamodel static metamodel} are expressions.
+ * For example,</p>
+ * <pre>
+ * _Car.vin
+ * _Car.price
+ * </pre>
+ *
+ * <h2>Function expressions</h2>
+ *
+ * <p>Function expressions can be obtained from attribute expressions. For
+ * example,</p>
+ * <pre>
+ * _Car.price.minus(discount)
+ * _Car.model.upper()
+ * _Car.vin.length()
+ * </pre>
+ *
+ * <p>Function expressions can also be obtained from other function
+ * expressions. For example, the following expression represents the second
+ * through third digits of a Vehicle Id Number,</p>
+ * <pre>
+ * _Car.vin.left(3).right(2)
+ * </pre>
+ *
+ * <p>Some function expressions are available via static methods. For example,
+ * </p>
+ * <pre>
+ * CurrentDateTime.now()
+ * </pre>
+ *
+ * <h2>Literal expressions</h2>
+ *
+ * <p>Literal expressions are available via static methods. For example,
+ * </p>
+ * <pre>
+ * StringLiteral.of("Jakarta EE")
+ * NumericLiteral.of(12);
+ * </pre>
+ *
+ * <p>Literal expressions are also used indirectly when literal values are
+ * supplied to methods that create other expressions and
+ * {@linkplain Restriction restrictions}.</p>
+ *
+
+ *
+ * @param <T> entity type.
+ * @param <V> entity attribute type.
+ * @since 1.1
+ */
 public interface Expression<T, V> {
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is equal to the specified value.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * newCars = cars.search(make, model, _Car.year.equalTo(Year.now().getValue()));
+     * </pre>
+     *
+     * @param value value against which to compare. Must not be {@code null}.
+     * @return the restriction.
+     * @throws NullPointerException if the value is {@code null}.
+     */
     default Restriction<T> equalTo(V value) {
         return BasicRestriction.of(this, EqualTo.value(value));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * and the specified expression evaluate to values that are equal.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * listedToday = cars.search(make, model, _Car.listed.equalTo(CurrentDate.now()));
+     * </pre>
+     *
+     * @param expression expression against which to compare. Must not be
+     *                   {@code null}.
+     * @return the restriction.
+     * @throws NullPointerException if the expression is {@code null}.
+     */
     default Restriction<T> equalTo(Expression<? super T, V> expression) {
         return BasicRestriction.of(this, EqualTo.expression(expression));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is equal to one of the values within the
+     * specified collection.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.year.in(Set.of(2022, 2024)));
+     * </pre>
+     *
+     * @param values values against which to compare. Must not be {@code null},
+     *               empty, or contain a {@code null} element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the values collection is empty.
+     * @throws NullPointerException     if the values collection is
+     *                                  {@code null} or contains a {@code null}
+     *                                  element.
+     */
     default Restriction<T> in(Collection<V> values) {
-        if (values == null || values.isEmpty())
-            throw new IllegalArgumentException("The values are required");
-
         return BasicRestriction.of(this, In.values(values));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is equal to one of the specified values.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.color.in(Color.BLACK, Color.BLUE, Color.GRAY));
+     * </pre>
+     *
+     * @param values values against which to compare. Must not be {@code null},
+     *               empty, or contain a {@code null} element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the values array is empty.
+     * @throws NullPointerException     if the values array is {@code null} or
+     *                                  contains a {@code null} element.
+     */
     @SuppressWarnings("unchecked")
     default Restriction<T> in(V... values) {
         return BasicRestriction.of(this, In.values(values));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * and at least one of the specified expressions evaluate to values that
+     * are equal.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.firstModelYear.in(_Car.year.minus(3),
+     *                                            _Car.year.minus(4),
+     *                                            _Car.year.minus(5)));
+     * </pre>
+     *
+     * @param expressions expressions against which to compare. Must not be
+     *                    {@code null}, empty, or contain a {@code null}
+     *                    element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the expressions array is empty.
+     * @throws NullPointerException     if the expressions array is
+     *                                  {@code null} or contains a {@code null}
+     *                                  element.
+     */
     @SuppressWarnings("unchecked")
     default Restriction<T> in(Expression<? super T, V>... expressions) {
         return BasicRestriction.of(this, In.expressions(expressions));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a {@code null} value.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * notListedYet = cars.search(make, model, _Car.listed.isNull());
+     * </pre>
+     *
+     * @return the restriction.
+     */
     default Restriction<T> isNull() {
         return BasicRestriction.of(this, Null.instance());
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is not equal to the specified value.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make, model, _Car.color.notEqualTo(Color.RED));
+     * </pre>
+     *
+     * @param value value against which to compare. Must not be {@code null}.
+     * @return the restriction.
+     * @throws NullPointerException if the value is {@code null}.
+     */
     default Restriction<T> notEqualTo(V value) {
         return BasicRestriction.of(this, NotEqualTo.value(value));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * and the specified expression evaluate to values that are not equal to
+     * each other.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make, model, _Car.listed.notEqualTo(CurrentDate.now()));
+     * </pre>
+     *
+     * @param expression expression against which to compare. Must not be
+     *                   {@code null}.
+     * @return the restriction.
+     * @throws NullPointerException if the expression is {@code null}.
+     */
     default Restriction<T> notEqualTo(Expression<? super T, V> expression) {
         return BasicRestriction.of(this, NotEqualTo.expression(expression));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is not equal to any of the values within the
+     * specified collection.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.color.notIn(Set.of(Color.GRAY, Color.WHITE)));
+     * </pre>
+     *
+     * @param values values against which to compare. Must not be {@code null},
+     *               empty, or contain a {@code null} element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the values collection is empty.
+     * @throws NullPointerException     if the values collection is
+     *                                  {@code null} or contains a {@code null}
+     *                                  element.
+     */
     default Restriction<T> notIn(Collection<V> values) {
         if (values == null || values.isEmpty())
             throw new IllegalArgumentException("The values are required");
@@ -75,20 +294,88 @@ public interface Expression<T, V> {
         return BasicRestriction.of(this, NotIn.values(values));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is not equal to any of the specified values.
+     * </p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.year.notIn(2019, 2020, 2023));
+     * </pre>
+     *
+     * @param values values against which to compare. Must not be {@code null},
+     *               empty, or contain a {@code null} element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the values array is empty.
+     * @throws NullPointerException     if the values array is {@code null} or
+     *                                  contains a {@code null} element.
+     */
     @SuppressWarnings("unchecked")
     default Restriction<T> notIn(V... values) {
         return BasicRestriction.of(this, NotIn.values(values));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that is not equal to any of the values to which the
+     * specified expressions evaluate.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.year.notIn(_Car.firstModelYear,
+     *                                     _Car.firstModelYear.plus(1)));
+     * </pre>
+     *
+     * @param expressions expressions against which to compare. Must not be
+     *                    {@code null}, empty, or contain a {@code null}
+     *                    element.
+     * @return the restriction.
+     * @throws IllegalArgumentException if the expressions array is empty.
+     * @throws NullPointerException     if the expressions array is
+     *                                  {@code null} or contains a {@code null}
+     *                                  element.
+     */
     @SuppressWarnings("unchecked")
     default Restriction<T> notIn(Expression<? super T, V>... expressions) {
         return BasicRestriction.of(this, NotIn.expressions(expressions));
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * does not evaluate to a {@code null} value.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make, model, _Car.listed.notNull());
+     * </pre>
+     *
+     * @return the restriction.
+     */
     default Restriction<T> notNull() {
         return BasicRestriction.of(this, NotNull.instance());
     }
 
+    /**
+     * <p>Obtains a {@link Restriction} that requires that this expression
+     * evaluate to a value that satisfies the specified {@link Constraint}.</p>
+     *
+     * <p>Example:</p>
+     * <pre>
+     * found = cars.search(make,
+     *                     model,
+     *                     _Car.year.satisfies(Constraint.between(2021, 2024)));
+     * </pre>
+     *
+     * @param constraint constraint to use for comparing the value to which
+     *                   this expression evaluates. Must not be {@code null}.
+     * @return the restriction.
+     * @throws NullPointerException if the constraint is {@code null}.
+     */
     // TODO: should this be called restrict() ?
     default Restriction<T> satisfies(Constraint<V> constraint) {
         return BasicRestriction.of(this, constraint);
diff --git a/api/src/main/java/jakarta/data/metamodel/Attribute.java b/api/src/main/java/jakarta/data/metamodel/Attribute.java
index a39ddce8..e05b11a5 100644
--- a/api/src/main/java/jakarta/data/metamodel/Attribute.java
+++ b/api/src/main/java/jakarta/data/metamodel/Attribute.java
@@ -55,12 +55,15 @@ package jakarta.data.metamodel;
  * public class Car {
  *     &#64;Id
  *     public String vin;
+ *     public Color color;
  *     public LocalDate listed;
  *     public String make;
  *     public String model;
  *     public int price;
  *     public int year;
  * }
+ *
+ * public enum Color { BLACK, BLUE, GRAY, RED, WHITE }
  * </pre>
  *
  * <p>The static metamodel class (typically generated from the entity class)
@@ -69,6 +72,7 @@ package jakarta.data.metamodel;
  * <pre>
  * &#64;StaticMetamodel
  * public interface _Car {
+ *     String COLOR = "color";
  *     String LISTED = "listed";
  *     String MAKE = "make";
  *     String MODEL = "model";
@@ -76,6 +80,8 @@ package jakarta.data.metamodel;
  *     String VIN = "vin";
  *     String YEAR = "year";
  *
+ *     ComparableAttribute&lt;Car,Color&gt; price = ComparableAttribute.of(
+ *             Car.class, COLOR, Color.class);
  *     TemporalAttribute&lt;Car,LocalDate&gt; price = TemporalAttribute.of(
  *             Car.class, LISTED, LocalDate.class);
  *     TextAttribute&lt;Car&gt; make = TextAttribute.of(
diff --git a/api/src/main/java/jakarta/data/restrict/Restriction.java b/api/src/main/java/jakarta/data/restrict/Restriction.java
index 49b81889..4dd47674 100644
--- a/api/src/main/java/jakarta/data/restrict/Restriction.java
+++ b/api/src/main/java/jakarta/data/restrict/Restriction.java
@@ -31,7 +31,7 @@ import jakarta.data.repository.Find;
  * expressions.</p>
  *
  * <p>Basic restrictions that impose a single
- * {@linkplain Constraint constraint} are obtained from {@linkplain Attribute}
+ * {@linkplain Constraint constraint} are obtained from {@link Attribute}
  * subtypes of the {@linkplain StaticMetamodel static metamodel}.</p>
  *
  * <p>Composite restrictions that impose multiple


Test patch:
diff --git a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
index fad1d627..654c263d 100644
--- a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
+++ b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java
@@ -90,7 +90,7 @@ class AttributeTest {
     void shouldThrowExceptionForEmptyInRestriction() {
         assertThatThrownBy(() -> testAttribute.in(Set.of()))
                 .isInstanceOf(IllegalArgumentException.class)
-                .hasMessage("The values are required");
+                .hasMessage("The values must not be empty");
     }
 
     @Test