001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.controls.runtime.bean;
020:
021: import java.lang.annotation.Annotation;
022: import java.lang.reflect.Method;
023: import java.net.MalformedURLException;
024: import java.net.URI;
025: import java.net.URL;
026: import java.text.ParseException;
027: import java.text.SimpleDateFormat;
028: import java.text.ParsePosition;
029: import java.util.Date;
030:
031: import org.apache.beehive.controls.api.bean.AnnotationMemberTypes;
032: import org.apache.beehive.controls.api.bean.AnnotationConstraints.MembershipRule;
033: import org.apache.beehive.controls.api.bean.AnnotationConstraints.MembershipRuleValues;
034: import org.apache.beehive.controls.api.properties.PropertyKey;
035:
036: /**
037: * This class offers methods for validating values assigned to a control property.
038: * The validation process will ensure
039: * 1. The value is appropriate for the property's property type
040: * 2. The value satisfies the constraints defined on the property type
041: * 3. The value satisfies the constraints defined on the property set that the property is defined in.
042: * Refer to {@link org.apache.beehive.controls.api.bean.AnnotationMemberTypes AnnotationMemberTypes} and
043: * {@link org.apache.beehive.controls.api.bean.AnnotationConstraints AnnotationConstraints} for more
044: * information on property constraints.
045: */
046: public class AnnotationConstraintValidator {
047:
048: public AnnotationConstraintValidator() {
049: super ();
050: }
051:
052: /**
053: * This method ensures that any control property value assignment satisfies
054: * all property constraints. This method should be called by control
055: * property setters to ensure values assigned to properties at runtime are
056: * validated.
057: *
058: * @param key
059: * The property that the specified key is assigned to
060: * @param value
061: * The value assigned to the specified property key
062: * @throws IllegalArgumentException
063: * when the value assigned to the specified property key does
064: * not satisfy a property constraint.
065: */
066: public static void validate(PropertyKey key, Object value)
067: throws IllegalArgumentException {
068: validate(key.getAnnotations(), value);
069: }
070:
071: /**
072: * This method ensures the membership constraints defined on a property set
073: * is satisfied.
074: *
075: * @param propertySet the property set to validate
076: */
077: public static void validateMembership(Annotation propertySet) {
078: Class c = propertySet.annotationType();
079: MembershipRule rule = (MembershipRule) c
080: .getAnnotation(MembershipRule.class);
081: if (rule == null)
082: return;
083: MembershipRuleValues ruleValue = rule.value();
084: String[] memberNames = rule.memberNames();
085: Method[] members = getMembers(c, memberNames);
086: int i = getNumOfMembersSet(propertySet, members);
087: if (ruleValue == MembershipRuleValues.ALL_IF_ANY) {
088: if (i != 0 && i != members.length)
089: throw new IllegalArgumentException(
090: "The membership rule on "
091: + propertySet.toString()
092: + " is not satisfied. Either all members must be set or none is set");
093: } else if (ruleValue == MembershipRuleValues.EXACTLY_ONE) {
094: if (i != 1)
095: throw new IllegalArgumentException(
096: "The membership rule on "
097: + propertySet.toString()
098: + " is not satisfied. Exactly one member must be set");
099: } else if (ruleValue == MembershipRuleValues.AT_LEAST_ONE) {
100: if (i < 1)
101: throw new IllegalArgumentException(
102: "The membership rule on "
103: + propertySet.toString()
104: + " is not satisfied. At least one member must be set");
105: } else if (ruleValue == MembershipRuleValues.AT_MOST_ONE) {
106: if (i > 1)
107: throw new IllegalArgumentException(
108: "The membership rule on "
109: + propertySet.toString()
110: + " is not satisfied. At most one member may be set");
111: }
112: }
113:
114: private static Method[] getMembers(Class<? extends Annotation> c,
115: String[] memberNames) {
116: Method[] methods = null;
117: if (memberNames == null || memberNames.length == 0) {
118: methods = c.getDeclaredMethods();
119: } else {
120: methods = new Method[memberNames.length];
121: for (int i = 0; i < memberNames.length; i++) {
122: try {
123: methods[i] = c.getMethod(memberNames[i],
124: (Class[]) null);
125: } catch (Exception e) {
126: // method is not found, so the member is ignored.
127: }
128: }
129: }
130: return methods;
131: }
132:
133: private static int getNumOfMembersSet(Annotation propertySet,
134: Method[] members) {
135: int num = 0;
136: for (Method m : members) {
137: Class returnType = m.getReturnType();
138: Object o = null;
139: try {
140: o = m.invoke(propertySet, (Object[]) null);
141: } catch (Exception e) {
142: // This should never happen.
143: throw new RuntimeException(e);
144: }
145:
146: if ((returnType == String.class && !((String) o)
147: .equals(AnnotationMemberTypes.OPTIONAL_STRING))
148: || (returnType == int.class && ((Integer) o)
149: .intValue() != AnnotationMemberTypes.OPTIONAL_INT)
150: || (returnType == short.class && ((Short) o)
151: .shortValue() != AnnotationMemberTypes.OPTIONAL_SHORT)
152: || (returnType == long.class && ((Long) o)
153: .longValue() != AnnotationMemberTypes.OPTIONAL_LONG)
154: || (returnType == float.class && ((Float) o)
155: .floatValue() != AnnotationMemberTypes.OPTIONAL_FLOAT)
156: || (returnType == double.class && ((Double) o)
157: .doubleValue() != AnnotationMemberTypes.OPTIONAL_DOUBLE)
158: || (returnType == char.class && ((Character) o)
159: .charValue() != AnnotationMemberTypes.OPTIONAL_CHAR)
160: || (returnType == byte.class && ((Byte) o)
161: .byteValue() != AnnotationMemberTypes.OPTIONAL_BYTE)
162: || (returnType == boolean.class && !((Boolean) o)
163: .booleanValue()))
164: num++;
165: }
166: return num;
167: }
168:
169: protected static synchronized void validate(
170: Annotation[] annotations, Object value)
171: throws IllegalArgumentException {
172:
173: // Determine if the member is optional. This is done in a separate loop
174: // because a control property may have multiple constraints and the
175: // optional
176: // annotation may be declared after another constraint annotation.
177: boolean optional = false;
178: for (Annotation a : annotations) {
179: if (a instanceof AnnotationMemberTypes.Optional) {
180: optional = true;
181: break;
182: }
183: }
184:
185: for (Annotation a : annotations) {
186: if (a instanceof AnnotationMemberTypes.Text)
187: validateText((AnnotationMemberTypes.Text) a, value,
188: optional);
189: else if (a instanceof AnnotationMemberTypes.Decimal)
190: validateDecimal((AnnotationMemberTypes.Decimal) a,
191: value, optional);
192: else if (a instanceof AnnotationMemberTypes.Int)
193: validateInt((AnnotationMemberTypes.Int) a, value,
194: optional);
195: else if (a instanceof AnnotationMemberTypes.Date)
196: validateDate((AnnotationMemberTypes.Date) a, value,
197: optional);
198: else if (a instanceof AnnotationMemberTypes.FilePath)
199: validateFilePath((AnnotationMemberTypes.FilePath) a,
200: value, optional);
201: else if (a instanceof AnnotationMemberTypes.JndiName)
202: validateJndiName((AnnotationMemberTypes.JndiName) a,
203: value, optional);
204: else if (a instanceof AnnotationMemberTypes.QName)
205: validateQName((AnnotationMemberTypes.QName) a, value,
206: optional);
207: else if (a instanceof AnnotationMemberTypes.URI)
208: validateURI((AnnotationMemberTypes.URI) a, value,
209: optional);
210: else if (a instanceof AnnotationMemberTypes.URL)
211: validateURL((AnnotationMemberTypes.URL) a, value,
212: optional);
213: else if (a instanceof AnnotationMemberTypes.URN)
214: validateURN((AnnotationMemberTypes.URN) a, value,
215: optional);
216: else if (a instanceof AnnotationMemberTypes.XML)
217: validateXML((AnnotationMemberTypes.XML) a, value,
218: optional);
219: }
220: }
221:
222: private static void validateXML(AnnotationMemberTypes.XML a,
223: Object value, boolean optional) {
224: }
225:
226: private static void validateURN(AnnotationMemberTypes.URN a,
227: Object value, boolean optional) {
228: if (optional
229: && (value == null || value
230: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
231: return;
232:
233: if (!(value instanceof String)) {
234: error("The value, "
235: + value
236: + ", assigned to an URN property must be of type java.lang.String.");
237: }
238:
239: URI.create((String) value);
240: }
241:
242: private static void validateURL(AnnotationMemberTypes.URL a,
243: Object value, boolean optional) {
244: if (optional
245: && (value == null || value
246: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
247: return;
248:
249: if (!(value instanceof String)) {
250: error("The value, "
251: + value
252: + ", assigned to an URL property must be of type java.lang.String.");
253: }
254:
255: try {
256: new URL((String) value);
257: } catch (MalformedURLException mue) {
258: error(
259: "The value, "
260: + value
261: + ", assigned to the URL property is a malformed URL.",
262: mue);
263: }
264: }
265:
266: private static void validateURI(AnnotationMemberTypes.URI a,
267: Object value, boolean optional) {
268: if (optional
269: && (value == null || value
270: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
271: return;
272:
273: if (!(value instanceof String)) {
274: error("The value, "
275: + value
276: + ", assigned to an URI property must be of type java.lang.String.");
277: }
278:
279: URI.create((String) value);
280: }
281:
282: private static void validateQName(AnnotationMemberTypes.QName a,
283: Object value, boolean optional) {
284: }
285:
286: private static void validateJndiName(
287: AnnotationMemberTypes.JndiName a, Object value,
288: boolean optional) {
289: }
290:
291: private static void validateFilePath(
292: AnnotationMemberTypes.FilePath a, Object value,
293: boolean optional) {
294: if (optional
295: && (value == null || value
296: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
297: return;
298:
299: if (!(value instanceof String)) {
300: error("The value, "
301: + value
302: + ", assigned to a FilePath property must be of type java.lang.String.");
303: }
304:
305: //Temporarily commenting out the following check on FilePath until
306: //an agreement is reached on what is a valid FilePath.
307: //
308: // File file = new File((String) value);
309: // if (!file.isFile() || !file.canRead())
310: // {
311: // error("The value, "
312: // + value
313: // + ", assigned to a FilePath property must be a readable file.");
314: // }
315:
316: }
317:
318: private static void validateDate(AnnotationMemberTypes.Date a,
319: Object value, boolean optional) {
320:
321: if (optional
322: && (value == null || value
323: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
324: return;
325:
326: if (!(value instanceof String))
327: error("The value, "
328: + value
329: + ", assigned to a date property must be of type java.lang.String.");
330:
331: String format = a.format();
332: Date date = null;
333: try {
334: date = parseDate(format, (String) value);
335: } catch (ParseException pe) {
336: error("The value, "
337: + value
338: + ", assigned to a date property is not in the specified format of: "
339: + format);
340: }
341:
342: String minValue = a.minValue();
343: if (minValue != null && minValue.length() > 0) {
344:
345: Date minDate = null;
346: try {
347: minDate = parseDate(format, a.minValue());
348: } catch (ParseException pe) {
349: error("The value, "
350: + minValue
351: + ", assigned to minValue date constraint property is not in the specified format of: "
352: + format);
353: }
354:
355: if (minDate.compareTo(date) > 0) {
356: error("The value, "
357: + value
358: + ", assigned to a date property is earlier than the earliest date allowed: "
359: + minValue);
360: }
361: }
362:
363: String maxValue = a.maxValue();
364: if (maxValue != null && maxValue.length() > 0) {
365:
366: Date maxDate = null;
367: try {
368: maxDate = parseDate(format, a.maxValue());
369: } catch (ParseException pe) {
370: error("The value, "
371: + maxValue
372: + ", assigned to maxValue date constraint property is not in the specified format of: "
373: + format);
374: }
375:
376: if (maxDate.compareTo(date) < 0) {
377: error("The date, "
378: + value
379: + ", assigned to a date property is later than the latest date allowed: "
380: + maxValue);
381: }
382: }
383: }
384:
385: /**
386: * Parse a date value into the specified format. Pay special attention to the case of the value
387: * having trailing characters, ex. 12/02/2005xx which will not cause the parse of the date to fail
388: * but should be still treated as an error for our purposes.
389: *
390: * @param format Format string for the date.
391: * @param value A String containing the date value to parse.
392: * @return A Date instance if the parse was successful.
393: * @throws ParseException If the value is not a valid date.
394: */
395: public static Date parseDate(String format, String value)
396: throws ParseException {
397:
398: SimpleDateFormat sdFormat = new SimpleDateFormat(format);
399: sdFormat.setLenient(false);
400: ParsePosition pp = new ParsePosition(0);
401: Date d = sdFormat.parse(value, pp);
402:
403: /*
404: a date value such as: 12/01/2005x will not cause a parse error,
405: use the parse position to detect this case.
406: */
407: if (d == null || pp.getIndex() < value.length())
408: throw new ParseException("Parsing date value, " + value
409: + ", failed at index " + pp.getIndex(), pp
410: .getIndex());
411: else
412: return d;
413: }
414:
415: /**
416: * @param value
417: */
418: private static void validateInt(AnnotationMemberTypes.Int a,
419: Object value, boolean optional) {
420: if (optional
421: && (value == null
422: || value
423: .equals(AnnotationMemberTypes.OPTIONAL_STRING) || value
424: .equals(AnnotationMemberTypes.OPTIONAL_INT)))
425: return;
426:
427: int intValue = 0;
428:
429: if (value instanceof String) {
430: try {
431: intValue = Integer.parseInt((String) value);
432: } catch (NumberFormatException nfe) {
433: error("The value ,"
434: + value
435: + ", assigned to an int property does not represent an integer.");
436: }
437: } else if (value instanceof Integer) {
438: intValue = ((Integer) value).intValue();
439: } else {
440: error("The value, "
441: + value
442: + ", assigned to an int property must be of type java.lang.String or int.");
443: }
444:
445: if (intValue < a.minValue())
446: error("The value, "
447: + intValue
448: + ", assigned to an int property is less than the minimum value allowed: "
449: + a.minValue() + ".");
450: else if (intValue > a.maxValue())
451: error("The value, "
452: + intValue
453: + ", assigned to an int property exeeds the maximum value allowed: "
454: + a.maxValue() + ".");
455: }
456:
457: private static void validateDecimal(
458: AnnotationMemberTypes.Decimal a, Object value,
459: boolean optional) {
460: if (optional
461: && (value == null
462: || value
463: .equals(AnnotationMemberTypes.OPTIONAL_STRING)
464: || value
465: .equals(AnnotationMemberTypes.OPTIONAL_FLOAT) || value
466: .equals(AnnotationMemberTypes.OPTIONAL_DOUBLE)))
467: return;
468:
469: double doubleValue = 0;
470: String doubleString = null;
471:
472: if (value instanceof String) {
473: doubleValue = Double.parseDouble((String) value);
474: doubleString = (String) value;
475: } else if (value instanceof Float) {
476: doubleValue = ((Float) value).doubleValue();
477: doubleString = ((Float) value).toString();
478: } else if (value instanceof Double) {
479: doubleValue = ((Double) value).doubleValue();
480: doubleString = ((Double) value).toString();
481: } else {
482: error("The value, "
483: + value
484: + ", assigned to a decimal property must be of type float, double, or java.lang.String.");
485: }
486:
487: if (doubleValue < a.minValue())
488: error("The value, "
489: + doubleValue
490: + ", assigned to a decimal property is less than the the minimum value allowed: "
491: + a.minValue() + ".");
492:
493: if (doubleValue > a.maxValue())
494: error("The value, "
495: + doubleValue
496: + ", assigned to a decimal property exceeds the maximum value allowed: "
497: + a.maxValue() + ".");
498:
499: int decimalPos = doubleString.indexOf('.');
500:
501: if (decimalPos == -1
502: || a.places() == AnnotationMemberTypes.UNLIMITED_PLACES)
503: return;
504:
505: if (doubleString.length() - decimalPos - 1 > a.places())
506: error("The decimal places in the value, " + doubleString
507: + ", assigned to a decimal property exceeds "
508: + a.places()
509: + ", the number of decimal places allowed.");
510:
511: }
512:
513: private static void validateText(AnnotationMemberTypes.Text a,
514: Object value, boolean optional) {
515: if (optional
516: && (value == null || value
517: .equals(AnnotationMemberTypes.OPTIONAL_STRING)))
518: return;
519:
520: if (!(value instanceof String))
521: error("The value, "
522: + value
523: + ", assigned to a text property must be of type java.lang.String.");
524:
525: String str = (String) value;
526: if (str.length() > a.maxLength())
527: error("The value, "
528: + str
529: + ", assigned to a text property exceeds the maximum length allowed: "
530: + a.maxLength());
531:
532: if (a.isLong()) {
533: try {
534: Long.parseLong(str);
535: } catch (NumberFormatException nfe) {
536: error("The value, "
537: + str
538: + ", assigned to a text property with a long number constraint does not represent a long number.");
539: }
540: }
541:
542: }
543:
544: private static void error(String message) {
545: error(message, null);
546: }
547:
548: private static void error(String message, Throwable t) {
549: throw new IllegalArgumentException(message, t);
550: }
551:
552: }
|