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