001: /* SimpleConstraint.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Tue Jun 28 13:58:11 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zul;
020:
021: import java.util.Date;
022: import java.util.Iterator;
023: import java.util.regex.Pattern;
024:
025: import org.zkoss.lang.Classes;
026: import org.zkoss.util.Dates;
027:
028: import org.zkoss.zk.ui.Component;
029: import org.zkoss.zk.ui.UiException;
030: import org.zkoss.zk.ui.WrongValueException;
031:
032: import org.zkoss.zul.mesg.MZul;
033:
034: /**
035: * A simple constraint that you could build based the predefined constants.
036: *
037: * <p>Depending on the component (such as {@link Intbox} and {@link Datebox},
038: * you could combine the flags, such as {@link #NO_POSITIVE} + {@link #NO_ZERO}
039: * to accept only negative number.
040: *
041: * @author tomyeh
042: */
043: public class SimpleConstraint implements Constraint, ClientConstraint,
044: java.io.Serializable {
045: private static final long serialVersionUID = 20070411L;
046:
047: /** Postive numbers are not allowed. */
048: public static final int NO_POSITIVE = 0x0001;
049: /** Negative numbers are not allowed. */
050: public static final int NO_NEGATIVE = 0x0002;
051: /** Zero numbers are not allowed. */
052: public static final int NO_ZERO = 0x0004;
053: /** Empty is not allowed.
054: * If not specified, empty usually means null.
055: */
056: public static final int NO_EMPTY = 0x0100;
057: /**
058: * The value must match inside the data from ListModel only.
059: */
060: public static final int STRICT = 0x0200;
061: /** Date in the future is not allowed. (Only date part is compared)
062: */
063: public static final int NO_FUTURE = NO_POSITIVE;
064: /** Date in the past is not allowed. (Only date part is compared)
065: */
066: public static final int NO_PAST = NO_NEGATIVE;
067: /** Today is not allowed. (Only date part is compared)
068: */
069: public static final int NO_TODAY = NO_ZERO;
070: /** The constraints. A combination of {@link #NO_POSITIVE} and others.
071: */
072: protected int _flags;
073: private Pattern _regex;
074: private String _errmsg;
075:
076: /** Constructs a constraint with flags.
077: *
078: * @param flags a combination of {@link #NO_POSITIVE}, {@link #NO_NEGATIVE},
079: * {@link #NO_ZERO}, and so on.
080: */
081: public SimpleConstraint(int flags) {
082: this (flags, null, null);
083: }
084:
085: /** Constructs a constraint with flags and an error message.
086: *
087: * @param flags a combination of {@link #NO_POSITIVE}, {@link #NO_NEGATIVE},
088: * {@link #NO_ZERO}, and so on.
089: * @param errmsg the error message to display. Ignored if null or empty.
090: */
091: public SimpleConstraint(int flags, String errmsg) {
092: this (flags, null, errmsg);
093: }
094:
095: /** Constructs a regular-expression constraint.
096: *
097: * @param regex ignored if null or empty
098: * @param errmsg the error message to display. Ignored if null or empty.
099: */
100: public SimpleConstraint(String regex, String errmsg) {
101: this (0, regex, errmsg);
102: }
103:
104: /** Constructs a constraint combining regular expression.
105: *
106: * @param flags a combination of {@link #NO_POSITIVE}, {@link #NO_NEGATIVE},
107: * {@link #NO_ZERO}, and so on.
108: * @param regex ignored if null or empty
109: * @param errmsg the error message to display. Ignored if null or empty.
110: */
111: public SimpleConstraint(int flags, String regex, String errmsg) {
112: _flags = flags;
113: _regex = regex == null || regex.length() == 0 ? null : Pattern
114: .compile(regex);
115: _errmsg = errmsg == null || errmsg.length() == 0 ? null
116: : errmsg;
117: }
118:
119: /** Constructs a constraint with a list of constraints separated by comma.
120: *
121: * @param constraint a list of constraints separated by comma.
122: * Example: no positive, no zero
123: * @since 3.0.2
124: */
125: public SimpleConstraint(String constraint) {
126: String regex = null, errmsg = null;
127: l_out: for (int j = 0, k = 0, len = constraint.length(); k >= 0; j = k + 1) {
128: for (;; ++j) {
129: if (j >= len)
130: break l_out; //done
131:
132: char cc = constraint.charAt(j);
133: switch (cc) {
134: case '/':
135: for (k = ++j;; ++k) { //look for ending /
136: if (k >= len) { //no ending /
137: k = -1;
138: break;
139: }
140:
141: cc = constraint.charAt(k);
142: if (cc == '/')
143: break; //ending / found
144: if (cc == '\\')
145: ++k; //skip one
146: }
147: regex = k >= 0 ? constraint.substring(j, k)
148: : constraint.substring(j);
149: continue l_out;
150: case ':':
151: errmsg = constraint.substring(j + 1).trim();
152: break l_out; //done
153: }
154: if (!Character.isWhitespace(cc))
155: break;
156: }
157:
158: String s;
159: for (k = j;; ++k) {
160: if (k >= len) {
161: s = constraint.substring(j);
162: k = -1;
163: break;
164: }
165: final char cc = constraint.charAt(k);
166: if (cc == ',' || cc == ':' || cc == ';' || cc == '/') {
167: s = constraint.substring(j, k);
168: if (cc == ':' || cc == '/')
169: --k;
170: break;
171: }
172: }
173:
174: _flags |= parseConstraint(s.trim().toLowerCase());
175: }
176:
177: _regex = regex == null || regex.length() == 0 ? null : Pattern
178: .compile(regex);
179: _errmsg = errmsg == null || errmsg.length() == 0 ? null
180: : errmsg;
181: }
182:
183: /** Parses a list of constraints from a string to an integer
184: * representing a combination of {@link #NO_POSITIVE} and other flags.
185: *
186: * @param constraint a list of constraints separated by comma.
187: * Example: no positive, no zero
188: */
189: public static final SimpleConstraint getInstance(String constraint) {
190: return new SimpleConstraint(constraint);
191: }
192:
193: /** Parses a constraint into an integer value.
194: * For example, "no positive" is parsed to {@link #NO_POSITIVE}.
195: *
196: * <p>Deriving classes might override this to provide more constraints.
197: *
198: * @since 3.0.2
199: */
200: protected int parseConstraint(String constraint) throws UiException {
201: if (constraint.equals("no positive"))
202: return NO_POSITIVE;
203: else if (constraint.equals("no negative"))
204: return NO_NEGATIVE;
205: else if (constraint.equals("no zero"))
206: return NO_ZERO;
207: else if (constraint.equals("no empty"))
208: return NO_EMPTY;
209: else if (constraint.equals("no future"))
210: return NO_FUTURE;
211: else if (constraint.equals("no past"))
212: return NO_PAST;
213: else if (constraint.equals("no today"))
214: return NO_TODAY;
215: else if (constraint.equals("strict"))
216: return STRICT;
217: else if (constraint.length() > 0)
218: throw new UiException("Unknown constraint: " + constraint);
219: return 0;
220: }
221:
222: /**
223: * Returns the constraint flags, i.e., a combination of
224: * {@link #NO_POSITIVE}, {@link #NO_NEGATIVE}, {@link #STRICT} and others.
225: *
226: * @since 3.0.2
227: */
228: public int getFlags() {
229: return _flags;
230: }
231:
232: //-- Constraint --//
233: public void validate(Component comp, Object value)
234: throws WrongValueException {
235: if (value == null) {
236: if ((_flags & NO_EMPTY) != 0)
237: throw wrongValue(comp, MZul.EMPTY_NOT_ALLOWED);
238: } else if (value instanceof Number) {
239: if ((_flags & (NO_POSITIVE | NO_NEGATIVE | NO_ZERO)) == 0)
240: return; //nothing to check
241:
242: final int cmp = ((Comparable) value).compareTo(Classes
243: .coerce(value.getClass(), null, false)); //compare to zero
244: if (cmp > 0) {
245: if ((_flags & NO_POSITIVE) != 0)
246: throw wrongValue(comp, getMessageForNumberDenied());
247: } else if (cmp == 0) {
248: if ((_flags & NO_ZERO) != 0)
249: throw wrongValue(comp, getMessageForNumberDenied());
250: } else {
251: if ((_flags & NO_NEGATIVE) != 0)
252: throw wrongValue(comp, getMessageForNumberDenied());
253: }
254: } else if (value instanceof String) {
255: final String s = (String) value;
256: if ((_flags & NO_EMPTY) != 0 && s.length() == 0)
257: throw wrongValue(comp, MZul.EMPTY_NOT_ALLOWED);
258: if (_regex != null
259: && !_regex.matcher(s != null ? s : "").matches())
260: throw wrongValue(comp, MZul.ILLEGAL_VALUE);
261: if ((_flags & STRICT) != 0) {
262: if (s.length() > 0 && comp instanceof Combobox) {
263: for (Iterator it = ((Combobox) comp).getItems()
264: .iterator(); it.hasNext();) {
265: final Comboitem ci = (Comboitem) it.next();
266: if (!ci.isDisabled() && ci.isVisible()
267: && s.equalsIgnoreCase(ci.getLabel()))
268: return;
269: }
270: throw wrongValue(comp, MZul.VALUE_NOT_MATCHED);
271: }
272: }
273: } else if (value instanceof Date) {
274: if ((_flags & (NO_FUTURE | NO_PAST | NO_TODAY)) == 0)
275: return;
276: final Date date = Dates.beginOfDate((Date) value, null);
277: final int cmp = date.compareTo(Dates.today());
278: if (cmp > 0) {
279: if ((_flags & NO_FUTURE) != 0)
280: throw wrongValue(comp, getMessageForDateDenied());
281: } else if (cmp == 0) {
282: if ((_flags & NO_TODAY) != 0)
283: throw wrongValue(comp, getMessageForDateDenied());
284: } else {
285: if ((_flags & NO_PAST) != 0)
286: throw wrongValue(comp, getMessageForDateDenied());
287: }
288: }
289: }
290:
291: private WrongValueException wrongValue(Component comp, int errcode) {
292: return _errmsg != null ? new WrongValueException(comp, _errmsg)
293: : new WrongValueException(comp, errcode);
294: }
295:
296: private int getMessageForNumberDenied() {
297: switch (_flags & (NO_POSITIVE | NO_NEGATIVE | NO_ZERO)) {
298: case (NO_POSITIVE | NO_ZERO):
299: return MZul.NO_POSITIVE_ZERO;
300: case (NO_POSITIVE):
301: return MZul.NO_POSITIVE;
302: case (NO_NEGATIVE | NO_ZERO):
303: return MZul.NO_NEGATIVE_ZERO;
304: case (NO_NEGATIVE):
305: return MZul.NO_NEGATIVE;
306: case (NO_ZERO):
307: return MZul.NO_ZERO;
308: case (NO_POSITIVE | NO_NEGATIVE | NO_ZERO):
309: return MZul.NO_POSITIVE_NEGATIVE_ZERO;
310: case (NO_POSITIVE | NO_NEGATIVE):
311: return MZul.NO_POSITIVE_NEGATIVE;
312: }
313: throw new InternalError();
314: }
315:
316: private int getMessageForDateDenied() {
317: switch (_flags & (NO_FUTURE | NO_PAST | NO_TODAY)) {
318: case (NO_FUTURE | NO_TODAY):
319: return MZul.NO_FUTURE_TODAY;
320: case (NO_FUTURE):
321: return MZul.NO_FUTURE;
322: case (NO_PAST | NO_TODAY):
323: return MZul.NO_PAST_TODAY;
324: case (NO_PAST):
325: return MZul.NO_PAST;
326: case (NO_TODAY):
327: return MZul.NO_TODAY;
328: case (NO_FUTURE | NO_PAST | NO_TODAY):
329: return MZul.NO_FUTURE_PAST_TODAY;
330: case (NO_FUTURE | NO_PAST):
331: return MZul.NO_FUTURE_PAST;
332: }
333: throw new InternalError();
334: }
335:
336: //ClientConstraint//
337: public String getClientValidation() {
338: return (_flags & NO_EMPTY) != 0 ? (_flags & STRICT) != 0 ? "zkVld.noEmptyAndStrict"
339: : "zkVld.noEmpty"
340: : (_flags & STRICT) != 0 ? "zkVld.strict" : null;
341: //FUTURE: support more validation in client
342: }
343:
344: public String getErrorMessage(Component comp) {
345: return _errmsg;
346: }
347:
348: public boolean isClientComplete() {
349: return (_flags == 0 || _flags == NO_EMPTY || _flags == STRICT)
350: && _regex == null;
351: }
352: }
|