1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
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 * http://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
17 package net.sf.ehcache.search.expression;
18
19 import java.util.Map;
20 import java.util.regex.Pattern;
21
22 import net.sf.ehcache.Element;
23 import net.sf.ehcache.search.SearchException;
24 import net.sf.ehcache.search.attribute.AttributeExtractor;
25
26 /***
27 * A regular expression criteria that matches attribute string values. For non <code>java.lang.String attributes</code>,
28 * the <code>toString()</code> form is used in the comparison. <br>
29 * <br>
30 * Expressions are <b>always case insensitive</b><br>
31 * <br>
32 * The following special characters are supported:<br>
33 * <ul>
34 * <li>'?' - match any one single character
35 * <li>'*' - match any multiple character(s) (including zero)
36 * </ul>
37 * The supported wildcard characters can be escaped with a backslash '\', and a literal backslash can be included with '//'<br>
38 * <br>
39 * WARN: Expressions starting with a leading wildcard character are potentially very expensive (ie. full scan) for indexed caches
40 * <p/>
41 *
42 * @author teck
43 */
44 public class ILike extends BaseCriteria {
45
46 private final String attributeName;
47 private final String regex;
48 private final Pattern pattern;
49
50 /***
51 * Construct a "like" criteria for the given expression
52 *
53 * @param attributeName attribute name
54 * @param regex expression
55 */
56 public ILike(String attributeName, String regex) {
57 if ((attributeName == null) || (regex == null)) {
58 throw new SearchException("Both the attribute name and regex must be non null.");
59 }
60
61 this.attributeName = attributeName;
62 this.regex = regex;
63 this.pattern = convertRegex(regex.trim());
64 }
65
66 /***
67 * Return attribute name.
68 *
69 * @return String attribute name
70 */
71 public String getAttributeName() {
72 return attributeName;
73 }
74
75 /***
76 * Return regex string.
77 *
78 * @return String regex.
79 */
80 public String getRegex() {
81 return regex;
82 }
83
84 private static Pattern convertRegex(final String expr) {
85 if (expr.length() == 0) {
86 throw new SearchException("Zero length regex");
87 }
88
89 StringBuilder javaRegex = new StringBuilder("^");
90
91 boolean escape = false;
92 for (int i = 0; i < expr.length(); i++) {
93 char ch = expr.charAt(i);
94
95 if (escape) {
96 switch (ch) {
97 case '//':
98 case '?':
99 case '*': {
100 javaRegex.append(Pattern.quote(lowerCase(ch)));
101 break;
102 }
103
104 default: {
105 throw new SearchException("Illegal escape character (" + ch + ") in regex: " + expr);
106 }
107 }
108
109 escape = false;
110 } else {
111 switch (ch) {
112 case '//': {
113 escape = true;
114 break;
115 }
116 case '?': {
117 javaRegex.append(".");
118 break;
119 }
120 case '*': {
121 javaRegex.append(".*");
122 break;
123 }
124 default: {
125 javaRegex.append(Pattern.quote(lowerCase(ch)));
126 }
127 }
128 }
129 }
130
131 javaRegex.append("$");
132
133 return Pattern.compile(javaRegex.toString(), Pattern.DOTALL);
134 }
135
136 private static String lowerCase(char ch) {
137
138 return Character.toString(ch).toLowerCase();
139 }
140
141 /***
142 * {@inheritDoc}
143 */
144 public boolean execute(Element e, Map<String, AttributeExtractor> attributeExtractors) {
145 Object value = attributeExtractors.get(attributeName).attributeFor(e, attributeName);
146 if (value == null) {
147 return false;
148 }
149
150 String asString = value.toString().toLowerCase();
151
152 return pattern.matcher(asString).matches();
153 }
154
155 }