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.common;
17  
18  import static org.mybatis.dynamic.sql.util.StringUtilities.spaceAfter;
19  
20  import java.util.Objects;
21  import java.util.Optional;
22  import java.util.stream.Collectors;
23  
24  import org.jspecify.annotations.Nullable;
25  import org.mybatis.dynamic.sql.SqlCriterion;
26  import org.mybatis.dynamic.sql.render.RenderingContext;
27  import org.mybatis.dynamic.sql.util.FragmentAndParameters;
28  import org.mybatis.dynamic.sql.util.FragmentCollector;
29  import org.mybatis.dynamic.sql.where.render.CriterionRenderer;
30  import org.mybatis.dynamic.sql.where.render.RenderedCriterion;
31  
32  public abstract class AbstractBooleanExpressionRenderer {
33      protected final AbstractBooleanExpressionModel model;
34      private final String prefix;
35      private final CriterionRenderer criterionRenderer;
36      protected final RenderingContext renderingContext;
37  
38      protected AbstractBooleanExpressionRenderer(String prefix, AbstractBuilder<?> builder) {
39          model = Objects.requireNonNull(builder.model);
40          this.prefix = Objects.requireNonNull(prefix);
41          renderingContext = Objects.requireNonNull(builder.renderingContext);
42          criterionRenderer = new CriterionRenderer(renderingContext);
43      }
44  
45      public Optional<FragmentAndParameters> render() {
46          return model.initialCriterion()
47                  .map(this::renderWithInitialCriterion)
48                  .orElseGet(this::renderWithoutInitialCriterion)
49                  .map(RenderedCriterion::fragmentAndParameters);
50      }
51  
52      private Optional<RenderedCriterion> renderWithInitialCriterion(SqlCriterion initialCriterion) {
53          return criterionRenderer.render(initialCriterion, model.subCriteria(), this::calculateClause);
54      }
55  
56      private Optional<RenderedCriterion> renderWithoutInitialCriterion() {
57          return criterionRenderer.render(model.subCriteria(), this::calculateClause);
58      }
59  
60      private String calculateClause(FragmentCollector collector) {
61          if (collector.hasMultipleFragments()) {
62              return collector.collectFragments(
63                      Collectors.joining(" ", spaceAfter(prefix), "")); //$NON-NLS-1$ //$NON-NLS-2$
64          } else {
65              return collector.firstFragment()
66                      .map(this::stripEnclosingParenthesesIfPresent)
67                      .map(this::addPrefix)
68                      .orElse(""); //$NON-NLS-1$
69          }
70      }
71  
72      private String stripEnclosingParenthesesIfPresent(String fragment) {
73          // The fragment will have surrounding open/close parentheses if there is more than one rendered condition.
74          // Since there is only a single fragment, we don't need these in the final rendered clause
75          if (fragment.startsWith("(") && fragment.endsWith(")")) { //$NON-NLS-1$ //$NON-NLS-2$
76              return fragment.substring(1, fragment.length() - 1);
77          } else {
78              return fragment;
79          }
80      }
81  
82      private String addPrefix(String fragment) {
83          return spaceAfter(prefix) + fragment;
84      }
85  
86      public abstract static class AbstractBuilder<B extends AbstractBuilder<B>> {
87          private final AbstractBooleanExpressionModel model;
88          private @Nullable RenderingContext renderingContext;
89  
90          protected AbstractBuilder(AbstractBooleanExpressionModel model) {
91              this.model = model;
92          }
93  
94          public B withRenderingContext(RenderingContext renderingContext) {
95              this.renderingContext = renderingContext;
96              return getThis();
97          }
98  
99          protected abstract B getThis();
100     }
101 }