/***************************************************************** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 * * http://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.apache.cayenne.exp; import org.apache.cayenne.CayenneRuntimeException; import org.apache.cayenne.exp.parser.PatternMatchNode; /** * @since 4.0 */ class LikeExpressionHelper { private static final char WILDCARD_SEQUENCE = '%'; private static final char WILDCARD_ONE = '_'; private static final boolean[] ESCAPE_ALPHABET; private static final int ESCAPE_ALPHABET_START = '!'; static { ESCAPE_ALPHABET = new boolean[Byte.MAX_VALUE]; // exclude certain chars, such as unprintable ones, and ? for (int i = ESCAPE_ALPHABET_START; i < Byte.MAX_VALUE; i++) { if (i != '?' && i != '\"' && i != '\'' && i != WILDCARD_SEQUENCE && i != WILDCARD_ONE) { ESCAPE_ALPHABET[i] = true; } } } static void toContains(PatternMatchNode exp) { escape(exp); wrap(exp, true, true); } static void toStartsWith(PatternMatchNode exp) { escape(exp); wrap(exp, false, true); } static void toEndsWith(PatternMatchNode exp) { escape(exp); wrap(exp, true, false); } static void escape(PatternMatchNode exp) { Object pattern = exp.getOperand(1); if (pattern instanceof String) { // find _ or % and then attempt to escape... String pString = pattern.toString(); int len = pString.length(); for (int i = 0; i < len; i++) { char c = pString.charAt(i); if (c == WILDCARD_SEQUENCE || c == WILDCARD_ONE) { exp.setOperand(1, escapeFrom(exp, pString, i, len)); break; } } } } private static String escapeFrom(PatternMatchNode exp, String pattern, int firstWildcard, int len) { boolean[] mutableEscapeAlphabet = new boolean[Byte.MAX_VALUE]; System.arraycopy(ESCAPE_ALPHABET, ESCAPE_ALPHABET_START, mutableEscapeAlphabet, ESCAPE_ALPHABET_START, Byte.MAX_VALUE - ESCAPE_ALPHABET_START); // can't use chars already in the pattern, so exclude the ones already // taken for (int i = 0; i < len; i++) { char c = pattern.charAt(i); if (c < Byte.MAX_VALUE) { mutableEscapeAlphabet[c] = false; } } // find the first available char char escapeChar = 0; for (int i = ESCAPE_ALPHABET_START; i < Byte.MAX_VALUE; i++) { if (mutableEscapeAlphabet[i]) { escapeChar = (char) i; break; } } if (escapeChar == 0) { // if we start seeing this this error in the wild, I guess we'll // need to extend escape char set beyond ASCII throw new CayenneRuntimeException("Could not properly escape pattern: %s", pattern); } exp.setEscapeChar(escapeChar); // build escaped pattern StringBuilder buffer = new StringBuilder(len + 1); buffer.append(pattern.substring(0, firstWildcard)); buffer.append(escapeChar).append(pattern.charAt(firstWildcard)); for (int i = firstWildcard + 1; i < len; i++) { char c = pattern.charAt(i); if (c == WILDCARD_SEQUENCE || c == WILDCARD_ONE) { buffer.append(escapeChar); } buffer.append(c); } return buffer.toString(); } private static void wrap(PatternMatchNode exp, boolean start, boolean end) { Object pattern = exp.getOperand(1); if (pattern instanceof String) { StringBuilder buffer = new StringBuilder(); if (start) { buffer.append(WILDCARD_SEQUENCE); } buffer.append(pattern); if (end) { buffer.append(WILDCARD_SEQUENCE); } exp.setOperand(1, buffer.toString()); } } }