001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio;
023
024import java.util.Collection;
025import java.util.Iterator;
026
027import org.biojava.bio.symbol.Location;
028
029/**
030 * Used by <code>AnnotationType</code> to represent the constraint on
031 * the collection of values in a property-slot.
032 * CollectionConstraints usually use a <code>PropertyConstraint</code>
033 * to validate the individual elements.
034 *
035 * 
036 * Use one or more of the built-in implementations to build new
037 * <code>AnnotationTypes</code>.
038 *
039 * @since 1.3
040 * @author Thomas Down
041 * @author Matthew Pocock
042 */
043public interface CollectionConstraint {
044    /**
045     * <code>accept</code> returns true if the value fulfills the
046     * constraint.
047     *
048     * @param values a <code>Collection</code> to check.
049     * @return true if the values are acceptable
050     *
051     * powerUser Manually compare items with the CollectionConstraint. Node:
052     * this will ususaly be done for you in an AnnotationType instance
053     */
054    public boolean accept(Object values);
055
056    /**
057     * <p><code>subConstraintOf</code> returns true if the constraint
058     * is a sub-constraint.<p>
059     *
060     * <p>A pair of constraints super and sub are in a
061     * superConstraint/subConstraint relationship if every object
062     * accepted by sub is also accepted by super. To put it another
063     * way, if instanceOf was used as a set-membership indicator
064     * function over some set of objects, then the set produced by
065     * super would be a superset of that produced by sub.</p>
066     *
067     * <p>It is not expected that constraints will neccesarily
068     * maintain references to super/sub types. It will be more usual
069     * to infer this relationship by introspecting the constraints
070     * themselves. For example,
071     * <code>CollectionConstraint.ByClass</code> will infer
072     * subConstraintOf by looking at the possible class of all items
073     * matching subConstraint.</p>
074     *
075     * @param subConstraint a <code>CollectionConstraint</code> to check.
076     * @return a <code>boolean</code>.
077     *
078     * 
079     * Usefull when attempting to compare two constraints to see
080     * if it is necisary to retain both. You may want to check the more
081     * general or the more specific constraint only.
082     */
083    public boolean subConstraintOf(CollectionConstraint subConstraint);
084
085
086    /**
087     * Return <code>true</code> iff the Collection formed by adding
088     * <code>newValue</code> to <code>current</code> would be accepted
089     * by this constraint.
090     *
091     * 
092     * Implementations may <em>not</em> assume that <code>current</code>
093     * is valid.
094     *
095     * @param current  a Collection containing the current values
096     * @param newValue the new value to add
097     * @return true if adding the new value will result in an acceptable
098     *    property
099     */
100    
101    public boolean validateAddValue(Collection current, Object newValue);
102    
103    /**
104     * Return <code>true</code> iff the Collection formed by removing
105     * <code>newValue</code> from <code>current</code> would be accepted
106     * by this constraint.
107     *
108     * 
109     * Implementations may <em>not</em> assume that <code>current</code>
110     * is valid.  However, <code>current</code> will already have been
111     * checked to ensure that it contains <code>victim</code>.
112     *
113     * @param current a Collection containing the current values
114     * @param victim  the value to remove
115     * @return true   if removing the victim will result in an acceptable
116     *    property value set
117     */
118    
119    public boolean validateRemoveValue(Collection current, Object victim);
120    
121    /**
122     * <code>ANY</code> is a constraint which accepts a property for
123     * addition under all conditions.
124     *
125     * Whenever a CollectionConstraint is needed and you want to allow
126     * any value there
127     */
128    public static final CollectionConstraint ANY = new AllValuesIn(PropertyConstraint.ANY, CardinalityConstraint.ANY);
129    
130    /**
131     * <code>EMPTY</code> is a constraint which only accepts the empty
132     * set.
133     * 
134     * Use this to indicate that a property must be undefined
135     */
136     
137    public static final CollectionConstraint EMPTY = new AllValuesIn(PropertyConstraint.NONE, CardinalityConstraint.ZERO);
138    
139    /**
140     * <code>NONE</code> is a constraint which accepts no value for a property
141     * under any condition.
142     *
143     * This value indicates an impossible condition.  It may be
144     *                 returned by methods such as <code>AnnotationTools.intersection</code>
145     *                 to indicate that <code>NO</code> values of a property (include undefined)
146     *                 are valid.
147     */
148    public static final CollectionConstraint NONE = new NoneCollectionConstraint();
149  
150    /**
151     * CollectionConstraint which validates all members of a Collection.
152     * All members must be vaild according to the supplied
153     * <code>PropertyConstraint</code>, and the total number of
154     * members must be acceptable by the given cardinality constraint.
155     *
156     * @author Thomas Down
157     * @author Matthew Pocock
158     */
159    
160    public class AllValuesIn implements CollectionConstraint {
161        private PropertyConstraint pc;
162        private Location card;
163
164      /**
165       * Create an AllValuesIn based upon a PropertyConstraint and a
166       * cardinality.
167       *
168       * @param pc    the PropertyConstraint to apply to each property value
169       * @param card  the cardinality constraint restricting the number of
170       *    values
171       */
172        public AllValuesIn(PropertyConstraint pc, Location card) {
173            this.pc = pc;
174            this.card = card;
175        }
176
177      /**
178       * Get the PropertyConstraint used to validate each property value.
179       *
180       * @return  the PropertyConstraint used
181       */
182        public PropertyConstraint getPropertyConstraint() {
183            return pc;
184        }
185
186      /**
187       * Get the cardinality constraint used to validate the number of property
188       * values.
189       *
190       * @return  the cardinality constraint as a Location
191       */
192        public Location getCardinalityConstraint() {
193            return card;
194        }
195        
196        public boolean accept(Object o) {
197            if (o instanceof Collection) {
198                Collection co = (Collection) o;
199                if (!card.contains(co.size())) {
200                    return false;
201                } else {
202                    for (Iterator i = co.iterator(); i.hasNext(); ) {
203                        if (!pc.accept(i.next())) {
204                            return false;
205                        }
206                    }
207                    return true;
208                }
209            } else {
210                return card.contains(1) && pc.accept(o);
211            }
212        }
213        
214        public boolean validateAddValue(Collection oldcol, Object newValue) {
215            if (!pc.accept(newValue)) {
216                return false;
217            }
218            if (!card.contains(oldcol.size() + 1)) {
219                return false;
220            }
221            for (Iterator i = oldcol.iterator(); i.hasNext(); ) {
222                if (!pc.accept(i.next())) {
223                    return false;
224                }
225            }
226            return true;
227        }
228        
229        public boolean validateRemoveValue(Collection oldcol, Object victim) {
230            if (!card.contains(oldcol.size() - 1)) {
231                return false;
232            }
233            
234            for (Iterator i = oldcol.iterator(); i.hasNext(); ) {
235                Object o = i.next();
236                if (!o.equals(victim) && !pc.accept(o)) {
237                    return false;
238                }
239            }
240            return true;
241        }
242        
243        public int hashCode() {
244            return pc.hashCode() + 87;
245        }
246        
247        public boolean equals(Object o) {
248            if (o instanceof AllValuesIn) {
249                AllValuesIn avo = (AllValuesIn) o;
250                return avo.getCardinalityConstraint().equals(getCardinalityConstraint()) &&
251                       avo.getPropertyConstraint().equals(getPropertyConstraint());
252            } else {
253                return false;
254            }
255        }
256        
257        public boolean subConstraintOf(CollectionConstraint cc) {
258            if (cc instanceof NoneCollectionConstraint) {
259                return true;
260            } else if (cc instanceof CollectionConstraint.AllValuesIn) {
261                AllValuesIn ccavi = (AllValuesIn) cc;
262                return getCardinalityConstraint().contains(ccavi.getCardinalityConstraint()) &&
263                       getPropertyConstraint().subConstraintOf(ccavi.getPropertyConstraint());
264            } else if (cc instanceof CollectionConstraint.Contains) {
265                if (!getCardinalityConstraint().contains(Integer.MAX_VALUE)) {
266                    return false;
267                } else {
268                    Contains ccc = (Contains) cc;
269                    return getPropertyConstraint().subConstraintOf(ccc.getPropertyConstraint());
270                }
271            } else if (cc instanceof CollectionConstraint.And) {
272                And cca = (And) cc;
273                return subConstraintOf(cca.getChild1()) || subConstraintOf(cca.getChild2());
274            } else if (cc instanceof CollectionConstraint.Or) {
275                Or cco = (Or) cc;
276                return subConstraintOf(cco.getChild1()) && subConstraintOf(cco.getChild2());
277            }
278            return false;
279        }
280        
281        public String toString() {
282          return "AllValuesIn: (" + pc.toString() + ", " + card.toString() + ")";
283        }
284    }
285    
286    /**
287     * CollectionConstraint which validates a portion of a Collection.
288     * Accepts only collections where the number of members matching
289     * the <code>PropertyConstraint</code> is in the supplied cardinality.
290     *
291     * <p>
292     * A typical application for this would be with Annotations where
293     * one property can contain a number of synonyms.
294     * <code>CollectionConstraint.Contains</code> could be used as
295     * a query to select instances based on one of these synonyms.
296     * </p>
297     *
298     * @author Thomas Down
299     */
300    
301    public class Contains implements CollectionConstraint {
302        private PropertyConstraint pc;
303        private Location card;
304        
305      /**
306       * Create a Contains based upon a PropertyConstraint and a
307       * cardinality.
308       *
309       * @param pc    the PropertyConstraint to apply to each property value
310       * @param card  the cardinality constraint restricting the number of
311       *    values
312       */
313        public Contains(PropertyConstraint pc, Location card) {
314            this.pc = pc;
315            this.card = card;
316        }
317        
318      /**
319       * Get the PropertyConstraint used to validate each property value.
320       *
321       * @return  the PropertyConstraint used
322       */
323        public PropertyConstraint getPropertyConstraint() {
324            return pc;
325        }
326        
327      /**
328       * Get the cardinality constraint used to validate the number of property
329       * values.
330       *
331       * @return  the cardinality constraint as a Location
332       */
333        public Location getCardinalityConstraint() {
334            return card;
335        }
336        
337        public boolean accept(Object o) {
338            if (o instanceof Collection) {
339                return card.contains(countMembers((Collection) o));
340            } else {
341                if (pc.accept(o)) {
342                    return card.contains(1);
343                } else {
344                    return card.contains(0);
345                }
346            }
347        }
348        
349        private int countMembers(Collection co) {
350            int members = 0;
351            for (Iterator i = co.iterator(); i.hasNext(); ) {
352                if (pc.accept(i.next())) {
353                    ++members;
354                }
355            }
356            return members;
357        }
358        
359        public int hashCode() {
360            return pc.hashCode() + 178;
361        }
362        
363        public boolean equals(Object o) {
364            if (o instanceof Contains) {
365                Contains avo = (Contains) o;
366                return avo.getCardinalityConstraint().equals(getCardinalityConstraint()) &&
367                       avo.getPropertyConstraint().equals(getPropertyConstraint());
368            } else {
369                return false;
370            }
371        }
372        
373        public boolean validateAddValue(Collection oldCol, Object newValue) {
374            int members = countMembers(oldCol);
375            if (pc.accept(newValue)) {
376                ++members;
377            }
378            return card.contains(members);
379        }
380        
381        public boolean validateRemoveValue(Collection oldCol, Object newValue) {
382            int members = countMembers(oldCol);
383            if (pc.accept(newValue)) {
384                --members;
385            }
386            return card.contains(members);
387        }
388        
389        
390        public boolean subConstraintOf(CollectionConstraint cc) {
391            if (cc instanceof NoneCollectionConstraint) {
392                return true;
393            } else if (cc instanceof CollectionConstraint.AllValuesIn) {
394                AllValuesIn ccavi = (AllValuesIn) cc;
395                return getCardinalityConstraint().contains(ccavi.getCardinalityConstraint()) &&
396                       getPropertyConstraint().subConstraintOf(ccavi.getPropertyConstraint());
397            } else if (cc instanceof CollectionConstraint.Contains) {
398                Contains ccavi = (Contains) cc;
399                return getCardinalityConstraint().contains(ccavi.getCardinalityConstraint()) &&
400                       getPropertyConstraint().subConstraintOf(ccavi.getPropertyConstraint());
401            } else if (cc instanceof CollectionConstraint.And) {
402                And cca = (And) cc;
403                return subConstraintOf(cca.getChild1()) || subConstraintOf(cca.getChild2());
404            } else if (cc instanceof CollectionConstraint.Or) {
405                Or cco = (Or) cc;
406                return subConstraintOf(cco.getChild1()) && subConstraintOf(cco.getChild2());
407            }
408            return false;
409        }
410        
411        public String toString() {
412          return "Contains: (" + pc.toString() + ", " + card.toString() + ")";
413        }
414    }
415    
416    
417    /**
418     * A collection constraint that accpepts collections iff they are accepted by both
419     * child constraints. This effectively matches the intersection of the items
420     * matched by the two constraints.
421     *
422     * Use this to combine multiple constraints. You can make one
423     *            or both of the children And instances if you need a tighter
424     *            intersection.
425     * @author Matthew Pocock
426     * @author Thomas Down
427     */
428    public class And implements CollectionConstraint {
429      private CollectionConstraint c1;
430      private CollectionConstraint c2;
431      
432      /**
433       * Create a new <code>And</code> from two child constraints.
434       *
435       * @param c1 the first child
436       * @param c2 the seccond child
437       */
438      public And(CollectionConstraint c1, CollectionConstraint c2) {
439        this.c1 = c1;
440        this.c2 = c2;
441      }
442      
443      /**
444       * Get the first child CollectionConstraint.
445       *
446       * @return the first child CollectionConstraint
447       *
448       */
449      public CollectionConstraint getChild1() {
450        return c1;
451      }
452      
453      /**
454       * Get the seccond child CollectionConstraint.
455       *
456       * @return the seccond child CollectionConstraint
457       *
458       */
459      public CollectionConstraint getChild2() {
460        return c2;
461      }
462      
463      public boolean accept(Object object) {
464        return c1.accept(object) && c2.accept(object);
465      }
466      
467      public boolean subConstraintOf(CollectionConstraint pc) {
468        return c1.subConstraintOf(pc) && c2.subConstraintOf(pc);
469      }
470      
471      
472      public boolean validateAddValue(Collection oldcoll, Object newvalue) {
473          return c1.validateAddValue(oldcoll, newvalue) && c2.validateAddValue(oldcoll, newvalue);
474      }
475      
476      public boolean validateRemoveValue(Collection oldcoll, Object victim) {
477          return c1.validateAddValue(oldcoll, victim) && c2.validateAddValue(oldcoll, victim);
478      }
479      
480      public String toString() {
481        return "And(" + c1 + ", " + c2 + ")";
482      }
483    }
484    
485    /**
486     * A collection constraint that accepts items iff they are accepted by either
487     * child constraints. This effectively matches the union of the items
488     * matched by the two constraints. Use this to combine multiple constraints. You can make one
489     *            or both of the children Or instances if you need a wider
490     *            union.
491     *
492     * @author Matthew Pocock
493     * @author Thomas Down
494     */
495    public class Or implements CollectionConstraint {
496      private CollectionConstraint c1;
497      private CollectionConstraint c2;
498      
499      /**
500       * Create a new <code>Or</code> from two child constraints.
501       *
502       * @param c1 the first child
503       * @param c2 the seccond child
504       */
505      public Or(CollectionConstraint c1, CollectionConstraint c2) {
506        this.c1 = c1;
507        this.c2 = c2;
508      }
509      
510      /**
511       * Get the first child CollectionConstraint.
512       *
513       * @return the first child CollectionConstraint
514       */
515      public CollectionConstraint getChild1() {
516        return c1;
517      }
518      
519      /**
520       * Get the seccond child CollectionConstraint.
521       *
522       * @return the seccond child CollectionConstraint
523       * 
524       */
525      public CollectionConstraint getChild2() {
526        return c2;
527      }
528      
529      public boolean accept(Object object) {
530        return c1.accept(object) || c2.accept(object);
531      }
532      
533      public boolean subConstraintOf(CollectionConstraint pc) {
534        return c1.subConstraintOf(pc) || c2.subConstraintOf(pc);
535      }
536      
537      public boolean validateAddValue(Collection oldcoll, Object newvalue) {
538          return c1.validateAddValue(oldcoll, newvalue) || c2.validateAddValue(oldcoll, newvalue);
539      }
540      
541      public boolean validateRemoveValue(Collection oldcoll, Object victim) {
542          return c1.validateAddValue(oldcoll, victim) || c2.validateAddValue(oldcoll, victim);
543      }
544      
545      public String toString() {
546        return "Or(" + c1 + ", " + c2 + ")";
547      }
548    }
549}
550