/*****************************************************************
* 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());
}
}
}