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.HashSet;
025import java.util.Iterator;
026import java.util.Set;
027
028/**
029 * <p><code>PropertyConstraint</code>s describes a constraint applied
030 * to the members of an annotation bundle.</p>
031 *
032 * <p><code>PropertyConstraint</code>s are usually used in conjunction
033 * with the <code>AnnotationType</code> interface to describe a class
034 * of annotations by the values of their properties. In this way, you
035 * can generate controlled vocabularies over Java objects.</p>
036 *
037 * <p>The constraints accept or reject individual objects.
038 * In general, it is not possible to get back a set of all items
039 * that would be accepted by a particular constraint.</p>
040 * Instantiate PropertyConstraint classes when populating an
041 *            AnnotationType instance
042 * Implement PropertyContraint to provide meta-data about a new
043 *            type of object relationship. For example, if there was a
044 *            data-structure representing an inheritance hierachy, then an
045 *            implementation of PropertyConstraint could be written that allowed
046 *            a propertie's value to be constrained to be a child of a
047 *            particular node in the hierachy
048 * @since 1.3
049 * @author Matthew Pocock
050 * @author Keith James
051 * @author Thomas Down
052 *
053 * 
054 */
055public interface PropertyConstraint {
056    /**
057     * <code>accept</code> returns true if the value fulfills the
058     * constraint.
059     *
060     * Manually compare items with the PropertyConstraint. Node:
061     * this will ususaly be done for you in an AnnotationType instance
062     *
063     * Use for implementing accept() on AnnotatoinType
064     * 
065     * @param value an <code>Object</code> to check.
066     * @return a <code>boolean</code>.
067     */
068    boolean accept(Object value);
069
070    /**
071     * <p><code>subConstraintOf</code> returns true if the constraint
072     * is a sub-constraint.<p>
073     *
074     * <p>A pair of constraints super and sub are in a
075     * superConstraint/subConstraint relationship if every object
076     * accepted by sub is also accepted by super. To put it another
077     * way, if instanceOf was used as a set-membership indicator
078     * function over some set of objects, then the set produced by
079     * super would be a superset of that produced by sub.</p>
080     *
081     * <p>It is not expected that constraints will neccesarily
082     * maintain references to super/sub types. It will be more usual
083     * to infer this relationship by introspecting the constraints
084     * themselves. For example,
085     * <code>PropertyConstraint.ByClass</code> will infer
086     * subConstraintOf by looking at the possible class of all items
087     * matching subConstraint.</p>
088     *
089     * Useful when attempting to compare two constraints to see
090     * if it is necisary to retain both. You may want to check the more
091     * general or the more specific constraint only.
092     * 
093     * @param subConstraint a <code>PropertyConstraint</code> to check.
094     * @return a <code>boolean</code>.
095     * 
096     */
097    boolean subConstraintOf(PropertyConstraint subConstraint);
098        
099    /**
100     * <code>ANY</code> is a constraint which accepts a property for
101     * addition under all conditions.
102     *
103     * Whenever a PropertyConstraint is needed and you want to allow
104     * any value there
105     */
106    PropertyConstraint ANY = new AnyPropertyConstraint();
107    
108    /**
109     * <code>NONE</code> is a constraint which accepts no value for a property
110     * under any condition.
111     *
112     * Whenever a PropertyConstraint is needed and you want to
113     * dissalow all values there e.g. when marking a property as having to be unset
114     */
115    PropertyConstraint NONE = new NonePropertyConstraint();
116  
117    /**
118     * <code>ByClass</code> accepts a property value if it is an
119     * instance of a specific Java class.
120     *
121     * @since 1.3
122     * @author Matthew Pocock
123     * Constrain a property to containing values of a particular class
124     *       e.g. <code>new ByClass(String.class)</code> or 
125     *       <code>new ByClass(Double)</code> will ensure
126     *       that the property is a String or a Double respecitvely.
127     */
128    class ByClass implements PropertyConstraint {
129        private Class cl;
130
131        /**
132         * Create a new ByClass instance.
133         *
134         * @param cl the Class that all properties must be assignable to
135         */
136        public ByClass(Class cl) {
137            this.cl = cl;
138        }
139
140      /**
141       * Get the Class used as the constraint.
142       *
143       * @return the Class all properties must be instances of
144       */
145        public Class getPropertyClass() {
146            return cl;
147        }
148
149        public boolean accept(Object value) {
150            return cl.isInstance(value);
151        }
152
153        public boolean subConstraintOf(PropertyConstraint subConstraint) {
154            if (subConstraint instanceof ByClass) {
155                ByClass sc = (ByClass) subConstraint;
156                return cl.isAssignableFrom(sc.getPropertyClass());
157            } else if(subConstraint instanceof Enumeration) {
158              Set values = ((Enumeration) subConstraint).getValues();
159              for(Iterator i = values.iterator(); i.hasNext(); ) {
160                if(!accept(i.next())) {
161                  return false;
162                }
163              }
164              
165              return true;
166            } else if(subConstraint instanceof ExactValue) {
167              return accept(((ExactValue) subConstraint).getValue());
168            }
169
170            return false;
171        }
172        
173        public String toString() {
174          return "Class:" + cl.toString();
175        }
176    }
177
178    /**
179     * <p><code>ByAnnotationType</code> accepts a property value if it
180     * belongs to type defined by AnnotationType.</p>
181     *
182     * <p>If you had an Embl AnnotationType then you could say that the REF
183     * property must contain annotations that fits your reference AnnotationType
184     * (with author list, title, optinal medline ID etc.).</p>
185     * If you wish to build a tree of Annotations so that a
186     * property in one is guaranteed to be itself an Annotation of a
187     * particular type. Effectively this lets you build your own
188     * type system using AnnotationType and PropertyConstraint.
189     * @since 1.3
190     * @author Matthew Pocock
191     *
192     */
193    class ByAnnotationType implements PropertyConstraint {
194        private AnnotationType annType;
195
196        /**
197         * Create a new constraint by type.
198         *
199         * @param annType the AnnotationType to constrain to
200         */
201        public ByAnnotationType(AnnotationType annType) {
202            this.annType = annType;
203        }
204
205      /**
206       * Get the AnnotationType used as a constraint.
207       *
208       * @return the AnnotationType constraint
209       */
210        public AnnotationType getAnnotationType() {
211            return annType;
212        }
213
214        public boolean accept(Object value) {
215            if (value instanceof Annotation) {
216                return annType.instanceOf((Annotation) value);
217            }
218
219            return false;
220        }
221
222        public boolean subConstraintOf(PropertyConstraint subConstraint) {
223            if (subConstraint instanceof ByAnnotationType) {
224                ByAnnotationType at = (ByAnnotationType) subConstraint;
225                return annType.subTypeOf(at.getAnnotationType());
226            }
227
228            return false;
229        }
230        
231        public String toString() {
232          return "AnnotationType:" + annType.getProperties();
233        }
234    }
235
236    /**
237     * <p>Matches properties if they have exactly this one value.</p>
238     *
239     * <p>This is like the extreme case of an Enumeration which has just one
240     * member. It is most usefull for selecting annotations with a particular
241     * property set to a particular value e.g. ID="01234".</p>
242     *
243     * 
244     * If you want to declare that a property must have a single value
245     *
246     * In conjunction with CardinalityConstraint.ZERO_OR_ONE you
247     * could make a property that is potional but if present must have this
248     * value
249     *
250     * Use with FilterUtils.byAnnotation() to search for features
251     * with properties set to specific values
252     * @author Matthew Pocock
253     */
254    class ExactValue implements PropertyConstraint {
255      private Object value;
256      
257      /**
258       * Get a PropertyConstraint that matches this object and all those that
259       * are equal to it (by the Object.equals() method).
260       *
261       * @param value  the Object to match against
262       */
263      public ExactValue(Object value) {
264        this.value = value;
265      }
266
267      /**
268       * Get the value that all properties must match.
269       *
270       * @return  the value Object
271       */
272      public Object getValue() {
273        return value;
274      }
275      
276      public boolean accept(Object obj) {
277        return value.equals(obj);
278      }
279      
280      public boolean subConstraintOf(PropertyConstraint pc) {
281        if(pc instanceof ExactValue) {
282          return value.equals(((ExactValue) pc).getValue());
283        } else if(pc instanceof Enumeration) {
284          Enumeration e = (Enumeration) pc;
285          return e.getValues().size() == 1 && e.accept(value);
286        }
287        
288        return false;
289      }
290      
291      public String toString() {
292        return "ExactValue: " + value;
293      }
294    }
295    
296    /**
297     * <code>Enumeration</code> accepts a property if it is present
298     * in the specified set of values.
299     *
300     * 
301     *
302     * If you want to declare that a property must be within a range
303     * of values, for example PRIMARY_COLOR is one of "RED, YELLOW, BLUE"
304     *
305     * Use with FilterUtils.byAnnotation() to search for features
306     * with properties set to a range of values
307     * @since 1.3
308     * @author Matthew Pocock
309     */
310    class Enumeration implements PropertyConstraint {
311        private Set values;
312
313        /**
314         * Creates a new <code>Enumeration</code> using the members of
315         * the specified set as a constraint.
316         *
317         * @param values a <code>Set</code> of all possible values
318         * 
319         */
320        public Enumeration(Set values) {
321            this.values = values;
322        }
323        
324        /**
325         * Creates a new <code>Enumeration</code> using the elements of the
326         * specified array as a constraint.
327         *
328         * @param values an <code>Array</code> of all possible values
329         * 
330         */
331        public Enumeration(Object[] values) {
332          this.values = new HashSet();
333          for(int i = 0; i < values.length; i++) {
334            this.values.add(values[i]);
335          }
336        }
337
338        /**
339         * <code>getValues</code> returns the set of values which
340         * constrain the property.
341         *
342         * @return a <code>Set</code>.
343         */
344        public Set getValues() {
345            return values;
346        }
347
348        public boolean accept(Object value) {
349            return values.contains(value);
350        }
351
352        public boolean subConstraintOf(PropertyConstraint subConstraint) {
353            if (subConstraint instanceof Enumeration) {
354                Enumeration subE = (Enumeration) subConstraint;
355                return values.containsAll(subE.getValues());
356            } else if(subConstraint instanceof ExactValue) {
357              return accept(((ExactValue) subConstraint).getValue());
358            }
359
360            return false;
361        }
362        
363        public String toString() {
364          return "Enumeration:" + values;
365        }
366    }
367    
368    /**
369     * A property constraint that accpepts items iff they are accepted by both
370     * child constraints. This effectively matches the intersection of the items
371     * matched by the two constraints.
372     *
373     * 
374     * Use this to combine multiple constraints. You can make one
375     *            or both of the children And instances if you need a tighter
376     *            intersection.
377     * @author Matthew Pocock
378     */
379    class And implements PropertyConstraint {
380      private PropertyConstraint c1;
381      private PropertyConstraint c2;
382      
383      /**
384       * Create a new <code>And</code> from two child constraints.
385       *
386       * @param c1 the first child
387       * @param c2 the seccond child
388       */
389      public And(PropertyConstraint c1, PropertyConstraint c2) {
390        this.c1 = c1;
391        this.c2 = c2;
392      }
393      
394      /**
395       * Get the first child PropertyConstraint.
396       *
397       * @return the first child PropertyConstraint
398       *
399       */
400      public PropertyConstraint getChild1() {
401        return c1;
402      }
403      
404      /**
405       * Get the seccond child PropertyConstraint.
406       *
407       * @return the seccond child PropertyConstraint
408       *
409       * 
410       */
411      public PropertyConstraint getChild2() {
412        return c2;
413      }
414      
415      public boolean accept(Object object) {
416        return c1.accept(object) && c2.accept(object);
417      }
418      
419      public boolean subConstraintOf(PropertyConstraint pc) {
420        return c1.subConstraintOf(pc) && c2.subConstraintOf(pc);
421      }
422      
423      public String toString() {
424        return "And(" + c1 + ", " + c2 + ")";
425      }
426    }
427    
428    /**
429     * A property constraint that accepts items iff they are accepted by either
430     * child constraints. This effectively matches the union of the items
431     * matched by the two constraints. Use this to combine multiple constraints. You can make one
432     *            or both of the children Or instances if you need a wider
433     *            union.
434     *
435     * @author Matthew Pocock
436     */
437    class Or implements PropertyConstraint {
438      private PropertyConstraint c1;
439      private PropertyConstraint c2;
440      
441      /**
442       * Create a new <code>Or</code> from two child constraints.
443       *
444       * @param c1 the first child
445       * @param c2 the seccond child
446       */
447      public Or(PropertyConstraint c1, PropertyConstraint c2) {
448        this.c1 = c1;
449        this.c2 = c2;
450      }
451      
452      /**
453       * Get the first child PropertyConstraint.
454       *
455       * @return the first child PropertyConstraint
456       * 
457       */
458      public PropertyConstraint getChild1() {
459        return c1;
460      }
461      
462      /**
463       * Get the seccond child PropertyConstraint.
464       *
465       * @return the seccond child PropertyConstraint
466       * 
467       */
468      public PropertyConstraint getChild2() {
469        return c2;
470      }
471      
472      public boolean accept(Object object) {
473        return c1.accept(object) || c2.accept(object);
474      }
475      
476      public boolean subConstraintOf(PropertyConstraint pc) {
477        return c1.subConstraintOf(pc) || c2.subConstraintOf(pc);
478      }
479      
480      public String toString() {
481        return "Or(" + c1 + ", " + c2 + ")";
482      }
483    }
484}
485
486