001    /*
002     * Copyright 2002-2004 the original author or authors.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005     * use this file except in compliance with the License. You may obtain a copy of
006     * the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012     * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013     * License for the specific language governing permissions and limitations under
014     * the License.
015     */
016    package org.springframework.rules.factory;
017    
018    import java.util.Comparator;
019    import java.util.Set;
020    
021    import org.springframework.rules.closure.Closure;
022    import org.springframework.rules.constraint.Constraint;
023    import org.springframework.rules.closure.support.AlgorithmsAccessor;
024    import org.springframework.rules.closure.BinaryConstraint;
025    import org.springframework.rules.constraint.*;
026    import org.springframework.rules.constraint.Like.LikeType;
027    import org.springframework.rules.constraint.property.CompoundPropertyConstraint;
028    import org.springframework.rules.constraint.property.ConditionalPropertyConstraint;
029    import org.springframework.rules.constraint.property.NegatedPropertyConstraint;
030    import org.springframework.rules.constraint.property.ParameterizedPropertyConstraint;
031    import org.springframework.rules.constraint.property.PropertiesConstraint;
032    import org.springframework.rules.constraint.property.PropertyConstraint;
033    import org.springframework.rules.constraint.property.PropertyValueConstraint;
034    import org.springframework.rules.constraint.property.UniquePropertyValueConstraint;
035    import org.springframework.util.Assert;
036    import org.springframework.util.ObjectUtils;
037    
038    /**
039     * A factory for easing the construction and composition of constraints.
040     * 
041     * @author Keith Donald
042     */
043    public class Constraints extends AlgorithmsAccessor {
044    
045        private static Constraints INSTANCE = new Constraints();
046    
047        public Constraints() {
048    
049        }
050    
051        public static Constraints instance() {
052            return INSTANCE;
053        }
054    
055        public static void load(Constraints sharedInstance) {
056            Assert.notNull(sharedInstance, "The global constraints factory cannot be null");
057            INSTANCE = sharedInstance;
058        }
059    
060        /**
061         * Bind the specified parameter to the second argument of the
062         * <code>BinaryConstraint</code>. The result is a <code>Constraint</code>
063         * which will test a single variable argument against the constant
064         * parameter.
065         * 
066         * @param constraint the binary constraint to bind to
067         * @param parameter the parameter value (constant)
068         * @return The constraint
069         */
070        public Constraint bind(BinaryConstraint constraint, Object parameter) {
071            return new ParameterizedBinaryConstraint(constraint, parameter);
072        }
073    
074        /**
075         * Bind the specified <code>int</code> parameter to the second argument of
076         * the <code>BinaryConstraint</code>. The result is a
077         * <code>Constraint</code> which will test a single variable argument
078         * against the constant <code>int</code> parameter.
079         * 
080         * @param constraint the binary constraint to bind to
081         * @param parameter the <code>int</code> parameter value (constant)
082         * @return The constraint
083         */
084        public Constraint bind(BinaryConstraint constraint, int parameter) {
085            return new ParameterizedBinaryConstraint(constraint, parameter);
086        }
087    
088        /**
089         * Bind the specified <code>float</code> parameter to the second argument
090         * of the <code>BinaryConstraint</code>. The result is a
091         * <code>Constraint</code> which will test a single variable argument
092         * against the constant <code>float</code> parameter.
093         * 
094         * @param constraint the binary constraint to bind to
095         * @param parameter the <code>float</code> parameter value (constant)
096         * @return The constraint
097         */
098        public Constraint bind(BinaryConstraint constraint, float parameter) {
099            return new ParameterizedBinaryConstraint(constraint, parameter);
100        }
101    
102        /**
103         * Bind the specified <code>double</code> parameter to the second argument
104         * of the <code>BinaryConstraint</code>. The result is a
105         * <code>Constraint</code> which will test a single variable argument
106         * against the constant <code>double</code> parameter.
107         * 
108         * @param constraint the binary constraint to bind to
109         * @param parameter the <code>double</code> parameter value (constant)
110         * @return The constraint
111         */
112        public Constraint bind(BinaryConstraint constraint, double parameter) {
113            return new ParameterizedBinaryConstraint(constraint, parameter);
114        }
115    
116        /**
117         * Bind the specified <code>boolean</code> parameter to the second
118         * argument of the <code>BinaryConstraint</code>. The result is a
119         * <code>Constraint</code> which will test a single variable argument
120         * against the constant <code>boolean</code> parameter.
121         * 
122         * @param constraint the binary constraint to bind to
123         * @param parameter the <code>boolean</code> parameter value (constant)
124         * @return The constraint
125         */
126        public Constraint bind(BinaryConstraint constraint, boolean parameter) {
127            return new ParameterizedBinaryConstraint(constraint, parameter);
128        }
129    
130        /**
131         * Attaches a constraint that tests the result returned by evaluating the
132         * specified closure. This effectively attaches a constraint on the closure
133         * return value.
134         * 
135         * @param closure the closure
136         * @param constraint the constraint to test the closure result
137         * @return The testing constraint, which on the call to test(o) first
138         *         evaluates 'o' using the closure and then tests the result.
139         */
140        public Constraint testResultOf(Closure closure, Constraint constraint) {
141            return new ClosureResultConstraint(closure, constraint);
142        }
143    
144        public Constraint eq(Object value) {
145            return EqualTo.value(value);
146        }
147    
148        public Constraint eq(int value) {
149            return eq(new Integer(value));
150        }
151    
152        public Constraint eq(Object value, Comparator comparator) {
153            return EqualTo.value(value, comparator);
154        }
155    
156        public Constraint gt(Comparable value) {
157            return GreaterThan.value(value);
158        }
159    
160        public Constraint gt(Object value, Comparator comparator) {
161            return GreaterThan.value(value, comparator);
162        }
163    
164        public Constraint gt(int value) {
165            return gt(new Integer(value));
166        }
167    
168        public Constraint gt(long value) {
169            return gt(new Long(value));
170        }
171    
172        public Constraint gt(float value) {
173            return gt(new Float(value));
174        }
175    
176        public Constraint gt(double value) {
177            return gt(new Double(value));
178        }
179    
180        public Constraint gte(Comparable value) {
181            return GreaterThanEqualTo.value(value);
182        }
183    
184        public Constraint gte(Object value, Comparator comparator) {
185            return GreaterThanEqualTo.value(value, comparator);
186        }
187    
188        public Constraint gte(int value) {
189            return gte(new Integer(value));
190        }
191    
192        public Constraint gte(long value) {
193            return gte(new Long(value));
194        }
195    
196        public Constraint gte(float value) {
197            return gte(new Float(value));
198        }
199    
200        public Constraint gte(double value) {
201            return gte(new Double(value));
202        }
203    
204        public Constraint lt(Comparable value) {
205            return LessThan.value(value);
206        }
207    
208        public Constraint lt(Comparable value, Comparator comparator) {
209            return LessThan.value(value, comparator);
210        }
211    
212        public Constraint lt(int value) {
213            return lt(new Integer(value));
214        }
215    
216        public Constraint lt(long value) {
217            return lt(new Long(value));
218        }
219    
220        public Constraint lt(float value) {
221            return lt(new Float(value));
222        }
223    
224        public Constraint lt(double value) {
225            return lt(new Double(value));
226        }
227    
228        public Constraint lte(Comparable value) {
229            return LessThanEqualTo.value(value);
230        }
231    
232        public Constraint lte(Object value, Comparator comparator) {
233            return LessThanEqualTo.value(value, comparator);
234        }
235    
236        public Constraint lte(int value) {
237            return lte(new Integer(value));
238        }
239    
240        public Constraint lte(long value) {
241            return lte(new Long(value));
242        }
243    
244        public Constraint lte(float value) {
245            return lte(new Float(value));
246        }
247    
248        public Constraint lte(double value) {
249            return lte(new Double(value));
250        }
251    
252        public Constraint range(Comparable min, Comparable max) {
253            return new Range(min, max);
254        }
255    
256        public Constraint range(Comparable min, Comparable max, boolean inclusive) {
257            return new Range(min, max, inclusive);
258        }
259    
260        public Constraint range(Object min, Object max, Comparator comparator) {
261            return new Range(min, max, comparator);
262        }
263    
264        public Constraint range(Object min, Object max, Comparator comparator, boolean inclusive) {
265            return new Range(min, max, comparator, inclusive);
266        }
267    
268        public Constraint range(int min, int max) {
269            return new Range(min, max);
270        }
271    
272        public Constraint range(long min, long max) {
273            return new Range(min, max);
274        }
275    
276        public Constraint range(float min, float max) {
277            return new Range(min, max);
278        }
279    
280        public Constraint range(double min, double max) {
281            return new Range(min, max);
282        }
283    
284        public Constraint present() {
285            return Required.present();
286        }
287    
288            /**
289         * Returns a required constraint.
290         * 
291         * @return The required constraint instance.
292         */
293        public Constraint required() {
294            return Required.instance();
295        }
296    
297        public Constraint ifTrue(Constraint constraint, Constraint mustAlsoBeTrue) {
298            return new IfTrue(constraint, mustAlsoBeTrue);
299        }
300    
301        public Constraint ifTrue(Constraint constraint, Constraint mustAlsoBeTrue, Constraint elseMustAlsoBeTrue) {
302            return new IfTrue(constraint, mustAlsoBeTrue, elseMustAlsoBeTrue);
303        }
304        
305        public Constraint ifTrue(Constraint constraint, Constraint mustAlsoBeTrue, String type) {
306            return new IfTrue(constraint, mustAlsoBeTrue, type);
307        }
308    
309        public Constraint ifTrue(Constraint constraint, Constraint mustAlsoBeTrue, Constraint elseMustAlsoBeTrue, String type) {
310            return new IfTrue(constraint, mustAlsoBeTrue, elseMustAlsoBeTrue, type);
311        }
312        
313        /**
314             * Returns a ConditionalPropertyConstraint: one property will trigger the
315             * validation of another.
316             * 
317             * @see ConditionalPropertyConstraint
318             */
319            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint thenConstraint) {
320                    return new ConditionalPropertyConstraint(ifConstraint, thenConstraint);
321            }
322        
323            /**
324             * Returns a ConditionalPropertyConstraint: one property will trigger the
325             * validation of another.
326             * 
327             * @see ConditionalPropertyConstraint
328             */
329            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint thenConstraint, String type) {
330                    return new ConditionalPropertyConstraint(ifConstraint, thenConstraint, type);
331            }
332            
333        /**
334             * Returns a ConditionalPropertyConstraint: one property will trigger the
335             * validation of another.
336             * 
337             * @see ConditionalPropertyConstraint
338             */
339            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint thenConstraint,
340                            PropertyConstraint elseConstraint) {
341                    return new ConditionalPropertyConstraint(ifConstraint, thenConstraint, elseConstraint);
342            }
343            
344            /**
345             * Returns a ConditionalPropertyConstraint: one property will trigger the
346             * validation of another.
347             * 
348             * @see ConditionalPropertyConstraint
349             */
350            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint thenConstraint,
351                            PropertyConstraint elseConstraint, String type) {
352                    return new ConditionalPropertyConstraint(ifConstraint, thenConstraint, elseConstraint, type);
353            }
354            
355            /**
356             * Returns a ConditionalPropertyConstraint: one property will trigger the
357             * validation of another.
358             * 
359             * @see ConditionalPropertyConstraint
360             */
361            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint[] thenConstraints) {
362                    return new ConditionalPropertyConstraint(ifConstraint, new CompoundPropertyConstraint(new And(thenConstraints)));
363            }
364            
365            /**
366             * Returns a ConditionalPropertyConstraint: one property will trigger the
367             * validation of another.
368             * 
369             * @see ConditionalPropertyConstraint
370             */
371            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint[] thenConstraints, String type) {
372                    return new ConditionalPropertyConstraint(ifConstraint, new CompoundPropertyConstraint(new And(thenConstraints)), type);
373            }
374        
375        /**
376             * Returns a ConditionalPropertyConstraint: one property will trigger the
377             * validation of another.
378             * 
379             * @see ConditionalPropertyConstraint
380             */
381            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint[] thenConstraints,
382                            PropertyConstraint[] elseConstraints) {
383                    return new ConditionalPropertyConstraint(ifConstraint,
384                                    new CompoundPropertyConstraint(new And(thenConstraints)), new CompoundPropertyConstraint(new And(
385                                                    elseConstraints)));
386            }
387            
388            /**
389             * Returns a ConditionalPropertyConstraint: one property will trigger the
390             * validation of another.
391             * 
392             * @see ConditionalPropertyConstraint
393             */
394            public PropertyConstraint ifTrue(PropertyConstraint ifConstraint, PropertyConstraint[] thenConstraints,
395                            PropertyConstraint[] elseConstraints, String type) {
396                    return new ConditionalPropertyConstraint(ifConstraint,
397                                    new CompoundPropertyConstraint(new And(thenConstraints)), new CompoundPropertyConstraint(new And(
398                                                    elseConstraints)), type);
399            }
400    
401        /**
402             * Returns a maxlength constraint.
403             * 
404             * @param maxLength The maximum length in characters.
405             * @return The configured maxlength constraint.
406             */
407        public Constraint maxLength(int maxLength) {
408            return new StringLengthConstraint(maxLength);
409        }
410    
411        /**
412         * Returns a minlength constraint.
413         * 
414         * @param minLength The minimum length in characters.
415         * @return The configured minlength constraint.
416         */
417        public Constraint minLength(int minLength) {
418            return new StringLengthConstraint(RelationalOperator.GREATER_THAN_EQUAL_TO, minLength);
419        }
420    
421        /**
422         * Returns a 'like' constraint.
423         * 
424         * @param encodedLikeString the likeString
425         * @return The Like constraint.
426         */
427        public Constraint like(String encodedLikeString) {
428            return new Like(encodedLikeString);
429        }
430    
431        /**
432         * Creates a constraint backed by a regular expression.
433         * 
434         * @param regexp The regular expression string.
435         * @return The constraint.
436         */
437        public Constraint regexp(String regexp) {
438            return new RegexpConstraint(regexp);
439        }
440    
441        /**
442         * Creates a constraint backed by a regular expression, with a type for
443         * reporting.
444         * 
445         * @param regexp The regular expression string.
446         * @return The constraint.
447         */
448        public Constraint regexp(String regexp, String type) {
449            RegexpConstraint c = new RegexpConstraint(regexp);
450            c.setType(type);
451            return c;
452        }
453    
454        /**
455         * Returns a constraint whose test is determined by a boolean method on a
456         * target object.
457         * 
458         * @param targetObject The targetObject
459         * @param methodName The method name
460         * @return The constraint.
461         */
462        public Constraint method(Object target, String methodName, String constraintType) {
463            return new MethodInvokingConstraint(target, methodName, constraintType);
464        }
465    
466        /**
467         * Returns a 'in' group (or set) constraint.
468         * 
469         * @param group the group items
470         * @return The InGroup constraint
471         */
472        public Constraint inGroup(Set group) {
473            return new InGroup(group);
474        }
475    
476        /**
477         * Returns a 'in' group (or set) constraint.
478         * 
479         * @param group the group items
480         * @return The InGroup constraint.
481         */
482        public Constraint inGroup(Object[] group) {
483            return new InGroup(group);
484        }
485    
486        /**
487         * Returns a 'in' group (or set) constraint.
488         * 
489         * @param group the group items
490         * @return The InGroup constraint.
491         */
492        public Constraint inGroup(int[] group) {
493            return inGroup(ObjectUtils.toObjectArray(group));
494        }
495    
496        /**
497         * AND two constraints.
498         * 
499         * @param constraint1 the first constraint
500         * @param constraint2 the second constraint
501         * @return The compound AND constraint
502         */
503        public And and(Constraint constraint1, Constraint constraint2) {
504            return new And(constraint1, constraint2);
505        }
506    
507        /**
508         * Return the conjunction (all constraint) for all constraints.
509         * 
510         * @param constraints the constraints
511         * @return The compound AND constraint
512         */
513        public And all(Constraint[] constraints) {
514            return new And(constraints);
515        }
516    
517        /**
518         * Returns a new, empty conjunction prototype, capable of composing
519         * individual constraints where 'ALL' must test true.
520         * 
521         * @return the UnaryAnd
522         */
523        public And conjunction() {
524            return new And();
525        }
526    
527        /**
528         * OR two constraints.
529         * 
530         * @param constraint1 the first constraint
531         * @param constraint2 the second constraint
532         * @return The compound OR constraint
533         */
534        public Or or(Constraint constraint1, Constraint constraint2) {
535            return new Or(constraint1, constraint2);
536        }
537    
538        /**
539         * Return the disjunction (any constraint) for all constraints.
540         * 
541         * @param constraints the constraints
542         * @return The compound AND constraint
543         */
544        public Or any(Constraint[] constraints) {
545            return new Or(constraints);
546        }
547    
548        /**
549         * Returns a new, empty disjunction prototype, capable of composing
550         * individual constraints where 'ANY' must test true.
551         * 
552         * @return the UnaryOr
553         */
554        public Or disjunction() {
555            return new Or();
556        }
557    
558        /**
559         * Returns a new, empty exclusive disjunction prototype, capable of composing
560         * individual constraints where only one must test true.
561         * 
562         * @return the UnaryXOr
563         */
564        public XOr exclusiveDisjunction() {
565            return new XOr();
566        }
567    
568        /**
569         * Negate the specified constraint.
570         * 
571         * @param constraint The constraint to negate
572         * @return The negated constraint.
573         */
574        public Constraint not(Constraint constraint) {
575            if (!(constraint instanceof Not))
576                return new Not(constraint);
577    
578            return ((Not)constraint).getConstraint();
579        }
580    
581        /**
582         * Attach a value constraint for the provided bean property.
583         * 
584         * @param propertyName the bean property name
585         * @param valueConstraint the value constraint
586         * @return The bean property expression that tests the constraint
587         */
588        public PropertyConstraint value(String propertyName, Constraint valueConstraint) {
589            return new PropertyValueConstraint(propertyName, valueConstraint);
590        }
591    
592        /**
593         * Returns a present bean property expression.
594         * 
595         * @return The present constraint instance.
596         */
597            public PropertyConstraint present(String propertyName) {
598                    return value(propertyName, present());
599            }
600    
601        /**
602         * Returns a required bean property expression.
603         * 
604         * @return The required constraint instance.
605         */
606        public PropertyConstraint required(String property) {
607            return value(property, required());
608        }
609    
610        /**
611         * Return a 'like' constraint applied as a value constraint to the provided
612         * property.
613         * 
614         * @param property The property to constrain
615         * @param likeType The like type
616         * @param value The like string value to match
617         * @return The Like constraint
618         */
619        public PropertyConstraint like(String property, LikeType likeType, String value) {
620            return value(property, new Like(likeType, value));
621        }
622    
623        /**
624         * Returns a 'in' group (or set) constraint appled to the provided property.
625         * 
626         * @param propertyName the property
627         * @param group the group items
628         * @return The InGroup constraint.
629         */
630        public PropertyConstraint inGroup(String propertyName, Object[] group) {
631            return value(propertyName, new InGroup(group));
632        }
633    
634        /**
635         * Apply an "all" value constraint to the provided bean property.
636         * 
637         * @param propertyName The bean property name
638         * @param constraints The constraints that form a all conjunction
639         * @return
640         */
641        public PropertyConstraint all(String propertyName, Constraint[] constraints) {
642            return value(propertyName, all(constraints));
643        }
644    
645        /**
646         * Apply an "any" value constraint to the provided bean property.
647         * 
648         * @param propertyName The bean property name
649         * @param constraints The constraints that form a all disjunction
650         * @return
651         */
652        public PropertyConstraint any(String propertyName, Constraint[] constraints) {
653            return value(propertyName, any(constraints));
654        }
655    
656        /**
657         * Negate a bean property expression.
658         * 
659         * @param e the expression to negate
660         * @return The negated expression
661         */
662        public PropertyConstraint not(PropertyConstraint e) {
663            return new NegatedPropertyConstraint(e);
664        }
665    
666        public PropertyConstraint valueProperty(String propertyName, BinaryConstraint constraint, Object value) {
667            return new ParameterizedPropertyConstraint(propertyName, constraint, value);
668        }
669    
670        /**
671         * Apply a "equal to" constraint to a bean property.
672         * 
673         * @param propertyName The first property
674         * @param propertyValue The constraint value
675         * @return The constraint
676         */
677        public PropertyConstraint eq(String propertyName, Object propertyValue) {
678            return new ParameterizedPropertyConstraint(propertyName, eq(propertyValue));
679        }
680    
681        /**
682         * Apply a "equal to" constraint to a bean property.
683         * 
684         * @param propertyName The first property
685         * @param propertyValue The constraint value
686         * @param comparator the comparator to use while comparing the values
687         * @return The constraint
688         * 
689         * @since 0.3.0
690         */
691        public PropertyConstraint eq(String propertyName, Object propertyValue, Comparator comparator) {
692            return new ParameterizedPropertyConstraint(propertyName, eq(propertyValue, comparator));
693        }
694    
695        /**
696         * Apply a "greater than" constraint to a bean property.
697         * 
698         * @param propertyName The first property
699         * @param propertyValue The constraint value
700         * @return The constraint
701         */
702        public PropertyConstraint gt(String propertyName, Comparable propertyValue) {
703            return new ParameterizedPropertyConstraint(propertyName, gt(propertyValue));
704        }
705    
706        /**
707         * Apply a "greater than equal to" constraint to a bean property.
708         * 
709         * @param propertyName The first property
710         * @param propertyValue The constraint value
711         * @return The constraint
712         */
713        public PropertyConstraint gte(String propertyName, Comparable propertyValue) {
714            return new ParameterizedPropertyConstraint(propertyName, gte(propertyValue));
715        }
716    
717        /**
718         * Apply a "less than" constraint to a bean property.
719         * 
720         * @param propertyName The first property
721         * @param propertyValue The constraint value
722         * @return The constraint
723         */
724        public PropertyConstraint lt(String propertyName, Comparable propertyValue) {
725            return new ParameterizedPropertyConstraint(propertyName, lt(propertyValue));
726        }
727    
728        /**
729         * Apply a "less than equal to" constraint to a bean property.
730         * 
731         * @param propertyName The first property
732         * @param propertyValue The constraint value
733         * @return The constraint
734         */
735        public PropertyConstraint lte(String propertyName, Comparable propertyValue) {
736            return new ParameterizedPropertyConstraint(propertyName, lte(propertyValue));
737        }
738    
739        public PropertyConstraint valueProperties(String propertyName, BinaryConstraint constraint, String otherPropertyName) {
740            return new PropertiesConstraint(propertyName, constraint, otherPropertyName);
741        }
742    
743        /**
744         * Apply a "equal to" constraint to two bean properties.
745         * 
746         * @param propertyName The first property
747         * @param otherPropertyName The other property
748         * @param comparator the comparator to use while comparing the values
749         * @return The constraint
750         * 
751         * @since 0.3.0
752         */
753        public PropertyConstraint eqProperty(String propertyName, String otherPropertyName, Comparator comparator) {
754            return valueProperties(propertyName, EqualTo.instance(comparator), otherPropertyName);
755        }
756    
757        /**
758         * Apply a "greater than" constraint to two properties
759         * 
760         * @param propertyName The first property
761         * @param otherPropertyName The other property
762         * @param comparator the comparator to use while comparing the values
763         * @return The constraint
764         * 
765         * @since 0.3.0
766         */
767        public PropertyConstraint gtProperty(String propertyName, String otherPropertyName, Comparator comparator) {
768            return valueProperties(propertyName, GreaterThan.instance(comparator), otherPropertyName);
769        }
770    
771        /**
772         * Apply a "greater than or equal to" constraint to two properties.
773         * 
774         * @param propertyName The first property
775         * @param otherPropertyName The other property
776         * @param comparator the comparator to use while comparing the values
777         * @return The constraint
778         * 
779         * @since 0.3.0
780         */
781        public PropertyConstraint gteProperty(String propertyName, String otherPropertyName, Comparator comparator) {
782            return valueProperties(propertyName, GreaterThanEqualTo.instance(comparator), otherPropertyName);
783        }
784    
785        /**
786         * Apply a "less than" constraint to two properties.
787         * 
788         * @param propertyName The first property
789         * @param otherPropertyName The other property
790         * @param comparator the comparator to use while comparing the values
791         * @return The constraint
792         * 
793         * @since 0.3.0
794         */
795        public PropertyConstraint ltProperty(String propertyName, String otherPropertyName, Comparator comparator) {
796            return valueProperties(propertyName, LessThan.instance(comparator), otherPropertyName);
797        }
798    
799        /**
800         * Apply a "less than or equal to" constraint to two properties.
801         * 
802         * @param propertyName The first property
803         * @param otherPropertyName The other property
804         * @param comparator the comparator to use while comparing the values
805         * @return The constraint
806         * 
807         * @since 0.3.0
808         */
809        public PropertyConstraint lteProperty(String propertyName, String otherPropertyName, Comparator comparator) {
810            return valueProperties(propertyName, LessThanEqualTo.instance(comparator), otherPropertyName);
811        }
812    
813        /**
814         * Apply a inclusive "range" constraint to a bean property.
815         * 
816         * @param propertyName the property with the range constraint.
817         * @param min the low edge of the range
818         * @param max the high edge of the range
819         * @param comparator the comparator to use while comparing the values
820         * @return The range constraint constraint
821         * 
822         * @since 0.3.0
823         */
824        public PropertyConstraint inRange(String propertyName, Object min, Object max, Comparator comparator) {
825            return value(propertyName, range(min, max, comparator));
826        }
827    
828        /**
829         * Apply a inclusive "range" constraint between two other properties to a
830         * bean property.
831         * 
832         * @param propertyName the property with the range constraint.
833         * @param minPropertyName the low edge of the range
834         * @param maxPropertyName the high edge of the range
835         * @param comparator the comparator to use while comparing the values
836         * @return The range constraint constraint
837         * 
838         * @since 0.3.0
839         */
840        public PropertyConstraint inRangeProperties(String propertyName, String minPropertyName, String maxPropertyName, Comparator comparator) {
841            Constraint min = gteProperty(propertyName, minPropertyName, comparator);
842            Constraint max = lteProperty(propertyName, maxPropertyName, comparator);
843            return new CompoundPropertyConstraint(new And(min, max));
844        }
845    
846        /**
847         * Apply a "equal to" constraint to two bean properties.
848         * 
849         * @param propertyName The first property
850         * @param otherPropertyName The other property
851         * @return The constraint
852         */
853        public PropertyConstraint eqProperty(String propertyName, String otherPropertyName) {
854            return valueProperties(propertyName, EqualTo.instance(), otherPropertyName);
855        }
856    
857        /**
858         * Apply a "greater than" constraint to two properties
859         * 
860         * @param propertyName The first property
861         * @param otherPropertyName The other property
862         * @return The constraint
863         */
864        public PropertyConstraint gtProperty(String propertyName, String otherPropertyName) {
865            return valueProperties(propertyName, GreaterThan.instance(), otherPropertyName);
866        }
867    
868        /**
869         * Apply a "greater than or equal to" constraint to two properties.
870         * 
871         * @param propertyName The first property
872         * @param otherPropertyName The other property
873         * @return The constraint
874         */
875        public PropertyConstraint gteProperty(String propertyName, String otherPropertyName) {
876            return valueProperties(propertyName, GreaterThanEqualTo.instance(), otherPropertyName);
877        }
878    
879        /**
880         * Apply a "less than" constraint to two properties.
881         * 
882         * @param propertyName The first property
883         * @param otherPropertyName The other property
884         * @return The constraint
885         */
886        public PropertyConstraint ltProperty(String propertyName, String otherPropertyName) {
887            return valueProperties(propertyName, LessThan.instance(), otherPropertyName);
888        }
889    
890        /**
891         * Apply a "less than or equal to" constraint to two properties.
892         * 
893         * @param propertyName The first property
894         * @param otherPropertyName The other property
895         * @return The constraint
896         */
897        public PropertyConstraint lteProperty(String propertyName, String otherPropertyName) {
898            return valueProperties(propertyName, LessThanEqualTo.instance(), otherPropertyName);
899        }
900    
901        /**
902         * Apply a inclusive "range" constraint to a bean property.
903         * 
904         * @param propertyName the property with the range constraint.
905         * @param min the low edge of the range
906         * @param max the high edge of the range
907         * @return The range constraint constraint
908         */
909        public PropertyConstraint inRange(String propertyName, Comparable min, Comparable max) {
910            return value(propertyName, range(min, max));
911        }
912    
913        /**
914         * Apply a inclusive "range" constraint between two other properties to a
915         * bean property.
916         * 
917         * @param propertyName the property with the range constraint.
918         * @param minPropertyName the low edge of the range
919         * @param maxPropertyName the high edge of the range
920         * @return The range constraint constraint
921         */
922        public PropertyConstraint inRangeProperties(String propertyName, String minPropertyName, String maxPropertyName) {
923            Constraint min = gteProperty(propertyName, minPropertyName);
924            Constraint max = lteProperty(propertyName, maxPropertyName);
925            return new CompoundPropertyConstraint(new And(min, max));
926        }
927    
928        /**
929         * Create a unique property value constraint that will test a collection of
930         * domain objects, returning true if all objects have unique values for the
931         * provided propertyName.
932         * @param propertyName The property name
933         * @return The constraint
934         */
935        public PropertyConstraint unique(String propertyName) {
936            return new UniquePropertyValueConstraint(propertyName);
937        }
938    }