View Javadoc
1   /*
2    *    Copyright 2018-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.scripting.thymeleaf.expression;
17  
18  import java.util.Arrays;
19  import java.util.Optional;
20  import java.util.Set;
21  import java.util.function.Function;
22  import java.util.stream.Collectors;
23  
24  /**
25   * The expression utility object that provide helper method for like feature. <br>
26   * This object can be access using {@code #likes}) as expression utility object.
27   *
28   * @author Kazuki Shimizu
29   *
30   * @version 1.0.0
31   */
32  public class Likes {
33  
34    private char escapeChar = '\\';
35  
36    private Set<Character> additionalEscapeTargetChars = Set.of();
37  
38    private Function<Character, String> escapeClauseSupplier = targetEscapeChar -> "ESCAPE '" + targetEscapeChar + "'";
39  
40    /**
41     * Construct new instance that corresponds with specified configuration.
42     */
43    private Likes() {
44      // NOP
45    }
46  
47    /**
48     * Escape for LIKE condition value. <br>
49     * By default configuration, this method escape the {@code "%"} and {@code "_"} using {@code "\"}.
50     *
51     * @param value
52     *          A target condition value
53     *
54     * @return A escaped value
55     */
56    public String escapeWildcard(String value) {
57      if (value == null || value.isEmpty()) {
58        return "";
59      }
60      StringBuilder sb = new StringBuilder(value.length() + 16);
61      for (char c : value.toCharArray()) {
62        if (c == escapeChar || c == '%' || c == '_' || additionalEscapeTargetChars.contains(c)) {
63          sb.append(escapeChar);
64        }
65        sb.append(c);
66      }
67      return sb.toString();
68    }
69  
70    /**
71     * Return a escape clause string of LIKE. <br>
72     * By default configuration, this method return {@code "ESCAPE '\'"}.
73     *
74     * @return A escape clause string of LIKE
75     */
76    public String escapeClause() {
77      return escapeClauseSupplier.apply(escapeChar);
78    }
79  
80    /**
81     * Creates a new builder instance for {@link Likes}.
82     *
83     * @return a new builder instance
84     */
85    public static Builder newBuilder() {
86      return new Builder();
87    }
88  
89    /**
90     * The builder class for {@link Likes}.
91     */
92    public static class Builder {
93  
94      private final Likes instance = new Likes();
95  
96      private Builder() {
97        // NOP
98      }
99  
100     /**
101      * Set an escape character for wildcard of LIKE. <br>
102      * The default value is {@code '\'} (backslash)
103      *
104      * @param escapeChar
105      *          A escape character
106      *
107      * @return A self instance
108      */
109     public Builder escapeChar(Character escapeChar) {
110       Optional.ofNullable(escapeChar).ifPresent(v -> instance.escapeChar = v);
111       return this;
112     }
113 
114     /**
115      * Set additional escape target characters(custom wildcard characters) for LIKE condition. <br>
116      * The default value is nothing.
117      *
118      * @param additionalEscapeTargetChars
119      *          escape target characters(custom wildcard characters)
120      *
121      * @return A self instance
122      */
123     public Builder additionalEscapeTargetChars(Character... additionalEscapeTargetChars) {
124       Optional.ofNullable(additionalEscapeTargetChars)
125           .ifPresent(v -> instance.additionalEscapeTargetChars = Arrays.stream(v).collect(Collectors.toSet()));
126       return this;
127     }
128 
129     /**
130      * Set a format of escape clause. <br>
131      * The default value is {@code "ESCAPE '%s'"}.
132      *
133      * @param escapeClauseFormat
134      *          a format of escape clause
135      *
136      * @return A self instance
137      */
138     public Builder escapeClauseFormat(String escapeClauseFormat) {
139       Optional.ofNullable(escapeClauseFormat)
140           .ifPresent(v -> instance.escapeClauseSupplier = escapeChar -> String.format(v, escapeChar));
141       return this;
142     }
143 
144     /**
145      * Return a {@link Likes} instance .
146      *
147      * @return A {@link Likes} instance corresponding with specified option
148      */
149     public Likes build() {
150       return instance;
151     }
152 
153   }
154 
155 }