OnDateSpecification.java
package expresspecs.datetime;
import java.time.LocalDate;
import java.util.List;
import org.jspecify.annotations.NonNull;
import org.springframework.data.jpa.domain.Specification;
import expresspecs.PropertyPath;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import lombok.RequiredArgsConstructor;
/**
* {@link Specification} that matches entities whose temporal property falls on a given calendar
* {@link LocalDate}.
*
* <p>When the specification is translated to a {@linkplain jakarta.persistence.criteria.Predicate predicate},
* the property path's Java type selects the first {@link SameCalendarDay} implementation that
* {@linkplain SameCalendarDay#supports(Class) supports} that type. Unsupported types yield
* {@link UnsupportedDatePropertyException} at evaluation time.
*
* <p>{@link DateTimeSpecifications#onDate(PropertyPath, LocalDate)} is the typical entry point: it returns
* an instance of this class for a non-null date, and {@link expresspecs.BasicSpecifications#unrestricted()}
* when the date is {@code null}. The detailed semantics per supported property type are documented on that method.
*
* @param <T> entity root type
*/
@RequiredArgsConstructor
public final class OnDateSpecification<T> implements Specification<T> {
private static final long serialVersionUID = 1L;
// The ordering of this list is important
private static final List<SameCalendarDay> SAME_CALENDAR_DAY_STRATEGIES = List.of(
new SameCalendarDayForLocalDate(),
new SameCalendarDayForSqlDate(),
new SameCalendarDayForInstant(),
new SameCalendarDayForOffsetDateTime(),
new SameCalendarDayForZonedDateTime(),
new SameCalendarDayForLocalDateTime(),
new SameCalendarDayForUtilDate()
);
private final PropertyPath propertyPath;
private final LocalDate targetDate;
/**
* @throws UnsupportedDatePropertyException if the property is of a type not supported (eg, java.util.Calendar)
*/
@Override
public Predicate toPredicate(@NonNull Root<T> root, @NonNull CriteriaQuery<?> query, @NonNull CriteriaBuilder cb) {
Path<?> path = propertyPath.asPath(root);
Class<?> javaType = path.getJavaType();
SameCalendarDay strategy = getSameCalendarDayStrategy(javaType);
if (strategy == null) {
throw new UnsupportedDatePropertyException(propertyPath, javaType, root);
}
return strategy.toPredicate(path, targetDate, cb);
}
private static SameCalendarDay getSameCalendarDayStrategy(Class<?> propertyType) {
return SAME_CALENDAR_DAY_STRATEGIES
.stream()
.filter(s -> s.supports(propertyType))
.findFirst()
.orElse(null);
}
}