Likes.java

/*
 *    Copyright 2018-2022 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       https://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.mybatis.scripting.thymeleaf.expression;

import java.util.Arrays;
import java.util.Collections;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * The expression utility object that provide helper method for like feature. <br>
 * This object can be access using {@code #likes}) as expression utility object.
 *
 * @author Kazuki Shimizu
 *
 * @version 1.0.0
 */
public class Likes {

  private char escapeChar = '\\';

  private Set<Character> additionalEscapeTargetChars = Collections.emptySet();

  private Function<Character, String> escapeClauseSupplier = targetEscapeChar -> "ESCAPE '" + targetEscapeChar + "'";

  /**
   * Construct new instance that corresponds with specified configuration.
   */
  private Likes() {
    // NOP
  }

  /**
   * Escape for LIKE condition value. <br>
   * By default configuration, this method escape the {@code "%"} and {@code "_"} using {@code "\"}.
   *
   * @param value
   *          A target condition value
   *
   * @return A escaped value
   */
  public String escapeWildcard(String value) {
    if (value == null || value.isEmpty()) {
      return "";
    }
    StringBuilder sb = new StringBuilder(value.length() + 16);
    for (char c : value.toCharArray()) {
      if (c == escapeChar || c == '%' || c == '_' || additionalEscapeTargetChars.contains(c)) {
        sb.append(escapeChar);
      }
      sb.append(c);
    }
    return sb.toString();
  }

  /**
   * Return a escape clause string of LIKE. <br>
   * By default configuration, this method return {@code "ESCAPE '\'"}.
   *
   * @return A escape clause string of LIKE
   */
  public String escapeClause() {
    return escapeClauseSupplier.apply(escapeChar);
  }

  /**
   * Creates a new builder instance for {@link Likes}.
   *
   * @return a new builder instance
   */
  public static Builder newBuilder() {
    return new Builder();
  }

  /**
   * The builder class for {@link Likes}.
   */
  public static class Builder {

    private final Likes instance = new Likes();

    private Builder() {
      // NOP
    }

    /**
     * Set an escape character for wildcard of LIKE. <br>
     * The default value is {@code '\'} (backslash)
     *
     * @param escapeChar
     *          A escape character
     *
     * @return A self instance
     */
    public Builder escapeChar(Character escapeChar) {
      Optional.ofNullable(escapeChar).ifPresent(v -> instance.escapeChar = v);
      return this;
    }

    /**
     * Set additional escape target characters(custom wildcard characters) for LIKE condition. <br>
     * The default value is nothing.
     *
     * @param additionalEscapeTargetChars
     *          escape target characters(custom wildcard characters)
     *
     * @return A self instance
     */
    public Builder additionalEscapeTargetChars(Character... additionalEscapeTargetChars) {
      Optional.ofNullable(additionalEscapeTargetChars)
          .ifPresent(v -> instance.additionalEscapeTargetChars = Arrays.stream(v).collect(Collectors.toSet()));
      return this;
    }

    /**
     * Set a format of escape clause. <br>
     * The default value is {@code "ESCAPE '%s'"}.
     *
     * @param escapeClauseFormat
     *          a format of escape clause
     *
     * @return A self instance
     */
    public Builder escapeClauseFormat(String escapeClauseFormat) {
      Optional.ofNullable(escapeClauseFormat)
          .ifPresent(v -> instance.escapeClauseSupplier = escapeChar -> String.format(v, escapeChar));
      return this;
    }

    /**
     * Return a {@link Likes} instance .
     *
     * @return A {@link Likes} instance corresponding with specified option
     */
    public Likes build() {
      return instance;
    }

  }

}