diff --git a/api/src/main/java/jakarta/data/BasicRestriction.java b/api/src/main/java/jakarta/data/BasicRestriction.java new file mode 100644 index 000000000..f7c3b1674 --- /dev/null +++ b/api/src/main/java/jakarta/data/BasicRestriction.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +public interface BasicRestriction extends Restriction { + Operator comparison(); + + String field(); + + Object value(); +} diff --git a/api/src/main/java/jakarta/data/BasicRestrictionRecord.java b/api/src/main/java/jakarta/data/BasicRestrictionRecord.java new file mode 100644 index 000000000..e58ca18f3 --- /dev/null +++ b/api/src/main/java/jakarta/data/BasicRestrictionRecord.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +// Internal implementation class. +// The proper way for users to obtain instances is via +// the static metamodel or Restrict.* methods + +import java.util.Objects; + +record BasicRestrictionRecord( + String field, + boolean isNegated, + Operator comparison, + Object value) implements BasicRestriction { + + BasicRestrictionRecord { + Objects.requireNonNull(field, "Field must not be null"); + } + + BasicRestrictionRecord(String field, Operator comparison, Object value) { + this(field, false, comparison, value); + } +} diff --git a/api/src/main/java/jakarta/data/CompositeRestriction.java b/api/src/main/java/jakarta/data/CompositeRestriction.java new file mode 100644 index 000000000..1eeffbf9a --- /dev/null +++ b/api/src/main/java/jakarta/data/CompositeRestriction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import java.util.List; + +public interface CompositeRestriction extends Restriction { + List> restrictions(); + + Type type(); + + enum Type { + ALL, + ANY + } +} diff --git a/api/src/main/java/jakarta/data/CompositeRestrictionRecord.java b/api/src/main/java/jakarta/data/CompositeRestrictionRecord.java new file mode 100644 index 000000000..a8cd5e0ff --- /dev/null +++ b/api/src/main/java/jakarta/data/CompositeRestrictionRecord.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import java.util.List; + +// Internal implementation class. +// The proper way for users to obtain instances is via +// the Restrict.any(...) or Restrict.all(...) methods + +record CompositeRestrictionRecord( + Type type, + List> restrictions, + boolean isNegated) implements CompositeRestriction { + + CompositeRestrictionRecord { + if (restrictions == null || restrictions.isEmpty()) { + throw new IllegalArgumentException( + "Cannot create a composite restriction without any restrictions to combine."); + } + } + + CompositeRestrictionRecord(Type type, List> restrictions) { + this(type, restrictions, false); + } +} \ No newline at end of file diff --git a/api/src/main/java/jakarta/data/Operator.java b/api/src/main/java/jakarta/data/Operator.java new file mode 100644 index 000000000..a8d448c8b --- /dev/null +++ b/api/src/main/java/jakarta/data/Operator.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +public enum Operator { + EQUAL, + GREATER_THAN, + GREATER_THAN_EQUAL, + IN, + LESS_THAN, + LESS_THAN_EQUAL, + LIKE +} diff --git a/api/src/main/java/jakarta/data/Restrict.java b/api/src/main/java/jakarta/data/Restrict.java new file mode 100644 index 000000000..96b24d8ee --- /dev/null +++ b/api/src/main/java/jakarta/data/Restrict.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import java.util.List; +import java.util.Set; + +// TODO document +// This is one of two places from which to obtain restrictions. +// The other place is from static metamodel attributes. +public class Restrict { + + private static final char CHAR_WILDCARD = '_'; + + private static final char ESCAPE_CHAR = '\\'; + + // used internally for more readable code + private static final boolean ESCAPED = true; + private static final boolean NOT = true; + + private static final char STRING_WILDCARD = '%'; + + // prevent instantiation + private Restrict() { + } + + @SafeVarargs + public static Restriction all(Restriction... restrictions) { + return new CompositeRestrictionRecord<>(CompositeRestriction.Type.ALL, + List.of(restrictions)); + } + + @SafeVarargs + public static Restriction any(Restriction... restrictions) { + return new CompositeRestrictionRecord<>(CompositeRestriction.Type.ANY, + List.of(restrictions)); + } + + public static > Restriction between(V min, + V max, + String field) { + return all(greaterThanEqual(min, field), + lessThanEqual(max, field)); + } + + // TODO Need to think more about how to best cover negation of multiple + // and then make negation of Single consistent with it + + public static TextRestriction contains(String substring, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, substring, true); + return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern); + } + + public static TextRestriction endsWith(String suffix, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, suffix, false); + return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern); + } + + public static Restriction equalTo(Object value, String field) { + return new BasicRestrictionRecord<>(field, Operator.EQUAL, value); + } + + public static TextRestriction equalTo(String value, String field) { + return new TextRestrictionRecord<>(field, Operator.EQUAL, value); + } + + public static > Restriction greaterThan(V value, String field) { + return new BasicRestrictionRecord<>(field, Operator.GREATER_THAN, value); + } + + public static TextRestriction greaterThan(String value, String field) { + return new TextRestrictionRecord<>(field, Operator.GREATER_THAN, value); + } + + public static > Restriction greaterThanEqual(V value, String field) { + return new BasicRestrictionRecord<>(field, Operator.GREATER_THAN_EQUAL, value); + } + + public static TextRestriction greaterThanEqual(String value, String field) { + return new TextRestrictionRecord<>(field, Operator.GREATER_THAN_EQUAL, value); + } + + public static Restriction in(Set values, String field) { + return new BasicRestrictionRecord<>(field, Operator.IN, values); + } + + public static > Restriction lessThan(V value, String field) { + return new BasicRestrictionRecord<>(field, Operator.LESS_THAN, value); + } + + public static TextRestriction lessThan(String value, String field) { + return new TextRestrictionRecord<>(field, Operator.LESS_THAN, value); + } + + public static > Restriction lessThanEqual(V value, String field) { + return new BasicRestrictionRecord<>(field, Operator.LESS_THAN_EQUAL, value); + } + + public static TextRestriction lessThanEqual(String value, String field) { + return new TextRestrictionRecord<>(field, Operator.LESS_THAN_EQUAL, value); + } + + // TODO this would be possible if Pattern is added, but is it even useful? + //public static TextRestriction like(Pattern pattern, String field) { + // return new TextRestriction<>(field, Operator.LIKE, ESCAPED, pattern); + //} + + public static TextRestriction like(String pattern, String field) { + return new TextRestrictionRecord<>(field, Operator.LIKE, pattern); + } + + public static TextRestriction like(String pattern, + char charWildcard, + char stringWildcard, + String field) { + String p = toLikeEscaped(charWildcard, stringWildcard, false, pattern, false); + return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, p); + } + + public static Restriction notEqualTo(Object value, String field) { + return new BasicRestrictionRecord<>(field, NOT, Operator.EQUAL, value); + } + + public static TextRestriction notEqualTo(String value, String field) { + return new TextRestrictionRecord<>(field, NOT, Operator.EQUAL, value); + } + + public static TextRestriction notContains(String substring, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, substring, true); + return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern); + } + + public static TextRestriction notEndsWith(String suffix, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, true, suffix, false); + return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern); + } + + public static Restriction notIn(Set values, String field) { + return new BasicRestrictionRecord<>(field, NOT, Operator.IN, values); + } + + public static TextRestriction notLike(String pattern, String field) { + return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, pattern); + } + + public static TextRestriction notLike(String pattern, + char charWildcard, + char stringWildcard, + String field) { + String p = toLikeEscaped(charWildcard, stringWildcard, false, pattern, false); + return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, p); + } + + public static TextRestriction notStartsWith(String prefix, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, false, prefix, true); + return new TextRestrictionRecord<>(field, NOT, Operator.LIKE, ESCAPED, pattern); + } + + public static TextRestriction startsWith(String prefix, String field) { + String pattern = toLikeEscaped(CHAR_WILDCARD, STRING_WILDCARD, false, prefix, true); + return new TextRestrictionRecord<>(field, Operator.LIKE, ESCAPED, pattern); + } + + /** + * Converts the literal pattern into an escaped LIKE pattern. + * This method prepends a % character if previous characters are allowed, + * escapes the charWildcard (typically _), the stringWildcard (typically %), + * and the \ character within the literal by inserting \ prior to each, + * and then appends a % character if subsequent characters are allowed. + * + * @param charWildcard single character wildcard, typically _. + * @param stringWildcard 0 or more character wildcard, typically %. + * @param allowPrevious whether to allow characters prior to the text. + * @param literal text that is not escaped that must be matched. + * @param allowSubsequent whether to allow more characters after the text. + * @return escaped pattern. + * @throws IllegalArgumentException if the same character is supplied for + * both wildcard types. + */ + // TODO could move to Pattern class + private static String toLikeEscaped(char charWildcard, + char stringWildcard, + boolean allowPrevious, + String literal, + boolean allowSubsequent) { + if (charWildcard == stringWildcard) + throw new IllegalArgumentException( + "Cannot use the same character (" + charWildcard + + ") for both types of wildcards."); + + int length = literal.length(); + StringBuilder s = new StringBuilder(length + 10); + if (allowPrevious) { + s.append(STRING_WILDCARD); + } + for (int i = 0; i < length; i++) { + char ch = literal.charAt(i); + if (ch == charWildcard) { + s.append(ESCAPE_CHAR) + .append(CHAR_WILDCARD); + } else if (ch == stringWildcard) { + s.append(ESCAPE_CHAR) + .append(STRING_WILDCARD); + } else if (ch == ESCAPE_CHAR) { + s.append(ESCAPE_CHAR) + .append(ESCAPE_CHAR); + } else { + s.append(ch); + } + } + if (allowSubsequent) { + s.append(STRING_WILDCARD); + } + return s.toString(); + } +} diff --git a/api/src/main/java/jakarta/data/Restriction.java b/api/src/main/java/jakarta/data/Restriction.java new file mode 100644 index 000000000..0548fb4f2 --- /dev/null +++ b/api/src/main/java/jakarta/data/Restriction.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +public interface Restriction { + boolean isNegated(); +} diff --git a/api/src/main/java/jakarta/data/TextRestriction.java b/api/src/main/java/jakarta/data/TextRestriction.java new file mode 100644 index 000000000..b6c9850ca --- /dev/null +++ b/api/src/main/java/jakarta/data/TextRestriction.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +public interface TextRestriction extends BasicRestriction { + Restriction ignoreCase(); + + // TODO can mention in the JavaDoc that a value of true will be ignored + // if the database is not not capable of case sensitive comparisons + boolean isCaseSensitive(); + + boolean isEscaped(); + + @Override + String value(); +} diff --git a/api/src/main/java/jakarta/data/TextRestrictionRecord.java b/api/src/main/java/jakarta/data/TextRestrictionRecord.java new file mode 100644 index 000000000..831fec412 --- /dev/null +++ b/api/src/main/java/jakarta/data/TextRestrictionRecord.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +// Internal implementation class. +// The proper way for users to obtain instances is via +// the static metamodel or Restrict.* methods + +import java.util.Objects; + +record TextRestrictionRecord( + String field, + boolean isNegated, + Operator comparison, + boolean isCaseSensitive, + boolean isEscaped, + String value) implements TextRestriction { + + TextRestrictionRecord { + Objects.requireNonNull(field, "Field must not be null"); + } + + TextRestrictionRecord(String field, boolean negated, Operator comparison, boolean escaped, String value) { + this(field, negated, comparison, true, escaped, value); + } + + TextRestrictionRecord(String field, boolean negated, Operator comparison, String value) { + this(field, negated, comparison, true, false, value); + } + + TextRestrictionRecord(String field, Operator comparison, boolean escaped, String value) { + this(field, false, comparison, true, escaped, value); + } + + TextRestrictionRecord(String field, Operator comparison, String value) { + this(field, false, comparison, true, false, value); + } + + @Override + public Restriction ignoreCase() { + return new TextRestrictionRecord<>(field, isNegated, comparison, false, isEscaped, value); + } +} diff --git a/api/src/main/java/jakarta/data/metamodel/Attribute.java b/api/src/main/java/jakarta/data/metamodel/Attribute.java index 2906554d6..147b73ba9 100644 --- a/api/src/main/java/jakarta/data/metamodel/Attribute.java +++ b/api/src/main/java/jakarta/data/metamodel/Attribute.java @@ -17,7 +17,10 @@ */ package jakarta.data.metamodel; -import jakarta.data.Sort; +import java.util.Set; + +import jakarta.data.Restrict; +import jakarta.data.Restriction; /** * Represents an entity attribute in the {@link StaticMetamodel}. @@ -25,6 +28,22 @@ * @param entity class of the static metamodel. */ public interface Attribute { + + default Restriction equalTo(Object value) { + return Restrict.equalTo(value, name()); + } + + default Restriction in(Object... values) { + if (values == null || values.length == 0) + throw new IllegalArgumentException("values are required"); + + return Restrict.in(Set.of(values), name()); + } + + default Restriction isNull() { + return Restrict.equalTo(null, name()); + } + /** * Obtain the entity attribute name, suitable for use wherever the specification requires * an entity attribute name. For example, as the parameter to {@link Sort#asc(String)}. @@ -32,4 +51,19 @@ public interface Attribute { * @return the entity attribute name. */ String name(); + + default Restriction notEqualTo(Object value) { + return Restrict.notEqualTo(value, name()); + } + + default Restriction notIn(Object... values) { + if (values == null || values.length == 0) + throw new IllegalArgumentException("values are required"); + + return Restrict.notIn(Set.of(values), name()); + } + + default Restriction notNull() { + return Restrict.notEqualTo(null, name()); + } } diff --git a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java index 559960a41..650164de9 100644 --- a/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java +++ b/api/src/main/java/jakarta/data/metamodel/SortableAttribute.java @@ -17,6 +17,8 @@ */ package jakarta.data.metamodel; +import jakarta.data.Restrict; +import jakarta.data.Restriction; import jakarta.data.Sort; /** @@ -42,6 +44,10 @@ public interface SortableAttribute extends Attribute { */ Sort asc(); + default > Restriction between(V min, V max) { + return Restrict.between(min, max, name()); + } + /** * Obtain a request for a descending {@link Sort} based on the entity attribute. * @@ -49,4 +55,19 @@ public interface SortableAttribute extends Attribute { */ Sort desc(); + default > Restriction greaterThan(V value) { + return Restrict.greaterThan(value, name()); + } + + default > Restriction greaterThanEqual(V value) { + return Restrict.greaterThanEqual(value, name()); + } + + default > Restriction lessThan(V value) { + return Restrict.lessThan(value, name()); + } + + default > Restriction lessThanEqual(V value) { + return Restrict.lessThanEqual(value, name()); + } } diff --git a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java index c00390f09..9b0260256 100644 --- a/api/src/main/java/jakarta/data/metamodel/TextAttribute.java +++ b/api/src/main/java/jakarta/data/metamodel/TextAttribute.java @@ -17,7 +17,9 @@ */ package jakarta.data.metamodel; +import jakarta.data.Restrict; import jakarta.data.Sort; +import jakarta.data.TextRestriction; /** * Represents an textual entity attribute in the {@link StaticMetamodel}. @@ -33,6 +35,10 @@ public interface TextAttribute extends SortableAttribute { */ Sort ascIgnoreCase(); + default TextRestriction contains(String substring) { + return Restrict.contains(substring, name()); + } + /** * Obtain a request for a descending, case insensitive {@link Sort} based on the entity attribute. * @@ -40,4 +46,73 @@ public interface TextAttribute extends SortableAttribute { */ Sort descIgnoreCase(); + default TextRestriction endsWith(String suffix) { + return Restrict.endsWith(suffix, name()); + } + + default TextRestriction equalTo(String value) { + return Restrict.equalTo(value, name()); + } + + default TextRestriction greaterThan(String value) { + return Restrict.greaterThan(value, name()); + } + + default TextRestriction greaterThanEqual(String value) { + return Restrict.greaterThanEqual(value, name()); + } + + default TextRestriction lessThan(String value) { + return Restrict.lessThan(value, name()); + } + + default TextRestriction lessThanEqual(String value) { + return Restrict.lessThanEqual(value, name()); + } + + // TODO once we have Pattern: + //default TextRestriction like(Pattern pattern) { + // return Restrict.like(pattern, name()); + //} + + default TextRestriction like(String pattern) { + return Restrict.like(pattern, name()); + } + + default TextRestriction like(String pattern, + char charWildcard, + char stringWildcard) { + return Restrict.like(pattern, charWildcard, stringWildcard, name()); + } + + default TextRestriction notContains(String substring) { + return Restrict.notContains(substring, name()); + } + + default TextRestriction notEndsWith(String suffix) { + return Restrict.notEndsWith(suffix, name()); + } + + default TextRestriction notEqualTo(String value) { + return Restrict.notEqualTo(value, name()); + } + + default TextRestriction notLike(String pattern) { + return Restrict.notLike(pattern, name()); + } + + default TextRestriction notLike(String pattern, + char charWildcard, + char stringWildcard) { + return Restrict.notLike(pattern, charWildcard, stringWildcard, name()); + } + + default TextRestriction notStartsWith(String prefix) { + return Restrict.notStartsWith(prefix, name()); + } + + default TextRestriction startsWith(String prefix) { + return Restrict.startsWith(prefix, name()); + } + } diff --git a/api/src/test/java/jakarta/data/BasicRestrictionRecordTest.java b/api/src/test/java/jakarta/data/BasicRestrictionRecordTest.java new file mode 100644 index 000000000..c46bfb0db --- /dev/null +++ b/api/src/test/java/jakarta/data/BasicRestrictionRecordTest.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + + +class BasicRestrictionRecordTest { + + @Test + void shouldCreateBasicRestrictionWithDefaultNegation() { + BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isFalse(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(restriction.value()).isEqualTo("Java Guide"); + }); + } + + @Test + void shouldCreateBasicRestrictionWithExplicitNegation() { + BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("title", true, Operator.EQUAL, "Java Guide"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isTrue(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(restriction.value()).isEqualTo("Java Guide"); + }); + } + + @Test + void shouldCreateBasicRestrictionWithNullValue() { + // Create a restriction with a null value + BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("title", Operator.EQUAL, null); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isFalse(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(restriction.value()).isNull(); + }); + } + + @Test + void shouldSupportNegatedRestrictionUsingDefaultConstructor() { + BasicRestrictionRecord restriction = new BasicRestrictionRecord<>("author", Operator.EQUAL, "Unknown"); + BasicRestrictionRecord negatedRestriction = new BasicRestrictionRecord<>(restriction.field(), true, restriction.comparison(), restriction.value()); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(negatedRestriction.field()).isEqualTo("author"); + soft.assertThat(negatedRestriction.isNegated()).isTrue(); + soft.assertThat(negatedRestriction.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(negatedRestriction.value()).isEqualTo("Unknown"); + }); + } + + @Test + void shouldThrowExceptionWhenFieldIsNull() { + assertThatThrownBy(() -> new BasicRestrictionRecord<>(null, Operator.EQUAL, "testValue")) + .isInstanceOf(NullPointerException.class) + .hasMessage("Field must not be null"); + } +} diff --git a/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java b/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java new file mode 100644 index 000000000..79764f649 --- /dev/null +++ b/api/src/test/java/jakarta/data/CompositeRestrictionRecordTest.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +import java.util.List; + + +class CompositeRestrictionRecordTest { + + + @Test + void shouldCreateCompositeRestrictionWithDefaultNegation() { + Restriction restriction1 = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide"); + Restriction restriction2 = new BasicRestrictionRecord<>("author", Operator.EQUAL, "John Doe"); + + CompositeRestrictionRecord composite = new CompositeRestrictionRecord<>( + CompositeRestriction.Type.ALL, + List.of(restriction1, restriction2) + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(composite.type()).isEqualTo(CompositeRestriction.Type.ALL); + soft.assertThat(composite.restrictions()).containsExactly(restriction1, restriction2); + soft.assertThat(composite.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateCompositeRestrictionWithExplicitNegation() { + Restriction restriction1 = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide"); + Restriction restriction2 = new BasicRestrictionRecord<>("author", Operator.EQUAL, "John Doe"); + + CompositeRestrictionRecord composite = new CompositeRestrictionRecord<>( + CompositeRestriction.Type.ANY, + List.of(restriction1, restriction2), + true + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(composite.type()).isEqualTo(CompositeRestriction.Type.ANY); + soft.assertThat(composite.restrictions()).containsExactly(restriction1, restriction2); + soft.assertThat(composite.isNegated()).isTrue(); + }); + } + + @Test + void shouldFailIfEmptyRestrictions() { + assertThatThrownBy(() -> new CompositeRestrictionRecord<>(CompositeRestriction.Type.ALL, List.of())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot create a composite restriction without any restrictions to combine."); + } + + @Test + void shouldPreserveRestrictionsOrder() { + Restriction restriction1 = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide"); + Restriction restriction2 = new BasicRestrictionRecord<>("author", Operator.EQUAL, "John Doe"); + + CompositeRestrictionRecord composite = new CompositeRestrictionRecord<>( + CompositeRestriction.Type.ALL, + List.of(restriction1, restriction2) + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(composite.restrictions().get(0)).isEqualTo(restriction1); + soft.assertThat(composite.restrictions().get(1)).isEqualTo(restriction2); + }); + } + + @Test + void shouldSupportNegationUsingDefaultConstructor() { + // Given multiple restrictions + Restriction restriction1 = new BasicRestrictionRecord<>("title", Operator.EQUAL, "Java Guide"); + Restriction restriction2 = new BasicRestrictionRecord<>("author", Operator.EQUAL, "John Doe"); + + // When creating a composite restriction and manually setting negation + CompositeRestrictionRecord composite = new CompositeRestrictionRecord<>( + CompositeRestriction.Type.ALL, + List.of(restriction1, restriction2) + ); + CompositeRestrictionRecord negatedComposite = new CompositeRestrictionRecord<>( + composite.type(), + composite.restrictions(), + true + ); + + // Then validate the negated composite restriction + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(negatedComposite.type()).isEqualTo(CompositeRestriction.Type.ALL); + soft.assertThat(negatedComposite.restrictions()).containsExactly(restriction1, restriction2); + soft.assertThat(negatedComposite.isNegated()).isTrue(); + }); + } +} diff --git a/api/src/test/java/jakarta/data/RestrictTest.java b/api/src/test/java/jakarta/data/RestrictTest.java new file mode 100644 index 000000000..d2914e08f --- /dev/null +++ b/api/src/test/java/jakarta/data/RestrictTest.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + +class RestrictTest { + + + @Test + void shouldCreateEqualToRestriction() { + Restriction restriction = Restrict.equalTo("value", "field"); + + assertThat(restriction).isInstanceOf(TextRestrictionRecord.class); + + TextRestrictionRecord basic = (TextRestrictionRecord) restriction; + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(basic.field()).isEqualTo("field"); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.value()).isEqualTo("value"); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNotEqualToRestriction() { + Restriction restriction = Restrict.notEqualTo("value", "field"); + + assertThat(restriction).isInstanceOf(TextRestrictionRecord.class); + + TextRestrictionRecord basic = (TextRestrictionRecord) restriction; + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(basic.field()).isEqualTo("field"); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.value()).isEqualTo("value"); + soft.assertThat(basic.isNegated()).isTrue(); + }); + } + + @Test + void shouldCombineAllRestrictionsWithNegation() { + Restriction r1 = Restrict.notEqualTo("value1", "field1"); + Restriction r2 = Restrict.greaterThan(100, "field2"); + + Restriction combined = Restrict.all(r1, r2); + + assertThat(combined).isInstanceOf(CompositeRestrictionRecord.class); + + CompositeRestrictionRecord composite = (CompositeRestrictionRecord) combined; + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(composite.type()).isEqualTo(CompositeRestriction.Type.ALL); + soft.assertThat(composite.restrictions()).containsExactly(r1, r2); + soft.assertThat(composite.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateContainsRestriction() { + TextRestriction restriction = Restrict.contains("substring", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%substring%"); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNegatedContainsRestriction() { + TextRestriction restriction = Restrict.notContains("substring", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%substring%"); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateStartsWithRestriction() { + TextRestriction restriction = Restrict.startsWith("prefix", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("prefix%"); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNegatedStartsWithRestriction() { + TextRestriction restriction = Restrict.notStartsWith("prefix", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("prefix%"); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateEndsWithRestriction() { + TextRestriction restriction = Restrict.endsWith("suffix", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%suffix"); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNegatedEndsWithRestriction() { + TextRestriction restriction = Restrict.notEndsWith("suffix", "field"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("field"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%suffix"); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldEscapeToLikePatternCorrectly() { + String result = Restrict.endsWith("test_value", "fieldName").value(); + + assertThat(result).isEqualTo("%test\\_value"); + } + + @Test + void shouldThrowExceptionForInvalidWildcard() { + assertThatThrownBy(() -> Restrict.like("pattern_value", '_', '_', "fieldName")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("Cannot use the same character (_) for both types of wildcards."); + } +} diff --git a/api/src/test/java/jakarta/data/TextRestrictionRecordTest.java b/api/src/test/java/jakarta/data/TextRestrictionRecordTest.java new file mode 100644 index 000000000..5b7dcb2a7 --- /dev/null +++ b/api/src/test/java/jakarta/data/TextRestrictionRecordTest.java @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data; + +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; + + +class TextRestrictionRecordTest { + + @Test + void shouldCreateTextRestrictionWithDefaultValues() { + TextRestrictionRecord restriction = new TextRestrictionRecord<>( + "title", + Operator.LIKE, + "%Java%" + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isFalse(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%Java%"); + soft.assertThat(restriction.isCaseSensitive()).isTrue(); + soft.assertThat(restriction.isEscaped()).isFalse(); + }); + } + + @Test + void shouldCreateTextRestrictionWithExplicitNegation() { + TextRestrictionRecord restriction = new TextRestrictionRecord<>( + "title", + true, + Operator.LIKE, + "%Java%" + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isTrue(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%Java%"); + soft.assertThat(restriction.isCaseSensitive()).isTrue(); + soft.assertThat(restriction.isEscaped()).isFalse(); + }); + } + + @Test + void shouldIgnoreCaseForTextRestriction() { + TextRestrictionRecord restriction = new TextRestrictionRecord<>( + "title", + Operator.LIKE, + "%Java%" + ); + + Restriction caseInsensitiveRestriction = restriction.ignoreCase(); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(caseInsensitiveRestriction).isInstanceOf(TextRestrictionRecord.class); + TextRestrictionRecord textRestriction = (TextRestrictionRecord) caseInsensitiveRestriction; + soft.assertThat(textRestriction.field()).isEqualTo("title"); + soft.assertThat(textRestriction.isNegated()).isFalse(); + soft.assertThat(textRestriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(textRestriction.value()).isEqualTo("%Java%"); + soft.assertThat(textRestriction.isCaseSensitive()).isFalse(); + soft.assertThat(textRestriction.isEscaped()).isFalse(); + }); + } + + @Test + void shouldCreateTextRestrictionWithEscapedValue() { + TextRestrictionRecord restriction = new TextRestrictionRecord<>( + "title", + Operator.LIKE, + true, + "%Java%" + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("title"); + soft.assertThat(restriction.isNegated()).isFalse(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.value()).isEqualTo("%Java%"); + soft.assertThat(restriction.isCaseSensitive()).isTrue(); + soft.assertThat(restriction.isEscaped()).isTrue(); + }); + } + + @Test + void shouldSupportNegationForTextRestriction() { + TextRestrictionRecord restriction = new TextRestrictionRecord<>( + "author", + true, + Operator.EQUAL, + "John Doe" + ); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("author"); + soft.assertThat(restriction.isNegated()).isTrue(); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(restriction.value()).isEqualTo("John Doe"); + soft.assertThat(restriction.isCaseSensitive()).isTrue(); + soft.assertThat(restriction.isEscaped()).isFalse(); + }); + } + + @Test + void shouldThrowExceptionWhenFieldIsNullInTextRestriction() { + assertThatThrownBy(() -> new TextRestrictionRecord<>(null, Operator.LIKE, "testValue")) + .isInstanceOf(NullPointerException.class) + .hasMessage("Field must not be null"); + } +} diff --git a/api/src/test/java/jakarta/data/metamodel/AttributeTest.java b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java new file mode 100644 index 000000000..f087674c1 --- /dev/null +++ b/api/src/test/java/jakarta/data/metamodel/AttributeTest.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data.metamodel; + +import jakarta.data.BasicRestriction; +import jakarta.data.Operator; +import jakarta.data.Restriction; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +import java.util.Set; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class AttributeTest { + + private final Attribute testAttribute = () -> "testAttribute"; + @Test + void shouldCreateEqualToRestriction() { + Restriction restriction = testAttribute.equalTo("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo("testValue"); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNotEqualToRestriction() { + Restriction restriction = testAttribute.notEqualTo("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo("testValue"); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateInRestriction() { + Restriction restriction = testAttribute.in("value1", "value2"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(Set.of("value1", "value2")); + soft.assertThat(basic.comparison()).isEqualTo(Operator.IN); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldThrowExceptionForEmptyInRestriction() { + assertThatThrownBy(() -> testAttribute.in()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("values are required"); + } + + @Test + void shouldCreateNotInRestriction() { + Restriction restriction = testAttribute.notIn("value1", "value2"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(Set.of("value1", "value2")); + soft.assertThat(basic.comparison()).isEqualTo(Operator.IN); + soft.assertThat(basic.isNegated()).isTrue(); + }); + } + + @Test + void shouldThrowExceptionForEmptyNotInRestriction() { + assertThatThrownBy(() -> testAttribute.notIn()) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("values are required"); + } + + @Test + void shouldCreateIsNullRestriction() { + Restriction restriction = testAttribute.isNull(); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isNull(); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNotNullRestriction() { + Restriction restriction = testAttribute.notNull(); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isNull(); + soft.assertThat(basic.comparison()).isEqualTo(Operator.EQUAL); + soft.assertThat(basic.isNegated()).isTrue(); + }); + } +} diff --git a/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java b/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java new file mode 100644 index 000000000..697284cd8 --- /dev/null +++ b/api/src/test/java/jakarta/data/metamodel/SortableAttributeTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data.metamodel; + +import jakarta.data.BasicRestriction; +import jakarta.data.CompositeRestriction; +import jakarta.data.Operator; +import jakarta.data.Restriction; +import jakarta.data.Sort; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + + +class SortableAttributeTest { + + //it ignores the implementation of the SortableAttribute interface and uses an anonymous class to test the methods + private final SortableAttribute testAttribute = new SortableAttribute() { + @Override + public Sort asc() { + throw new UnsupportedOperationException("It is not the focus of this test"); + } + + @Override + public Sort desc() { + throw new UnsupportedOperationException("It is not the focus of this test"); + } + + @Override + public String name() { + return "testAttribute"; + } + }; + + @Test + void shouldCreateGreaterThanRestriction() { + Restriction restriction = testAttribute.greaterThan(10); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(10); + soft.assertThat(basic.comparison()).isEqualTo(Operator.GREATER_THAN); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateGreaterThanEqualRestriction() { + Restriction restriction = testAttribute.greaterThanEqual(10); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(10); + soft.assertThat(basic.comparison()).isEqualTo(Operator.GREATER_THAN_EQUAL); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateLessThanRestriction() { + Restriction restriction = testAttribute.lessThan(10); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(10); + soft.assertThat(basic.comparison()).isEqualTo(Operator.LESS_THAN); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateLessThanOrEqualRestriction() { + Restriction restriction = testAttribute.lessThanEqual(10); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(BasicRestriction.class); + BasicRestriction basic = (BasicRestriction) restriction; + soft.assertThat(basic.field()).isEqualTo("testAttribute"); + soft.assertThat(basic.value()).isEqualTo(10); + soft.assertThat(basic.comparison()).isEqualTo(Operator.LESS_THAN_EQUAL); + soft.assertThat(basic.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateBetweenRestriction() { + Restriction restriction = testAttribute.between(5, 15); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction).isInstanceOf(CompositeRestriction.class); + CompositeRestriction composite = (CompositeRestriction) restriction; + soft.assertThat(composite.type()).isEqualTo(CompositeRestriction.Type.ALL); + soft.assertThat(composite.restrictions()).hasSize(2); + + Restriction lowerBound = composite.restrictions().get(0); + soft.assertThat(lowerBound).isInstanceOf(BasicRestriction.class); + BasicRestriction lower = (BasicRestriction) lowerBound; + soft.assertThat(lower.field()).isEqualTo("testAttribute"); + soft.assertThat(lower.value()).isEqualTo(5); + soft.assertThat(lower.comparison()).isEqualTo(Operator.GREATER_THAN_EQUAL); + soft.assertThat(lower.isNegated()).isFalse(); + + Restriction upperBound = composite.restrictions().get(1); + soft.assertThat(upperBound).isInstanceOf(BasicRestriction.class); + BasicRestriction upper = (BasicRestriction) upperBound; + soft.assertThat(upper.field()).isEqualTo("testAttribute"); + soft.assertThat(upper.value()).isEqualTo(15); + soft.assertThat(upper.comparison()).isEqualTo(Operator.LESS_THAN_EQUAL); + soft.assertThat(upper.isNegated()).isFalse(); + }); + } +} diff --git a/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java b/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java new file mode 100644 index 000000000..b14f15a72 --- /dev/null +++ b/api/src/test/java/jakarta/data/metamodel/TextAttributeTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2024 Contributors to the Eclipse Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + */ +package jakarta.data.metamodel; + +import jakarta.data.Operator; +import jakarta.data.Sort; +import jakarta.data.TextRestriction; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.Test; + +class TextAttributeTest { + + + private final TextAttribute testAttribute = new TextAttribute() { + @Override + public Sort ascIgnoreCase() { + throw new UnsupportedOperationException("Not the focus of this test."); + } + + @Override + public Sort descIgnoreCase() { + throw new UnsupportedOperationException("Not the focus of this test."); + } + + @Override + public Sort asc() { + throw new UnsupportedOperationException("Not the focus of this test."); + } + + @Override + public Sort desc() { + throw new UnsupportedOperationException("Not the focus of this test."); + } + + @Override + public String name() { + return "testAttribute"; + } + }; + + @Test + void shouldCreateContainsRestriction() { + TextRestriction restriction = testAttribute.contains("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%testValue%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateStartsWithRestriction() { + TextRestriction restriction = testAttribute.startsWith("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("testValue%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateEndsWithRestriction() { + TextRestriction restriction = testAttribute.endsWith("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%testValue"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateLikeRestriction() { + TextRestriction restriction = testAttribute.like("%test%"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%test%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isFalse(); + }); + } + + @Test + void shouldCreateNotContainsRestriction() { + TextRestriction restriction = testAttribute.notContains("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%testValue%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateNotLikeRestriction() { + TextRestriction restriction = testAttribute.notLike("%test%"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%test%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateNotStartsWithRestriction() { + TextRestriction restriction = testAttribute.notStartsWith("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("testValue%"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } + + @Test + void shouldCreateNotEndsWithRestriction() { + TextRestriction restriction = testAttribute.notEndsWith("testValue"); + + SoftAssertions.assertSoftly(soft -> { + soft.assertThat(restriction.field()).isEqualTo("testAttribute"); + soft.assertThat(restriction.value()).isEqualTo("%testValue"); + soft.assertThat(restriction.comparison()).isEqualTo(Operator.LIKE); + soft.assertThat(restriction.isNegated()).isTrue(); + }); + } +}