All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.dellroad.querystream.jpa.SearchStreamImpl Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2018 Archie L. Cobbs. All rights reserved.
 */

package org.dellroad.querystream.jpa;

import jakarta.persistence.EntityManager;
import jakarta.persistence.FlushModeType;
import jakarta.persistence.LockModeType;
import jakarta.persistence.Parameter;
import jakarta.persistence.TemporalType;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SingularAttribute;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

import org.dellroad.querystream.jpa.querytype.SearchType;

class SearchStreamImpl>
  extends QueryStreamImpl, CriteriaQuery, TypedQuery, SearchType>
  implements SearchStream {

// Constructors

    SearchStreamImpl(EntityManager entityManager, SearchType queryType,
      QueryConfigurer, X, ? extends S> configurer, QueryInfo queryInfo) {
        super(entityManager, queryType, configurer, queryInfo);
    }

// Superclass overrides

    @Override
    SearchStream create(EntityManager entityManager, SearchType queryType,
      QueryConfigurer, X, ? extends S> configurer, QueryInfo queryInfo) {
        return new SearchStreamImpl<>(entityManager, queryType, configurer, queryInfo);
    }

    @Override
    SearchStream withConfig(QueryConfigurer, X, ? extends S> configurer) {
        return (SearchStream)super.withConfig(configurer);
    }

    @Override
    final CriteriaQuery select(CriteriaQuery query, S selection) {
        return query.select(selection);
    }

// Subclass required methods

    SearchValue toValue() {
        return this.toValue(false);
    }

    SearchValue toValue(boolean forceLimit) {
        return new SearchValueImpl<>(this.entityManager, this.queryType,
          this.configurer, forceLimit ? this.queryInfo.withMaxResults(1) : this.queryInfo);
    }

// CriteriaQuery stuff

    @Override
    public SearchStream distinct() {
        QueryStreamImpl.checkOffsetLimit(this, "distinct()");
        return this.modQuery((query, selection) -> query.distinct(true));
    }

    @Override
    public SearchStream orderBy(Ref> ref, boolean asc) {
        if (ref == null)
            throw new IllegalArgumentException("null ref");
        return this.orderBy(selection -> ref.get(), asc, false);
    }

    @Override
    public SearchStream thenOrderBy(Ref> ref, boolean asc) {
        if (ref == null)
            throw new IllegalArgumentException("null ref");
        return this.orderBy(selection -> ref.get(), asc, true);
    }

    @Override
    public SearchStream orderBy(Order... orders) {
        if (orders == null)
            throw new IllegalArgumentException("null orders");
        return this.orderByMulti(selection -> Arrays.asList(orders), false);
    }

    @Override
    public SearchStream thenOrderBy(Order... orders) {
        if (orders == null)
            throw new IllegalArgumentException("null orders");
        return this.orderByMulti(selection -> Arrays.asList(orders), true);
    }

    @Override
    public SearchStream orderBy(SingularAttribute attribute, boolean asc) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        return this.orderBy(selection -> ((Path)selection).get(attribute), asc, false);  // cast is valid if attribute exists
    }

    @Override
    public SearchStream thenOrderBy(SingularAttribute attribute, boolean asc) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        return this.orderBy(selection -> ((Path)selection).get(attribute), asc, true);   // cast is valid if attribute exists
    }

    @Override
    public SearchStream orderBy(
      SingularAttribute attribute1, boolean asc1,
      SingularAttribute attribute2, boolean asc2) {
        if (attribute1 == null)
            throw new IllegalArgumentException("null attribute1");
        if (attribute2 == null)
            throw new IllegalArgumentException("null attribute2");
        return this.orderByMulti(selection -> {
            final Path path = (Path)selection;                                    // cast must be valid if attribute exists
            return Arrays.asList(
              asc1 ? this.builder().asc(path.get(attribute1)) : this.builder().desc(path.get(attribute1)),
              asc2 ? this.builder().asc(path.get(attribute2)) : this.builder().desc(path.get(attribute2)));
        }, false);
    }

    @Override
    public SearchStream orderBy(
      SingularAttribute attribute1, boolean asc1,
      SingularAttribute attribute2, boolean asc2,
      SingularAttribute attribute3, boolean asc3) {
        if (attribute1 == null)
            throw new IllegalArgumentException("null attribute1");
        if (attribute2 == null)
            throw new IllegalArgumentException("null attribute2");
        if (attribute3 == null)
            throw new IllegalArgumentException("null attribute3");
        return this.orderByMulti(selection -> {
            final Path path = (Path)selection;                                    // cast must be valid if attribute exists
            return Arrays.asList(
              asc1 ? this.builder().asc(path.get(attribute1)) : this.builder().desc(path.get(attribute1)),
              asc2 ? this.builder().asc(path.get(attribute2)) : this.builder().desc(path.get(attribute2)),
              asc3 ? this.builder().asc(path.get(attribute3)) : this.builder().desc(path.get(attribute3)));
        }, false);
    }

    @Override
    public SearchStream orderBy(Function> orderExprFunction, boolean asc) {
        return this.orderBy(orderExprFunction, asc, false);
    }

    @Override
    public SearchStream thenOrderBy(Function> orderExprFunction, boolean asc) {
        return this.orderBy(orderExprFunction, asc, true);
    }

    private SearchStream orderBy(Function> orderExprFunction, boolean asc, boolean add) {
        if (orderExprFunction == null)
            throw new IllegalArgumentException("null orderExprFunction");
        return this.orderByMulti(selection -> {
            final Expression expr = orderExprFunction.apply(selection);
            return Collections.singletonList(asc ? this.builder().asc(expr) : this.builder().desc(expr));
        }, add);
    }

    @Override
    public SearchStream orderByMulti(Function> orderListFunction) {
        return this.orderByMulti(orderListFunction, false);
    }

    private SearchStream orderByMulti(Function> orderListFunction, boolean add) {
        if (orderListFunction == null)
            throw new IllegalArgumentException("null orderListFunction");
        QueryStreamImpl.checkOffsetLimit(this, "sorting");
        return this.withConfig((builder, query) -> {
            if (!(query instanceof CriteriaQuery)) {
                throw new UnsupportedOperationException("sorry, can't sort a subquery because"
                  + " the JPA Criteria API doesn't support sorting in subqueries");
            }
            final CriteriaQuery criteriaQuery = (CriteriaQuery)query;
            final S selection = this.configure(builder, query);
            final ArrayList newOrders = new ArrayList<>();
            if (add)
                newOrders.addAll(criteriaQuery.getOrderList());
            newOrders.addAll(orderListFunction.apply(selection));
            criteriaQuery.orderBy(newOrders);
            return selection;
        });
    }

    @Override
    public SearchStream groupBy(Ref> ref) {
        if (ref == null)
            throw new IllegalArgumentException("null ref");
        return this.groupBy(selection -> ref.get());
    }

    @Override
    public SearchStream groupBy(SingularAttribute attribute) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        return this.groupBy(selection -> ((Path)selection).get(attribute));           // cast must be valid if attribute exists
    }

    @Override
    public SearchStream groupBy(Function> groupFunction) {
        if (groupFunction == null)
            throw new IllegalArgumentException("null groupFunction");
        return this.groupByMulti(selection -> Collections.singletonList(groupFunction.apply(selection)));
    }

    @Override
    public SearchStream groupByMulti(Function>> groupFunction) {
        if (groupFunction == null)
            throw new IllegalArgumentException("null groupFunction");
        QueryStreamImpl.checkOffsetLimit(this, "grouping");
        return this.withConfig((builder, query) -> {
            final S selection = this.configure(builder, query);
            query.groupBy(groupFunction.apply(selection));
            return selection;
        });
    }

    @Override
    public SearchStream having(Function> havingFunction) {
        if (havingFunction == null)
            throw new IllegalArgumentException("null havingFunction");
        QueryStreamImpl.checkOffsetLimit(this, "grouping");
        return this.withConfig((builder, query) -> {
            final S selection = this.configure(builder, query);
            query.having(havingFunction.apply(selection));
            return selection;
        });
    }

// Streamy stuff

    @Override
    public boolean allMatch(SingularAttribute attribute) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        QueryStreamImpl.checkOffsetLimit(this, "allMatch()");
        return !this.withConfig((builder, query) -> {
            final S selection = this.configure(builder, query);
            this.and(builder, query, builder.not(((Path)selection).get(attribute))); // cast must be valid if attribute exists
            return selection;
        }).getResultStream().findAny().isPresent();
    }

    @Override
    public boolean allMatch(Function> predicateBuilder) {
        if (predicateBuilder == null)
            throw new IllegalArgumentException("null predicateBuilder");
        QueryStreamImpl.checkOffsetLimit(this, "allMatch()");
        return !this.withConfig((builder, query) -> {
            final S selection = this.configure(builder, query);
            this.and(builder, query, builder.not(predicateBuilder.apply(selection)));
            return selection;
        }).getResultStream().findAny().isPresent();
    }

    @Override
    public boolean anyMatch(SingularAttribute attribute) {
        return this.filter(attribute).getResultStream().findAny().isPresent();
    }

    @Override
    public boolean anyMatch(Function> predicateBuilder) {
        return this.filter(predicateBuilder).getResultStream().findAny().isPresent();
    }

    @Override
    public boolean noneMatch(SingularAttribute attribute) {
        return !this.anyMatch(attribute);
    }

    @Override
    public boolean noneMatch(Function> predicateBuilder) {
        return !this.anyMatch(predicateBuilder);
    }

    @Override
    public boolean isEmpty() {
        return !this.getResultStream().findAny().isPresent();
    }

    @Override
    public SearchValue findAny() {
        return this instanceof SearchValue ? (SearchValue)this : this.toValue(true);
    }

    @Override
    public SearchValue findFirst() {
        return this instanceof SearchValue ? (SearchValue)this : this.toValue(true);
    }

    @Override
    public SearchValue findSingle() {
        return this.toValue(false);
    }

// Binding

    @Override
    public  SearchStream addRoot(Ref> ref, Class type) {
        if (ref == null)
            throw new IllegalArgumentException("null ref");
        if (type == null)
            throw new IllegalArgumentException("null type");
        QueryStreamImpl.checkOffsetLimit(this, "roots must be added prior to skip() or limit()");
        return this.withConfig((builder, query) -> {
            final S selection = this.configure(builder, query);
            ref.bind(query.from(type));
            return selection;
        });
    }

// Fetches

    @Override
    public SearchStream fetch(SingularAttribute attribute) {
        return this.fetch(attribute, JoinType.INNER);
    }

    @Override
    public SearchStream fetch(SingularAttribute attribute, JoinType joinType) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        if (joinType == null)
            throw new IllegalArgumentException("null joinType");
        throw new UnsupportedOperationException();      // this should never happen if "attribute" is really valid
    }

    @Override
    public SearchStream fetch(PluralAttribute attribute) {
        return this.fetch(attribute, JoinType.INNER);
    }

    @Override
    public SearchStream fetch(PluralAttribute attribute, JoinType joinType) {
        if (attribute == null)
            throw new IllegalArgumentException("null attribute");
        if (joinType == null)
            throw new IllegalArgumentException("null joinType");
        throw new UnsupportedOperationException();      // this should never happen if "attribute" is really valid
    }

// Narrowing overrides (QueryStreamImpl)

    @Override
    public SearchType getQueryType() {
        return this.queryType;
    }

    @Override
    SearchStream modQuery(BiConsumer, ? super S> modifier) {
        return (SearchStream)super.modQuery(modifier);
    }

    @Override
    public SearchStream bind(Ref ref) {
        return (SearchStream)super.bind(ref);
    }

    @Override
    public SearchStream peek(Consumer peeker) {
        return (SearchStream)super.peek(peeker);
    }

    @Override
    public > SearchStream bind(
      Ref ref, Function refFunction) {
        return (SearchStream)super.bind(ref, refFunction);
    }

    @Override
    public SearchStream filter(SingularAttribute attribute) {
        return (SearchStream)super.filter(attribute);
    }

    @Override
    public SearchStream filter(Function> predicateBuilder) {
        return (SearchStream)super.filter(predicateBuilder);
    }

    @Override
    public SearchStream limit(int limit) {
        return (SearchStream)super.limit(limit);
    }

    @Override
    public SearchStream skip(int skip) {
        return (SearchStream)super.skip(skip);
    }

    @Override
    public SearchStream withFlushMode(FlushModeType flushMode) {
        return (SearchStream)super.withFlushMode(flushMode);
    }

    @Override
    public SearchStream withLockMode(LockModeType lockMode) {
        return (SearchStream)super.withLockMode(lockMode);
    }

    @Override
    public SearchStream withHint(String name, Object value) {
        return (SearchStream)super.withHint(name, value);
    }

    @Override
    public SearchStream withHints(Map hints) {
        return (SearchStream)super.withHints(hints);
    }

    @Override
    public  SearchStream withParam(Parameter parameter, T value) {
        return (SearchStream)super.withParam(parameter, value);
    }

    @Override
    public SearchStream withParam(Parameter parameter, Date value, TemporalType temporalType) {
        return (SearchStream)super.withParam(parameter, value, temporalType);
    }

    @Override
    public SearchStream withParam(Parameter parameter, Calendar value, TemporalType temporalType) {
        return (SearchStream)super.withParam(parameter, value, temporalType);
    }

    @Override
    public SearchStream withParams(Iterable> params) {
        return (SearchStream)super.withParams(params);
    }

    @Override
    public SearchStream withLoadGraph(String name) {
        return (SearchStream)super.withLoadGraph(name);
    }

    @Override
    public SearchStream withFetchGraph(String name) {
        return (SearchStream)super.withFetchGraph(name);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy