1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package org.apache.myfaces.trinidad.validator;
20
21 import java.util.regex.Matcher;
22 import java.util.regex.Pattern;
23 import java.util.regex.PatternSyntaxException;
24
25 import javax.el.ValueExpression;
26
27 import javax.faces.application.FacesMessage;
28 import javax.faces.component.StateHolder;
29 import javax.faces.component.UIComponent;
30 import javax.faces.context.FacesContext;
31 import javax.faces.el.ValueBinding;
32 import javax.faces.validator.Validator;
33 import javax.faces.validator.ValidatorException;
34
35 import org.apache.myfaces.trinidad.bean.FacesBean;
36 import org.apache.myfaces.trinidad.bean.PropertyKey;
37 import org.apache.myfaces.trinidad.util.ComponentUtils;
38 import org.apache.myfaces.trinidad.util.MessageFactory;
39 import org.apache.myfaces.trinidad.logging.TrinidadLogger;
40
41 /**
42 <p><strong>RegExpValidator</strong> is a {@link javax.faces.validator.Validator} that checks
43 * the value of the corresponding component against specified pattern
44 * using Java regular expression syntax.
45 *
46 * The regular expression syntax accepted by the RegExpValidator class is
47 * same as mentioned in class {@link java.util.regex.Pattern} in package
48 * <code>java.util.regex</code>. The following algorithm is implemented:</p>
49 *
50 * <ul>
51 * <li>If the passed value is <code>null</code>, exit immediately.</li>
52 *
53 * <li>If a <code>pattern</code> property has been configured on this
54 * {@link javax.faces.validator.Validator}, check the component value against this pattern.
55 * If value does not match pattern throw a {@link ValidatorException}
56 * containing a NO_MATCH_MESSAGE_ID message.
57 * If <code>noMatchMessageDetail</code> is set, it is used for constructing faces
58 * message. The message can contain placeholders which will be replaced as
59 * specified in {@link #NO_MATCH_MESSAGE_ID}</li>
60 * </ul>
61 * @see #setMessageDetailNoMatch(String)
62 *
63 * @version $Name: $ ($Revision: adfrt/faces/adf-faces-api/src/main/java/oracle/adf/view/faces/validator/RegExpValidator.java#0 $) $Date: 10-nov-2005.19:08:34 $
64 */
65 public class RegExpValidator implements StateHolder, Validator
66 {
67 /**
68 * <p>Standard validator id for this validator.</p>
69 */
70 public static final String VALIDATOR_ID = "org.apache.myfaces.trinidad.RegExp";
71
72 /**
73 * <p>The message identifier of the {@link FacesMessage}
74 * to be created if the match fails. The message format
75 * string for this message may optionally include a <code>{0}</code>,
76 * <code>{1}</code> and <code>{4}</code> placeholders, which will be replaced
77 * input value, label associated with the component and pattern respectively.</p>
78 */
79 public static final String NO_MATCH_MESSAGE_ID
80 = "org.apache.myfaces.trinidad.validator.RegExpValidator.NO_MATCH";
81
82 /**
83 * <p>Construct a RegExpValidator with no preconfigured pattern.</p>
84 */
85 public RegExpValidator()
86 {
87 super();
88 }
89
90 /**
91 * <p>Construct a RegExpValidator with preconfigured pattern.</p>
92 */
93 public RegExpValidator(String pattern)
94 {
95 setPattern(pattern);
96 }
97
98 /**
99 * @exception ValidatorException if validation fails
100 * @exception NullPointerException if <code>context</code>
101 * or <code>component</code> or <code>pattern</code> is <code>null</code>
102 * @exception IllegalArgumentException if <code>value</code> is not of type
103 * {@link java.lang.String}
104 */
105 public void validate(
106 FacesContext context,
107 UIComponent component,
108 Object value
109 ) throws ValidatorException
110 {
111
112 if ((context == null) || (component == null))
113 {
114 throw new NullPointerException(_LOG.getMessage(
115 "NULL_FACESCONTEXT_OR_UICOMPONENT"));
116 }
117
118 if ( value != null)
119 {
120 ValidatorUtils.assertIsString(value,
121 "'value' is not of type java.lang.String.");
122
123 if (getPattern() == null)
124 throw new NullPointerException(_LOG.getMessage(
125 "NULL_REGEXP_PATTERN"));
126
127
128
129
130 if (_compiled == null)
131 {
132 try
133 {
134 _compiled = Pattern.compile(getPattern());
135 }
136 catch (PatternSyntaxException pse)
137 {
138
139 throw pse;
140 }
141 }
142 String theValue = (String)value;
143 Matcher matcher = _compiled.matcher(theValue);
144
145 if (! matcher.matches())
146 {
147 throw new ValidatorException(_getNoMatchFoundMessage(context,
148 component,
149 theValue));
150 }
151 }
152 }
153
154 public boolean isTransient()
155 {
156 return (_isTransient);
157 }
158
159 public void setTransient(boolean transientValue)
160 {
161 _isTransient = transientValue;
162 }
163
164 public Object saveState(FacesContext context)
165 {
166 return _facesBean.saveState(context);
167 }
168
169 public void restoreState(FacesContext context, Object state)
170 {
171 _facesBean.restoreState(context, state);
172 }
173
174
175 /**
176 * <p>Set the {@link ValueExpression} used to calculate the value for the
177 * specified attribute if any.</p>
178 *
179 * @param name Name of the attribute for which to set a {@link ValueExpression}
180 * @param expression The {@link ValueExpression} to set, or <code>null</code>
181 * to remove any currently set {@link ValueExpression}
182 *
183 * @exception NullPointerException if <code>name</code>
184 * is <code>null</code>
185 * @exception IllegalArgumentException if <code>name</code> is not a valid
186 * attribute of this converter
187 */
188 public void setValueExpression(String name, ValueExpression expression)
189 {
190 ValidatorUtils.setValueExpression(_facesBean, name, expression) ;
191 }
192
193
194 /**
195 * <p>Return the {@link ValueExpression} used to calculate the value for the
196 * specified attribute name, if any.</p>
197 *
198 * @param name Name of the attribute or property for which to retrieve a
199 * {@link ValueExpression}
200 *
201 * @exception NullPointerException if <code>name</code>
202 * is <code>null</code>
203 * @exception IllegalArgumentException if <code>name</code> is not a valid
204 * attribute of this converter
205 */
206 public ValueExpression getValueExpression(String name)
207 {
208 return ValidatorUtils.getValueExpression(_facesBean, name);
209 }
210
211
212 /**
213 * <p>Set the {@link ValueBinding} used to calculate the value for the
214 * specified attribute if any.</p>
215 *
216 * @param name Name of the attribute for which to set a {@link ValueBinding}
217 * @param binding The {@link ValueBinding} to set, or <code>null</code>
218 * to remove any currently set {@link ValueBinding}
219 *
220 * @exception NullPointerException if <code>name</code>
221 * is <code>null</code>
222 * @exception IllegalArgumentException if <code>name</code> is not a valid
223 * attribute of this validator
224 * @deprecated
225 */
226 public void setValueBinding(String name, ValueBinding binding)
227 {
228 ValidatorUtils.setValueBinding(_facesBean, name, binding) ;
229 }
230
231 /**
232 * <p>Return the {@link ValueBinding} used to calculate the value for the
233 * specified attribute name, if any.</p>
234 *
235 * @param name Name of the attribute or property for which to retrieve a
236 * {@link ValueBinding}
237 *
238 * @exception NullPointerException if <code>name</code>
239 * is <code>null</code>
240 * @exception IllegalArgumentException if <code>name</code> is not a valid
241 * attribute of this validator
242 * @deprecated
243 */
244 public ValueBinding getValueBinding(String name)
245 {
246 return ValidatorUtils.getValueBinding(_facesBean, name);
247 }
248
249 /**
250 * <p>Compares this PatternValidator with the specified Object for
251 * equality.</p>
252 * @param object Object to which this PatternValidator is to be compared.
253 * @return true if and only if the specified Object is a PatternValidator
254 * and if the values pattern and transient are equal.
255 */
256 @Override
257 public boolean equals(Object object)
258 {
259 if (this == object)
260 return true;
261
262 if ( object instanceof RegExpValidator )
263 {
264 RegExpValidator other = (RegExpValidator) object;
265
266 if ( this.isTransient() == other.isTransient() &&
267 ValidatorUtils.equals(getPattern(), other.getPattern()) &&
268 ValidatorUtils.equals(getMessageDetailNoMatch(),
269 other.getMessageDetailNoMatch())
270 )
271 {
272 return true;
273 }
274 }
275 return false;
276 }
277
278 /**
279 * <p>Returns the hash code for this Validator.</p>
280 * @return a hash code value for this object.
281 */
282 @Override
283 public int hashCode()
284 {
285 int result = 17;
286 String pattern = getPattern();
287 String noMesgDetail = getMessageDetailNoMatch();
288 result = 37 * result + (pattern == null? 0 : pattern.hashCode());
289 result = 37 * result + (isTransient() ? 0 : 1);
290 result = 37 * result + (noMesgDetail == null ? 0 : noMesgDetail.hashCode());
291 return result;
292 }
293
294 /**
295 * <p>Custom hint message.</p>
296 * Overrides default hint message
297 * @param hintPattern Custom hint message.
298 */
299 public void setHint(String hintPattern)
300 {
301 _facesBean.setProperty(_HINT_PATTERN_KEY, hintPattern);
302 }
303
304 /**
305 * <p>Return custom hint message.</p>
306 * @return Custom hint message.
307 * @see #setHint(String)
308 */
309 public String getHint()
310 {
311 Object obj = _facesBean.getProperty(_HINT_PATTERN_KEY);
312 return ComponentUtils.resolveString(obj);
313 }
314
315 /**
316 * <p>Set the pattern value to be enforced by this {@link
317 * Validator}
318 * @param pattern to be enforced.
319 */
320 public void setPattern(String pattern)
321 {
322 String prevPattern = getPattern();
323 if ((prevPattern != null) && prevPattern.equals(pattern))
324 return;
325 if ((prevPattern == null) && (pattern == null))
326 return;
327
328 _facesBean.setProperty(_PATTERN_KEY, pattern);
329 _compiled = null;
330 }
331
332 /**
333 * <p>Return the pattern value to be enforced by this {@link
334 * Validator}
335 */
336 public String getPattern()
337 {
338 Object obj = _facesBean.getProperty(_PATTERN_KEY);
339 return ComponentUtils.resolveString(obj);
340 }
341
342 /**
343 * <p>Custom error message to be used, for creating detail part of the
344 * {@link FacesMessage}, when value does not match the specified pattern.</p>
345 * Overrides detail message identified by message id {@link #NO_MATCH_MESSAGE_ID}
346 * @param noMatchMessageDetail
347 */
348 public void setMessageDetailNoMatch(String noMatchMessageDetail)
349 {
350 _facesBean.setProperty(_NO_MATCH_MESSAGE_DETAIL_KEY, noMatchMessageDetail);
351 }
352
353 /**
354 * <p>Return custom detail error message that was set for creating faces message,
355 * for values that do not match the specified pattern.</p>
356 * @return Custom error message
357 * @see #setMessageDetailNoMatch(String)
358 */
359 public String getMessageDetailNoMatch()
360 {
361 Object obj = _facesBean.getProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
362 return ComponentUtils.resolveString(obj);
363 }
364
365 /**
366 * @todo custom message should be evaluated lazily and then be used for
367 * displaying message.
368 */
369 private FacesMessage _getNoMatchFoundMessage(
370 FacesContext context,
371 UIComponent component,
372 String value)
373 {
374 Object noMatchMsgDet = _getRawNoMatchMessageDetail();
375 Object label = ValidatorUtils.getComponentLabel(component);
376 Object[] params = {label, value, getPattern()};
377
378 FacesMessage msg =
379 MessageFactory.getMessage(context, NO_MATCH_MESSAGE_ID,
380 noMatchMsgDet, params, label);
381 return msg;
382 }
383
384 private Object _getRawNoMatchMessageDetail()
385 {
386 return _facesBean.getRawProperty(_NO_MATCH_MESSAGE_DETAIL_KEY);
387 }
388
389
390 private static final FacesBean.Type _TYPE = new FacesBean.Type();
391
392 private static final PropertyKey _PATTERN_KEY
393 = _TYPE.registerKey("pattern", String.class);
394
395 private static final PropertyKey _NO_MATCH_MESSAGE_DETAIL_KEY
396 = _TYPE.registerKey("messageDetailNoMatch", String.class);
397
398 private static final PropertyKey _HINT_PATTERN_KEY =
399 _TYPE.registerKey("hint", String.class);
400
401 private FacesBean _facesBean = ValidatorUtils.getFacesBean(_TYPE);
402
403 private transient Pattern _compiled;
404
405 private boolean _isTransient = false;
406
407 private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
408 RegExpValidator.class);
409 }