View Javadoc
1   /*
2    *    Copyright 2016-2025 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.mybatis.dynamic.sql;
17  
18  import java.util.Collection;
19  import java.util.Objects;
20  import java.util.function.Function;
21  import java.util.function.Predicate;
22  import java.util.function.Supplier;
23  import java.util.stream.Collectors;
24  import java.util.stream.Stream;
25  
26  import org.jspecify.annotations.NonNull;
27  import org.mybatis.dynamic.sql.render.RenderedParameterInfo;
28  import org.mybatis.dynamic.sql.render.RenderingContext;
29  import org.mybatis.dynamic.sql.util.FragmentAndParameters;
30  import org.mybatis.dynamic.sql.util.FragmentCollector;
31  
32  public abstract class AbstractListValueCondition<T> implements RenderableCondition<T> {
33      protected final Collection<T> values;
34  
35      protected AbstractListValueCondition(Collection<T> values) {
36          this.values = Objects.requireNonNull(values);
37      }
38  
39      public final Stream<T> values() {
40          return values.stream();
41      }
42  
43      @Override
44      public boolean isEmpty() {
45          return values.isEmpty();
46      }
47  
48      private <R> Collection<R> applyMapper(Function<? super T, ? extends R> mapper) {
49          Objects.requireNonNull(mapper);
50          return values().map(mapper).collect(Collectors.toList());
51      }
52  
53      private Collection<T> applyFilter(Predicate<? super T> predicate) {
54          Objects.requireNonNull(predicate);
55          return values().filter(predicate).toList();
56      }
57  
58      protected <S extends AbstractListValueCondition<T>> S filterSupport(Predicate<? super T> predicate,
59              Function<Collection<T>, S> constructor, S self, Supplier<S> emptySupplier) {
60          if (isEmpty()) {
61              return self;
62          } else {
63              Collection<T> filtered = applyFilter(predicate);
64              return filtered.isEmpty() ? emptySupplier.get() : constructor.apply(filtered);
65          }
66      }
67  
68      protected <R, S extends AbstractListValueCondition<R>> S mapSupport(Function<? super T, ? extends R> mapper,
69              Function<Collection<R>, S> constructor, Supplier<S> emptySupplier) {
70          if (isEmpty()) {
71              return emptySupplier.get();
72          } else {
73              return constructor.apply(applyMapper(mapper));
74          }
75      }
76  
77      public abstract String operator();
78  
79      @Override
80      public FragmentAndParameters renderCondition(RenderingContext renderingContext, BindableColumn<T> leftColumn) {
81          return values().map(v -> toFragmentAndParameters(v, renderingContext, leftColumn))
82                  .collect(FragmentCollector.collect())
83                  .toFragmentAndParameters(Collectors.joining(",", //$NON-NLS-1$
84                          operator() + " (", ")")); //$NON-NLS-1$ //$NON-NLS-2$
85      }
86  
87      private FragmentAndParameters toFragmentAndParameters(T value, RenderingContext renderingContext,
88                                                            BindableColumn<T> leftColumn) {
89          RenderedParameterInfo parameterInfo = renderingContext.calculateParameterInfo(leftColumn);
90          return FragmentAndParameters.withFragment(parameterInfo.renderedPlaceHolder())
91                  .withParameter(parameterInfo.parameterMapKey(), leftColumn.convertParameterType(value))
92                  .build();
93      }
94  
95      /**
96       * Conditions may implement Filterable to add optionality to rendering.
97       *
98       * <p>If a condition is Filterable, then a user may add a filter to the usage of the condition that makes a decision
99       * whether to render the condition at runtime. Conditions that fail the filter will be dropped from the
100      * rendered SQL.
101      *
102      * <p>Implementations of Filterable may call
103      * {@link AbstractListValueCondition#filterSupport(Predicate, Function, AbstractListValueCondition, Supplier)} as
104      * a common implementation of the filtering algorithm.
105      *
106      * @param <T> the Java type related to the database column type
107      */
108     public interface Filterable<T> {
109         /**
110          * If renderable and the value matches the predicate, returns this condition. Else returns a condition
111          *     that will not render.
112          *
113          * @param predicate predicate applied to the value, if renderable
114          * @return this condition if renderable and the value matches the predicate, otherwise a condition
115          *     that will not render.
116          */
117         AbstractListValueCondition<T> filter(Predicate<? super @NonNull T> predicate);
118     }
119 
120     /**
121      * Conditions may implement Mappable to alter condition values or types during rendering.
122      *
123      * <p>If a condition is Mappable, then a user may add a mapper to the usage of the condition that can alter the
124      * values of a condition, or change that datatype.
125      *
126      * <p>Implementations of Mappable may call
127      * {@link AbstractListValueCondition#mapSupport(Function, Function, Supplier)} as
128      * a common implementation of the mapping algorithm.
129      *
130      * @param <T> the Java type related to the database column type
131      */
132     public interface Mappable<T> {
133         /**
134          * If renderable, apply the mapping to the value and return a new condition with the new value. Else return a
135          * condition that will not render (this).
136          *
137          * @param mapper a mapping function to apply to the value, if renderable
138          * @param <R> type of the new condition
139          * @return a new condition with the result of applying the mapper to the value of this condition,
140          *     if renderable, otherwise a condition that will not render.
141          */
142         <R> AbstractListValueCondition<R> map(Function<? super @NonNull T, ? extends R> mapper);
143     }
144 }